├── .gitignore ├── demo.gif ├── ISSUE_TEMPLATE.md ├── .gitmodules ├── .zuul.yml ├── lib ├── evtx.js ├── eventprop.js ├── mark.js ├── indicator.js ├── raf.js ├── vertical.js ├── phantom.js ├── element.js ├── rangebar.js └── range.js ├── bower.json ├── Makefile ├── .travis.yml ├── package.json ├── licence.md ├── contributing.md ├── demos ├── requirejs.html └── index.html ├── elessar.css ├── test ├── utils.js ├── rangebar.js └── functional.js ├── CODE_OF_CONDUCT.md ├── readme.md └── dist ├── elessar.min.js └── elessar.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | bower_components 3 | t.* 4 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarterpast/Elessar/HEAD/demo.gif -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Elessar is unmaintained. 2 | 3 | I am unlikely to respond to your issue. 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs"] 2 | path = docs 3 | url = https://github.com/quarterto/Elessar.git 4 | branch = gh-pages 5 | -------------------------------------------------------------------------------- /.zuul.yml: -------------------------------------------------------------------------------- 1 | ui: tape 2 | browsers: 3 | - name: chrome 4 | version: latest 5 | - name: ie 6 | version: 9..latest 7 | - name: firefox 8 | version: latest 9 | browserify: 10 | - transform: cssify 11 | -------------------------------------------------------------------------------- /lib/evtx.js: -------------------------------------------------------------------------------- 1 | var has = Object.prototype.hasOwnProperty; 2 | 3 | module.exports = function getEvtX(prop, event) { 4 | return has.call(event, prop) ? event[prop] 5 | : event.originalEvent && has.call(event.originalEvent, 'touches') ? event.originalEvent.touches[0][prop] 6 | : 0; 7 | }; 8 | -------------------------------------------------------------------------------- /lib/eventprop.js: -------------------------------------------------------------------------------- 1 | var has = Object.prototype.hasOwnProperty; 2 | 3 | module.exports = function getEventProperty(prop, event) { 4 | return has.call(event, prop) ? event[prop] 5 | : event.originalEvent && event.originalEvent.touches ? event.originalEvent.touches[0][prop] 6 | : undefined; 7 | }; 8 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elessar", 3 | "description": "Draggable multiple range sliders", 4 | "authors": [ 5 | "Matt Brennan " 6 | ], 7 | "main": "dist/elessar.min.js", 8 | "license": "MIT", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test", 14 | "tests" 15 | ], 16 | "dependencies": { 17 | "jquery": ">=1.8" 18 | }, 19 | "devDependencies": { 20 | "requirejs": "~2.1.9", 21 | "moment": "~2.4.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/mark.js: -------------------------------------------------------------------------------- 1 | var Element = require('./element.js'); 2 | 3 | var Mark = Element.extend({ 4 | initialize: function initialize(options) { 5 | initialize.super$.call(this, '
'); 6 | this.$el.css(options.perant.edge('start'), (options.value * 100) + '%'); 7 | 8 | if(typeof options.label === 'function') { 9 | this.$el.text(options.label.call(this, options.perant.normalise(options.value))); 10 | } else if(typeof options.label === 'string') { 11 | this.$el.text(options.label); 12 | } else { 13 | this.$el.text(options.perant.normalise(options.value)); 14 | } 15 | } 16 | }); 17 | 18 | module.exports = Mark; 19 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | export SHELL := /bin/bash 2 | export PATH := $(shell npm bin):$(PATH) 3 | 4 | ENTRY_FILE="./lib/rangebar.js" 5 | DEPS := $(shell node_modules/.bin/browserify --list $(ENTRY_FILE)) 6 | TEST_FILES = $(filter-out test/utils.js, $(wildcard test/*.js)) 7 | 8 | all: dist/elessar.js 9 | min: dist/elessar.min.js 10 | 11 | dist/%.min.js: dist/%.js 12 | uglifyjs $< -o $@ 13 | 14 | dist/%.js: $(DEPS) 15 | mkdir -p $(@D) 16 | browserify -t browserify-global-shim -s RangeBar $(ENTRY_FILE) -o $@ 17 | 18 | .PHONY: clean test test-local 19 | 20 | clean: 21 | rm -rf dist 22 | 23 | test-local: $(DEPS) $(TEST_FILES) 24 | zuul --phantom -- $(TEST_FILES) | tap-spec 25 | 26 | test: $(DEPS) $(TEST_FILES) 27 | zuul -- $(TEST_FILES) 28 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | cache: 4 | directories: 5 | - node_modules 6 | notifications: 7 | email: false 8 | node_js: 9 | - '4' 10 | before_install: 11 | - npm i -g npm@^2.0.0 12 | before_script: 13 | - npm prune 14 | after_success: 15 | - npm run semantic-release 16 | branches: 17 | except: 18 | - "/^v\\d+\\.\\d+\\.\\d+$/" 19 | env: 20 | global: 21 | - secure: aHGhw+qTXAA06ZMTPdO1AiE52pY2j/6Vhw1iXJjSVZ4U1Qw9LzY0dTX+715HdppFVeVpi46geogEyAM21k1ZEICSNF9r5+RW3Z93fwqIwugnhevJM8SGLZOWf5UXTRIRX8Z2zXTbcyV+tOXBTjI9P78fol7GFHDPYUujxzmztA0= 22 | - secure: Jcxl7H/NhcL9RR1JylY24JJQj4t8b3lCAuDmEEHP3Gr4xvVURI75QnukdQgduf9QM7ez9qpXKOZxyykPlDAaLUbLrui0mEFLwlPhxk57pMYiNoRqksc5fdGEaY+U49QHFukzOeBgEfikjgfqjruUCwoE8nDcye/a/dIPMXe3taQ= 23 | -------------------------------------------------------------------------------- /lib/indicator.js: -------------------------------------------------------------------------------- 1 | var Element = require('./element'); 2 | var vertical = require('./vertical'); 3 | 4 | var Indicator = Element.extend(vertical).extend({ 5 | initialize: function initialize(options) { 6 | initialize.super$.call(this,'
'); 7 | if(options.indicatorClass) this.$el.addClass(options.indicatorClass); 8 | if(options.value) this.val(options.value); 9 | this.options = options; 10 | }, 11 | 12 | val: function(pos) { 13 | if(pos) { 14 | if(this.isVertical()) { 15 | this.draw({top: 100*pos + '%'}); 16 | } else { 17 | this.draw({left: 100*pos + '%'}); 18 | } 19 | this.value = pos; 20 | } 21 | return this.value; 22 | } 23 | }); 24 | 25 | module.exports = Indicator; 26 | -------------------------------------------------------------------------------- /lib/raf.js: -------------------------------------------------------------------------------- 1 | // thanks to http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 2 | var lastTime = 0; 3 | var vendors = ['webkit', 'moz'], requestAnimationFrame = window.requestAnimationFrame; 4 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 5 | requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 6 | } 7 | 8 | if (!requestAnimationFrame) { 9 | requestAnimationFrame = function(callback, element) { 10 | var currTime = new Date().getTime(); 11 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 12 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 13 | timeToCall); 14 | lastTime = currTime + timeToCall; 15 | return id; 16 | }; 17 | } 18 | 19 | module.exports = requestAnimationFrame; 20 | -------------------------------------------------------------------------------- /lib/vertical.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | isVertical: function() { 3 | return this.options.vertical; 4 | }, 5 | 6 | ifVertical: function(v, h) { 7 | return this.isVertical() ? v : h; 8 | }, 9 | edge: function(which) { 10 | if(which === 'start') { 11 | return this.ifVertical('top', 'left'); 12 | } else if(which === 'end') { 13 | return this.ifVertical('bottom', 'right'); 14 | } 15 | throw new TypeError('What kind of an edge is ' + which); 16 | }, 17 | totalSize: function() { 18 | return this.$el[this.ifVertical('height','width')](); 19 | }, 20 | edgeProp: function(edge, prop) { 21 | var o = this.$el[prop](); 22 | return o[this.edge(edge)]; 23 | }, 24 | startProp: function(prop) { 25 | return this.edgeProp('start', prop); 26 | }, 27 | endProp: function(prop) { 28 | return this.edgeProp('end', prop); 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /lib/phantom.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var Range = require('./range'); 3 | var requestAnimationFrame = require('./raf'); 4 | 5 | var Phantom = Range.extend({ 6 | initialize: function initialize(options) { 7 | initialize.super$.call(this, $.extend({ 8 | readonly: true, 9 | label: '+' 10 | }, options)); 11 | this.$el.addClass('elessar-phantom'); 12 | this.on('mousedown.elessar touchstart.elessar', $.proxy(this.mousedown, this)); 13 | }, 14 | 15 | mousedown: function(ev) { 16 | if(ev.which === 1) { // left mouse button 17 | var startX = ev.pageX; 18 | var newRange = this.options.perant.addRange(this.val()); 19 | this.remove(); 20 | this.options.perant.trigger('addrange', [newRange.val(), newRange]); 21 | requestAnimationFrame(function() { 22 | newRange.$el.find('.elessar-handle:first-child').trigger(ev.type); 23 | }); 24 | } 25 | }, 26 | 27 | removePhantom: function() { 28 | // NOOP 29 | } 30 | }); 31 | 32 | module.exports = Phantom; 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elessar", 3 | "description": "Draggable multiple range sliders", 4 | "main": "lib/rangebar.js", 5 | "author": "Matt Brennan ", 6 | "license": "MIT", 7 | "scripts": { 8 | "test": "make test", 9 | "prepublish": "make -B all min", 10 | "semantic-release": "semantic-release pre && npm publish && semantic-release post" 11 | }, 12 | "dependencies": { 13 | "estira": "^0.3.3", 14 | "jquery": "^1.12.0" 15 | }, 16 | "devDependencies": { 17 | "browserify": "^13.0.0", 18 | "browserify-global-shim": "^1.0.3", 19 | "cssify": "^0.5.1", 20 | "istanbul": "^0.2.11", 21 | "phantomjs": "^2.1.3", 22 | "semantic-release": "^4.3.5", 23 | "semver": "^4.1.0", 24 | "tap-spec": "^0.2.0", 25 | "tape": "^2.13.3", 26 | "tin": "^0.5.0", 27 | "uglify-js": "^2.4", 28 | "zuul": "^3.9.0" 29 | }, 30 | "browserify-global-shim": { 31 | "jquery": "$" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "https://github.com/quarterto/Elessar.git" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /licence.md: -------------------------------------------------------------------------------- 1 | Copyright © 2013 [Matt Brennan](mailto:matt@anqagroup.com) 2 | ==================================================================== 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | **The Software is provided *as is*, without warranty of any kind, *express* or *implied*, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the software or the use or other dealings in the Software.** 8 | -------------------------------------------------------------------------------- /contributing.md: -------------------------------------------------------------------------------- 1 | Contributing to Elessar 2 | ======================= 3 | 4 | Thanks for your contribution! Let's check a few things before you get started: 5 | 6 | Code of Conduct 7 | --- 8 | 9 | First and foremost, please read and follow the [Code of Conduct](CODE_OF_CONDUCT.md). This outlines what is and is not acceptible behaviour for participants in the repository, as well as how to raise a complaint. 10 | 11 | Writing an issue 12 | --- 13 | 14 | Please include: 15 | 16 | - Your browser and operating system 17 | - The code you're using to set up Elessar, including all configuration. If possible, create a [JSBin](http://jsbin.com/) to demonstrate the issue 18 | - Concise but exact steps to reproduce the issue 19 | - What you expected to happen, and what actually happened 20 | 21 | Creating a Pull Request 22 | --- 23 | 24 | - Elessar uses the [Angular Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153). In short: 25 | - If it's a bugfix, prefix the message with `fix:` 26 | - If it's a new feature, prefix with `feat:` 27 | - If it introduces a breaking change, explain it with `BREAKING CHANGE:` in the [commit description](http://stackoverflow.com/questions/16122234) 28 | - Take the time before pushing to run the tests locally, using `make test-local`. A more comprehensive test suite will run when you push 29 | - Don't be disheartened if your PR is rejected! We appreciate the time you took, but not every change is a good fit. Stick around and keep contributing! 30 | -------------------------------------------------------------------------------- /lib/element.js: -------------------------------------------------------------------------------- 1 | var Base = require('estira'); 2 | var $ = require('jquery'); 3 | var requestAnimationFrame = require('./raf'); 4 | 5 | var has = Object.prototype.hasOwnProperty; 6 | 7 | var Element = Base.extend({ 8 | initialize: function(html) { 9 | this.$el = $(html); 10 | this.$data = {}; 11 | this.$el.data('element', this); 12 | }, 13 | draw: function(css) { 14 | var self = this; 15 | if(this.drawing) return this.$el; 16 | requestAnimationFrame(function() { 17 | self.drawing = false; 18 | self.$el.css(css); 19 | }); 20 | this.drawing = true; 21 | return this.$el; 22 | }, 23 | on: function() { 24 | this.$el.on.apply(this.$el, arguments); 25 | return this; 26 | }, 27 | one: function() { 28 | this.$el.one.apply(this.$el, arguments); 29 | return this; 30 | }, 31 | off: function() { 32 | this.$el.off.apply(this.$el, arguments); 33 | return this; 34 | }, 35 | trigger: function() { 36 | this.$el.trigger.apply(this.$el, arguments); 37 | return this; 38 | }, 39 | remove: function() { 40 | this.$el.remove(); 41 | }, 42 | detach: function() { 43 | this.$el.detach(); 44 | }, 45 | data: function(key, value) { 46 | var obj = key; 47 | if(typeof key === 'string') { 48 | if(typeof value === 'undefined') { 49 | return this.$data[key]; 50 | } 51 | obj = {}; 52 | obj[key] = value; 53 | } 54 | $.extend(this.$data, obj); 55 | return this; 56 | } 57 | }); 58 | 59 | module.exports = Element; 60 | -------------------------------------------------------------------------------- /demos/requirejs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |

10 | 
11 | 
52 | 
53 | 


--------------------------------------------------------------------------------
/demos/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 | 
 5 | 
 6 | 
 7 | 
 8 | 
 9 | 
10 | 
11 | 
12 | 

13 | 
14 | 
54 | 
55 | 


--------------------------------------------------------------------------------
/elessar.css:
--------------------------------------------------------------------------------
  1 | .elessar-handle {
  2 |   display: block;
  3 |   background: black;
  4 |   height: 100%;
  5 |   width: 5px;
  6 |   cursor: ew-resize;
  7 |   position: absolute;
  8 |   top: 0;
  9 |   bottom: 0;
 10 | }
 11 | .elessar-handle:first-child {
 12 |   left: 0;
 13 | }
 14 | .elessar-handle:last-child {
 15 |   right: 0;
 16 | }
 17 | .elessar-vertical .elessar-handle {
 18 |   height: 5px;
 19 |   width: 100%;
 20 |   left: 0;
 21 |   right: 0;
 22 |   top: auto;
 23 |   bottom: auto;
 24 |   cursor: ns-resize;
 25 | }
 26 | .elessar-vertical .elessar-handle:first-child {
 27 |   top: 0;
 28 | }
 29 | .elessar-vertical .elessar-handle:last-child {
 30 |   bottom: 0;
 31 | }
 32 | .elessar-indicator {
 33 |   position: absolute;
 34 |   background: black;
 35 |   border-color: white;
 36 |   border-style: solid;
 37 |   border-width: 0 1px;
 38 |   width: 2px;
 39 |   height: 100%;
 40 |   z-index: 15;
 41 | }
 42 | .elessar-vertical .elessar-indicator {
 43 |   width: 100%;
 44 |   height: 2px;
 45 | }
 46 | .elessar-range {
 47 |   position: absolute;
 48 |   cursor: pointer;
 49 |   cursor: -webkit-grab;
 50 |   cursor: -moz-grab;
 51 |   cursor: grab;
 52 |   overflow: hidden;
 53 |   text-overflow: ellipsis;
 54 |   white-space: nowrap;
 55 |   background: blue;
 56 |   width: 0;
 57 |   z-index: 10;
 58 | }
 59 | .elessar-vertical .elessar-range {
 60 |   width: auto;
 61 |   height: 0;
 62 | }
 63 | .elessar-label {
 64 |   position: absolute;
 65 |   z-index: 5;
 66 |   pointer-events: none;
 67 |   border-left: 1px solid #999;
 68 |   padding-left: 10px;
 69 | }
 70 | .elessar-readonly {
 71 |   opacity: 0.6;
 72 |   cursor: default !important;
 73 | }
 74 | .elessar-phantom {
 75 |   background-color: transparent;
 76 |   background: rgba(0,0,0,0.5);
 77 |   color: white;
 78 |   cursor: pointer !important;
 79 | }
 80 | .elessar-rangebar {
 81 |   position: relative;
 82 |   height: 30px;
 83 |   line-height: 30px;
 84 |   background: #ccc;
 85 |   -webkit-touch-callout: none;
 86 |   -webkit-user-select: none;
 87 |   -khtml-user-select: none;
 88 |   -moz-user-select: none;
 89 |   -ms-user-select: none;
 90 |   user-select: none;
 91 | }
 92 | .elessar-delete-confirm {
 93 |   background: red;
 94 | }
 95 | body.elessar-resizing, body.elessar-dragging {
 96 |   -webkit-touch-callout: none;
 97 |   -webkit-user-select: none;
 98 |   -khtml-user-select: none;
 99 |   -moz-user-select: none;
100 |   -ms-user-select: none;
101 |   user-select: none;
102 | }
103 | body.elessar-resizing,
104 | body.elessar-resizing .bar,
105 | body.elessar-resizing .handle {
106 |   cursor: ew-resize !important;
107 | }
108 | body.elessar-resizing.elessar-resizing-vertical,
109 | body.elessar-resizing.elessar-resizing-vertical .bar,
110 | body.elessar-resizing.elessar-resizing-vertical .handle {
111 |   cursor: ns-resize !important;
112 | }
113 | body.elessar-dragging,
114 | body.elessar-dragging .bar,
115 | body.elessar-dragging .handle {
116 |   cursor: move !important;
117 |   cursor: -webkit-grabbing !important;
118 |   cursor: -moz-grabbing !important;
119 |   cursor: grabbing !important;
120 | }
121 | 


--------------------------------------------------------------------------------
/test/utils.js:
--------------------------------------------------------------------------------
  1 | var $ = require('jquery');
  2 | var tape = require('tape');
  3 | var raf = require('../lib/raf.js');
  4 | 
  5 | $.fn.isAfter = function(el) {
  6 | 	return $(el).nextAll(this).length > 0;
  7 | };
  8 | 
  9 | $.fn.isBefore = function(el) {
 10 | 	return $(el).prevAll(this).length > 0;
 11 | };
 12 | 
 13 | $.fn.contains = function(el) {
 14 | 	return this.has(el).length > 0;
 15 | };
 16 | 
 17 | $.fn.btnClick = function(which) {
 18 | 	var e = $.Event('mousedown');
 19 | 	e.which = which || 1;
 20 | 	this.trigger(e);
 21 | }
 22 | 
 23 | function waitForAnimation(fn) {
 24 | 	raf(function() {
 25 | 		process.nextTick(fn);
 26 | 	});
 27 | }
 28 | 
 29 | function move(pos, el) {
 30 | 	var e = $.Event('mousemove');
 31 | 	e.clientX = e.pageX = pos.x;
 32 | 	e.clientY = e.pageY = pos.y;
 33 | 	$(el || document).trigger(e);
 34 | }
 35 | 
 36 | function step(steps, cb) {
 37 | 	if(steps.length) {
 38 | 		waitForAnimation(function() {
 39 | 			steps[0]();
 40 | 			step(steps.slice(1), cb);
 41 | 		});
 42 | 	} else {
 43 | 		cb();
 44 | 	}
 45 | }
 46 | 
 47 | var end = tape.Test.prototype.end;
 48 | tape.Test.prototype.end = function() {
 49 | 	$('body').empty().removeClass();
 50 | 	end.apply(this, arguments);
 51 | }
 52 | 
 53 | function valuesEqual(a, b) {
 54 | 	return a.length === b.length && a.every(function(av, i) {
 55 | 		var bv = b[i];
 56 | 		return floatEqual(av[0], bv[0]) && floatEqual(av[1], bv[1]);
 57 | 	});
 58 | }
 59 | 
 60 | function floatEqual(a, b) {
 61 | 	return Math.abs(a - b) < 1
 62 | }
 63 | 
 64 | tape.Test.prototype.rangebarValuesEqual = function(a, b, msg, extra) {
 65 | 	this._assert(valuesEqual(a, b), {
 66 | 			message : msg || 'rangebar values should be equal',
 67 | 			operator : 'equal',
 68 | 			actual : a,
 69 | 			expected : b,
 70 | 			extra : extra
 71 | 	});
 72 | }
 73 | 
 74 | tape.Test.prototype.floatEqual = function(a, b, msg, extra) {
 75 | 	this._assert(floatEqual(a, b), {
 76 | 			message : msg || 'floats should be equal',
 77 | 			operator : 'equal',
 78 | 			actual : a,
 79 | 			expected : b,
 80 | 			extra : extra
 81 | 	});
 82 | }
 83 | 
 84 | function drag(el, pos, cb) {
 85 | 	el.mousedown();
 86 | 	var moves = [];
 87 | 
 88 | 	if(pos.step) {
 89 | 		var large = Math.abs(pos.x) > Math.abs(pos.y) ? pos.x : pos.y;
 90 | 		var xstep = pos.x / large;
 91 | 		var ystep = pos.y / large;
 92 | 		var xstart = el.offset().left + (pos.rightEdge ? el.width() : 0);
 93 | 		var ystart = el.offset().top + (pos.bottomEdge ? el.height() : 0);
 94 | 
 95 | 		if(large > 0) {
 96 | 			for(var x = xstart, y = ystart; x < xstart + pos.x || y < ystart + pos.y; x += xstep, y += ystep) {
 97 | 				moves.push({x: x, y: y});
 98 | 			}
 99 | 		} else {
100 | 			for(var x = xstart, y = ystart; x > xstart + pos.x || y > ystart + pos.y; x -= xstep, y -= ystep) {
101 | 				moves.push({x: x, y: y});
102 | 			}
103 | 		}
104 | 	}
105 | 
106 | 	moves.push({
107 | 		x: pos.x + el.offset().left + (pos.rightEdge ? el.width() : 0),
108 | 		y: pos.y + el.offset().top + (pos.bottomEdge ? el.height() : 0)
109 | 	});
110 | 
111 | 	step(moves.map(function(pos) {
112 | 		return function() {
113 | 			move(pos);
114 | 		};
115 | 	}), function() {
116 | 		if(!pos.keepMouseDown) {
117 | 			var e = $.Event('mouseup');
118 | 			e.clientX = moves[moves.length - 1].x;
119 | 			e.clientY = moves[moves.length - 1].y;
120 | 			el.trigger(e);
121 | 		}
122 | 		if(cb) cb();
123 | 	});
124 | }
125 | 
126 | module.exports = {
127 | 	waitForAnimation: waitForAnimation,
128 | 	drag: drag,
129 | 	move: move
130 | };
131 | 


--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
 1 | # Contributor Covenant Code of Conduct
 2 | 
 3 | ## Our Pledge
 4 | 
 5 | In the interest of fostering an open and welcoming environment, we as
 6 | contributors and maintainers pledge to making participation in our project and
 7 | our community a harassment-free experience for everyone, regardless of age, body
 8 | size, disability, ethnicity, gender identity and expression, level of experience,
 9 | nationality, personal appearance, race, religion, or sexual identity and
10 | orientation.
11 | 
12 | ## Our Standards
13 | 
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 | 
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 | 
23 | Examples of unacceptable behavior by participants include:
24 | 
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 |   address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 |   professional setting
33 | 
34 | ## Our Responsibilities
35 | 
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 | 
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 | 
46 | ## Scope
47 | 
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 | 
55 | ## Enforcement
56 | 
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at https://gitter.im/quarterto/Elessar/elessar-maintainers. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 | 
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 | 
68 | ## Attribution
69 | 
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at [http://contributor-covenant.org/version/1/4][version]
72 | 
73 | [homepage]: http://contributor-covenant.org
74 | [version]: http://contributor-covenant.org/version/1/4/
75 | 


--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
 1 | Unmaintained
 2 | ===
 3 | 
 4 | I do not have time or inclination to work on Elessar. Do not expect me to respond to your issues or pull requests.
 5 | 
 6 | Elessar
 7 | =======
 8 | [![Build Status](https://travis-ci.org/quarterto/Elessar.svg?branch=master)](https://travis-ci.org/quarterto/Elessar)
 9 | [![npm](https://img.shields.io/npm/v/elessar.svg)](https://www.npmjs.com/package/elessar)
10 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/quarterto/Elessar)
11 | [![PayPal](https://img.shields.io/badge/paypal-buy%20me%20a%20beer-blue.svg)](https://paypal.me/quarterto)
12 | 
13 | ![Browser Matrix](https://saucelabs.com/browser-matrix/quarterto.svg)
14 | 
15 | Draggable multiple range sliders
16 | ![elessar draggable range demo](demo.gif)
17 | 
18 | Installation
19 | ------------
20 | Elessar is available via npm and Bower, and as [standalone files](/dist):
21 | 
22 | `npm install elessar` | `bower install elessar`
23 | ----------------------|------------------------
24 | 
25 | Elessar requires [jQuery](http://jquery.com). If you're using npm or Bower, it's installed as part of this step. If not: a) why not? they're pretty sweet, b) download it, and I assume you're just using `
40 | ```
41 | 
42 | Create a rangebar with `var rangeBar = new RangeBar` then add `rangeBar.$el` to the DOM somewhere.
43 | 
44 | Options
45 | -------
46 | ```javascript
47 | new RangeBar({
48 |   values: [], // array of value pairs; each pair is the min and max of the range it creates
49 |   readonly: false, // whether this bar is read-only
50 |   min: 0, // value at start of bar
51 |   max: 100, // value at end of bar
52 |   valueFormat: function(a) {return a;}, // formats a value on the bar for output
53 |   valueParse: function(a) {return a;}, // parses an output value for the bar
54 |   snap: 0, // clamps range ends to multiples of this value (in bar units)
55 |   minSize: 0, // smallest allowed range (in bar units)
56 |   maxRanges: Infinity, // maximum number of ranges allowed on the bar
57 |   bgMarks: {
58 |     count: 0, // number of value labels to write in the background of the bar
59 |     interval: Infinity, // provide instead of count to specify the space between labels
60 |     label: id // string or function to write as the text of a label. functions are called with normalised values.
61 |   },
62 |   indicator: null, // pass a function(RangeBar, Indicator, Function?) Value to calculate where to put a current indicator, calling the function whenever you want the position to be recalculated
63 |   allowDelete: false, // set to true to enable double-middle-click-to-delete
64 |   deleteTimeout: 5000, // maximum time in ms between middle clicks
65 |   vertical: false, // if true the rangebar is aligned vertically, and given the class elessar-vertical
66 |   bounds: null, // a function that provides an upper or lower bound when a range is being dragged. call with the range that is being moved, should return an object with an upper or lower key
67 |   htmlLabel: false, // if true, range labels are written as html
68 |   allowSwap: true // swap ranges when dragging past
69 | });
70 | ```
71 | 
72 | API
73 | ---
74 | ### ``.val()``
75 | Returns array of pairs of min and max values of each range.
76 | 
77 | ```javascript
78 | bar.val(); //=> [[0,20], [34,86]]
79 | ```
80 | 
81 | ### ``.val(values)``
82 | Updates the ranges in the bar with the values. Returns the bar, for chaining.
83 | ```javascript
84 | bar.val([[0,30], [40,68]]); //=> bar: RangeBar
85 | ```
86 | 
87 | ### ``.on('changing' function(values, range))``
88 | Event that triggers constantly as the value changes. Useful for reactively triggering things in your UI. Callback is passed the current values of the ranges and the range element that is changing.
89 | 
90 | ### ``.on('change' function(values, range))``
91 | Event that triggers after the user has finished changing a range. Useful for updating a Backbone model. Callback is passed the current values of the ranges and the range element that has changed.
92 | 
93 | Licence
94 | -------
95 | [MIT](licence.md)
96 | 


--------------------------------------------------------------------------------
/test/rangebar.js:
--------------------------------------------------------------------------------
  1 | var tape = require('tape');
  2 | var $ = require('jquery');
  3 | var RangeBar = require('../lib/rangebar.js');
  4 | var Indicator = require('../lib/indicator.js');
  5 | var raf = require('../lib/raf.js');
  6 | require('./utils.js');
  7 | 
  8 | tape.test('RangeBar', function(t) {
  9 | 	var r = new RangeBar();
 10 | 	t.ok(r.$el, 'has an element');
 11 | 	t.ok(r.$el.hasClass('elessar-rangebar'), 'has the rangebar class');
 12 | 
 13 | 	t.test('options', function(t) {
 14 | 		t.test('sets default options', function(t) {
 15 | 			var r = new RangeBar();
 16 | 			t.equal(r.options.min, 0, 'max');
 17 | 			t.equal(r.options.max, 100, 'min');
 18 | 			t.equal(r.options.maxRanges, Infinity, 'maxRanges');
 19 | 			t.equal(r.options.readonly, false, 'readonly');
 20 | 			t.equal(r.options.bgLabels, 0, 'bgLabels');
 21 | 			t.equal(r.options.deleteTimeout, 5000, 'deleteTimeout');
 22 | 			t.equal(r.options.allowDelete, false, 'allowDelete');
 23 | 			t.end();
 24 | 		});
 25 | 
 26 | 		t.test('parses max and min', function(t) {
 27 | 			var r = new RangeBar({
 28 | 				min: 'value: 10',
 29 | 				max: 'value: 20',
 30 | 				valueFormat: function(v) {
 31 | 					return 'value: ' + v;
 32 | 				},
 33 | 				valueParse: function(v) {
 34 | 					return +(v.substr(7));
 35 | 				}
 36 | 			});
 37 | 
 38 | 			t.equal(r.options.min, 10);
 39 | 			t.equal(r.options.max, 20);
 40 | 
 41 | 			t.end();
 42 | 		});
 43 | 
 44 | 		t.test('barClass', function(t) {
 45 | 			var r = new RangeBar({barClass: 'test-class'});
 46 | 			t.ok(r.$el.hasClass('test-class'), 'adds options.barClass to the element');
 47 | 			t.end();
 48 | 		});
 49 | 
 50 | 		t.test('values', function(t) {
 51 | 			var r = new RangeBar({values: [[10, 20], [30, 40]]});
 52 | 			t.deepEqual(
 53 | 				r.val(),
 54 | 				[[10, 20], [30, 40]],
 55 | 				'sets the initial value'
 56 | 			);
 57 | 			t.end();
 58 | 		});
 59 | 
 60 | 		t.test('indicator', function(t) {
 61 | 			var called = false;
 62 | 			var r = new RangeBar({
 63 | 				indicator: function(rangeBar, indicator, refresh) {
 64 | 					t.ok(rangeBar instanceof RangeBar, 'gets passed the rangebar');
 65 | 					t.ok(indicator instanceof Indicator, 'gets passed the indicator');
 66 | 					if(called) {
 67 | 						t.equal(refresh, undefined, 'no function the second time');
 68 | 						process.nextTick(function() {
 69 | 							called = true;
 70 | 							t.equal(
 71 | 								indicator.val(),
 72 | 								rangeBar.abnormalise(20),
 73 | 								'return value updates the value'
 74 | 							);
 75 | 
 76 | 							t.end();
 77 | 						});
 78 | 					} else {
 79 | 						t.ok(refresh instanceof Function, 'gets passed a function the first time');
 80 | 						process.nextTick(function() {
 81 | 							called = true;
 82 | 							t.equal(
 83 | 								indicator.val(),
 84 | 								rangeBar.abnormalise(10),
 85 | 								'return value sets the initial value'
 86 | 							);
 87 | 							refresh();
 88 | 						});
 89 | 					}
 90 | 
 91 | 					return called ? 20 : 10;
 92 | 				}
 93 | 			});
 94 | 
 95 | 			t.ok(r.indicator instanceof Indicator, 'adds an indicator');
 96 | 		});
 97 | 
 98 | 		t.end();
 99 | 	});
100 | 
101 | 	t.test('normalise and abnormalise', function(t) {
102 | 		var r = new RangeBar({
103 | 			min: 'value: 10',
104 | 			max: 'value: 20',
105 | 			valueFormat: function(v) {
106 | 				return 'value: ' + v;
107 | 			},
108 | 			valueParse: function(v) {
109 | 				return +(v.substr(7));
110 | 			}
111 | 		});
112 | 
113 | 		t.equal(
114 | 			r.normaliseRaw(0.1),
115 | 			11,
116 | 			'normaliseRaw maps values [0,1] to [min,max]'
117 | 		);
118 | 
119 | 		t.equal(
120 | 			r.abnormaliseRaw(11),
121 | 			0.1,
122 | 			'abnormaliseRaw maps values [min,max] to [0,1]'
123 | 		);
124 | 
125 | 		t.equal(
126 | 			r.normalise(0.1),
127 | 			'value: 11',
128 | 			'normalise maps and formats values'
129 | 		);
130 | 
131 | 		t.equal(
132 | 			r.abnormalise('value: 11'),
133 | 			0.1,
134 | 			'abnormalise parses and maps values'
135 | 		);
136 | 
137 | 		t.end();
138 | 	});
139 | 
140 | 	t.test('findGap', function(t) {
141 | 		var r = new RangeBar();
142 | 		r.ranges = [
143 | 			{val: function() { return [0.1, 0.2] }},
144 | 			{val: function() { return [0.4, 0.5] }},
145 | 			{val: function() { return [0.7, 0.8] }}
146 | 		];
147 | 
148 | 		t.equal(r.findGap([0, 0.05]), 0);
149 | 		t.equal(r.findGap([0.25, 0.3]), 1);
150 | 		t.equal(r.findGap([0.55, 0.6]), 2);
151 | 		t.equal(r.findGap([0.9, 1]), 3);
152 | 		t.end();
153 | 	});
154 | 
155 | 	t.test('insertRangeIndex', function(t) {
156 | 		var r = new RangeBar();
157 | 		function range() {return {$el: $('
')}} 158 | 159 | t.test('inserts ranges', function(t) { 160 | var r1 = range(); 161 | r.insertRangeIndex(r1, 0); 162 | t.ok(r.$el.contains(r1.$el), 'at start when empty (dom)'); 163 | t.deepEqual(r.ranges, [r1], 'at start when empty (array)'); 164 | 165 | var r2 = range(); 166 | r.insertRangeIndex(r2, 1); 167 | t.ok(r2.$el.isAfter(r1.$el), 'after existing element if exists (dom)'); 168 | t.deepEqual(r.ranges, [r1, r2], 'after existing element if exists (array)'); 169 | 170 | var r3 = range(); 171 | r.insertRangeIndex(r3, 1); 172 | t.ok(r3.$el.isAfter(r1.$el) && r3.$el.isBefore(r2.$el), 'between existing elements (dom)'); 173 | t.deepEqual(r.ranges, [r1, r3, r2], 'between existing elements (array)'); 174 | 175 | var r4 = range(); 176 | r.insertRangeIndex(r4, 0); 177 | t.ok(r4.$el.isBefore(r1.$el), 'before everything when index 0 (dom)'); 178 | t.deepEqual(r.ranges, [r4, r1, r3, r2], 'before everything when index 0 (array)'); 179 | 180 | // clean up 181 | r.ranges = []; 182 | r.$el.empty(); 183 | t.end(); 184 | }); 185 | 186 | var r1 = range(); 187 | r.insertRangeIndex(r1, 0, true); 188 | t.ok(r.$el.contains(r1.$el), 'inserts to dom when avoidList is true'); 189 | t.equal(r.ranges.length, 0, 'but not to array'); 190 | 191 | t.end(); 192 | }); 193 | 194 | t.test('minSize', function(t) { 195 | t.test('is ignored for val', function(t) { 196 | var r = new RangeBar({minSize: 10}); 197 | r.val([[0,5]]); 198 | t.rangebarValuesEqual(r.val(), [[0,5]]); 199 | t.end(); 200 | }); 201 | 202 | t.test('is ignored setting initial value', function(t) { 203 | var r = new RangeBar({minSize: 10, values: [[0,5]]}); 204 | t.rangebarValuesEqual(r.val(), [[0,5]]); 205 | t.end(); 206 | }); 207 | 208 | t.test('is ignored adding range manually', function(t) { 209 | var r = new RangeBar({minSize: 10}); 210 | r.addRange([0,0.05]); 211 | t.rangebarValuesEqual(r.val(), [[0,5]]); 212 | t.end(); 213 | }); 214 | 215 | t.end(); 216 | }); 217 | 218 | t.end(); 219 | }); 220 | -------------------------------------------------------------------------------- /lib/rangebar.js: -------------------------------------------------------------------------------- 1 | var Element = require('./element'); 2 | var Range = require('./range'); 3 | var Phantom = require('./phantom'); 4 | var Indicator = require('./indicator'); 5 | var getEventProperty = require('./eventprop'); 6 | var vertical = require('./vertical'); 7 | var $ = require('jquery'); 8 | var Mark = require('./mark.js'); 9 | 10 | var RangeBar = Element.extend(vertical).extend({ 11 | initialize: function initialize(options) { 12 | options = options || {}; 13 | initialize.super$.call(this, '
'); 14 | this.options = $.extend({}, RangeBar.defaults, options); 15 | this.options.min = this.options.valueParse(this.options.min); 16 | this.options.max = this.options.valueParse(this.options.max); 17 | if(this.options.barClass) this.$el.addClass(this.options.barClass); 18 | if(this.options.vertical) this.$el.addClass('elessar-vertical'); 19 | 20 | this.ranges = []; 21 | this.on('mousemove.elessar touchmove.elessar', $.proxy(this.mousemove, this)); 22 | this.on('mouseleave.elessar touchleave.elessar', $.proxy(this.removePhantom, this)); 23 | 24 | if(options.values) this.setVal(options.values); 25 | 26 | if(options.bgLabels) { 27 | options.bgMark = { count: options.bgLabels }; 28 | } 29 | 30 | if(options.bgMark) { 31 | this.$markContainer = $('
').appendTo(this.$el); 32 | if(options.bgMark.count) { 33 | for(var i = 0; i < options.bgMark.count; ++i) { 34 | this.$markContainer.append((new Mark({ 35 | label: options.bgMark.label, 36 | value: i / options.bgMark.count, 37 | perant: this 38 | })).$el); 39 | } 40 | } else if(options.bgMark.interval) { 41 | for(var i = this.abnormalise(this.options.min); i < this.abnormalise(this.options.max); i += this.abnormalise(options.bgMark.interval)) { 42 | this.$markContainer.append((new Mark({ 43 | label: options.bgMark.label, 44 | value: i, 45 | perant: this 46 | })).$el); 47 | } 48 | } 49 | } 50 | 51 | var self = this; 52 | 53 | if(options.indicator) { 54 | var indicator = this.indicator = new Indicator({ 55 | perant: this, 56 | vertical: this.options.vertical, 57 | indicatorClass: options.indicatorClass 58 | }); 59 | indicator.val(this.abnormalise(options.indicator(this, indicator, function() { 60 | indicator.val(self.abnormalise(options.indicator(self, indicator))); 61 | }))); 62 | this.$el.append(indicator.$el); 63 | } 64 | }, 65 | 66 | normaliseRaw: function (value) { 67 | return this.options.min + value * (this.options.max - this.options.min); 68 | }, 69 | 70 | normalise: function (value) { 71 | return this.options.valueFormat(this.normaliseRaw(value)); 72 | }, 73 | 74 | abnormaliseRaw: function (value) { 75 | return (value - this.options.min)/(this.options.max - this.options.min); 76 | }, 77 | 78 | abnormalise: function (value) { 79 | return this.abnormaliseRaw(this.options.valueParse(value)); 80 | }, 81 | 82 | findGap: function(range) { 83 | var newIndex = 0; 84 | this.ranges.forEach(function($r, i) { 85 | if($r.val()[0] < range[0] && $r.val()[1] < range[1]) newIndex = i + 1; 86 | }); 87 | 88 | return newIndex; 89 | }, 90 | 91 | insertRangeIndex: function(range, index, avoidList) { 92 | if(!avoidList) this.ranges.splice(index, 0, range); 93 | 94 | if(this.ranges[index - 1]) { 95 | this.ranges[index - 1].$el.after(range.$el); 96 | } else { 97 | this.$el.prepend(range.$el); 98 | } 99 | }, 100 | 101 | addRange: function(range, data) { 102 | var $range = Range({ 103 | perant: this, 104 | snap: this.options.snap ? this.abnormaliseRaw(this.options.snap + this.options.min) : null, 105 | label: this.options.label, 106 | rangeClass: this.options.rangeClass, 107 | minSize: this.options.minSize ? this.abnormaliseRaw(this.options.minSize + this.options.min) : null, 108 | readonly: this.options.readonly, 109 | htmlLabel: this.options.htmlLabel 110 | }); 111 | 112 | if (this.options.data) { 113 | $range.data(this.options.data.call($range, this)); 114 | } 115 | 116 | if (data) { 117 | $range.data(data); 118 | } 119 | 120 | this.insertRangeIndex($range, this.findGap(range)); 121 | $range.val(range); 122 | 123 | var self = this; 124 | 125 | $range.on('changing', function(ev, nrange, changed) { 126 | ev.stopPropagation(); 127 | self.trigger('changing', [self.val(), changed]); 128 | }).on('change', function(ev, nrange, changed) { 129 | ev.stopPropagation(); 130 | self.trigger('change', [self.val(), changed]); 131 | }); 132 | return $range; 133 | }, 134 | 135 | prevRange: function(range) { 136 | var idx = range.index(); 137 | if(idx >= 0) return this.ranges[idx - 1]; 138 | }, 139 | 140 | nextRange: function(range) { 141 | var idx = range.index(); 142 | if(idx >= 0) return this.ranges[range instanceof Phantom ? idx : idx + 1]; 143 | }, 144 | 145 | setVal: function(ranges) { 146 | if (this.ranges.length > ranges.length) { 147 | for (var i = ranges.length-1, l = this.ranges.length-1; i < l; --l) { 148 | this.removeRange(l); 149 | } 150 | this.ranges.length = ranges.length; 151 | } 152 | 153 | var self = this; 154 | 155 | ranges.forEach(function(range, i) { 156 | if(self.ranges[i]) { 157 | self.ranges[i].val(range.map($.proxy(self.abnormalise, self))); 158 | } else { 159 | self.addRange(range.map($.proxy(self.abnormalise, self))); 160 | } 161 | }); 162 | 163 | return this; 164 | }, 165 | 166 | val: function(ranges) { 167 | var self = this; 168 | if(typeof ranges === 'undefined') { 169 | return this.ranges.map(function(range) { 170 | return range.val().map($.proxy(self.normalise, self)); 171 | }); 172 | } 173 | 174 | if(!this.readonly()) this.setVal(ranges); 175 | return this; 176 | }, 177 | 178 | removePhantom: function() { 179 | if(this.phantom) { 180 | this.phantom.remove(); 181 | this.phantom = null; 182 | } 183 | }, 184 | 185 | removeRange: function(i, noTrigger, preserveEvents) { 186 | if(i instanceof Range) { 187 | i = this.ranges.indexOf(i); 188 | } 189 | this.ranges.splice(i,1)[0][preserveEvents ? 'detach' : 'remove'](); 190 | if(!noTrigger) { 191 | this.trigger('change', [this.val()]); 192 | } 193 | }, 194 | 195 | repositionRange: function(range, val) { 196 | this.removeRange(range, true, true); 197 | this.insertRangeIndex(range, this.findGap(val)); 198 | }, 199 | 200 | calcGap: function(index) { 201 | var start = this.ranges[index - 1] ? this.ranges[index - 1].val()[1] : 0; 202 | var end = this.ranges[index] ? this.ranges[index].val()[0] : 1; 203 | return this.normaliseRaw(end) - this.normaliseRaw(start); 204 | }, 205 | 206 | readonly: function() { 207 | if(typeof this.options.readonly === 'function') { 208 | return this.options.readonly.call(this); 209 | } 210 | return this.options.readonly; 211 | }, 212 | 213 | mousemove: function(ev) { 214 | var w = this.options.minSize ? this.abnormaliseRaw(this.options.minSize + this.options.min) : 0.05; 215 | var pageStart = getEventProperty(this.ifVertical('pageY','pageX'), ev); 216 | var val = (pageStart - this.startProp('offset'))/this.totalSize() - w/2; 217 | 218 | var direct = ev.target === ev.currentTarget; 219 | var phantom = $(ev.target).is('.elessar-phantom'); 220 | 221 | if((direct || phantom) && this.ranges.length < this.options.maxRanges && !$('body').is('.elessar-dragging, .elessar-resizing') && !this.readonly()) { 222 | if(!this.phantom) this.phantom = Phantom({ 223 | perant: this, 224 | snap: this.options.snap ? this.abnormaliseRaw(this.options.snap + this.options.min) : null, 225 | label: "+", 226 | minSize: this.options.minSize ? this.abnormaliseRaw(this.options.minSize + this.options.min) : null, 227 | rangeClass: this.options.rangeClass 228 | }); 229 | var idx = this.findGap([val,val + w]); 230 | var self = this; 231 | this.one('addrange', function(ev, val, range) { 232 | range.one('mouseup', function() { 233 | self.trigger('change', [self.val(), range]); 234 | }); 235 | }); 236 | 237 | if(!this.options.minSize || this.calcGap(idx) >= this.options.minSize) { 238 | this.insertRangeIndex(this.phantom, idx, true); 239 | this.phantom.val([val,val + w], {trigger: false}); 240 | } 241 | } 242 | } 243 | }); 244 | 245 | RangeBar.defaults = { 246 | min: 0, 247 | max: 100, 248 | valueFormat: function (a) {return a;}, 249 | valueParse: function (a) {return a;}, 250 | maxRanges: Infinity, 251 | readonly: false, 252 | bgLabels: 0, 253 | deleteTimeout: 5000, 254 | allowDelete: false, 255 | vertical: false, 256 | htmlLabel: false, 257 | allowSwap: true 258 | }; 259 | 260 | module.exports = RangeBar; 261 | -------------------------------------------------------------------------------- /lib/range.js: -------------------------------------------------------------------------------- 1 | var $ = require('jquery'); 2 | var Element = require('./element'); 3 | var getEventProperty = require('./eventprop'); 4 | var vertical = require('./vertical'); 5 | 6 | var Range = Element.extend(vertical).extend({ 7 | initialize: function initialize(options) { 8 | var self = this; 9 | initialize.super$.call(this, 10 | '
' 11 | ); 12 | this.options = options; 13 | this.perant = options.perant; 14 | 15 | if(this.options.rangeClass) this.$el.addClass(this.options.rangeClass); 16 | 17 | if(!this.readonly()) { 18 | this.$el.prepend('
').append('
'); 19 | this.on('mouseenter.elessar touchstart.elessar', $.proxy(this.removePhantom, this)); 20 | this.on('mousedown.elessar touchstart.elessar', $.proxy(this.mousedown, this)); 21 | this.on('click', $.proxy(this.click, this)); 22 | } else { 23 | this.$el.addClass('elessar-readonly'); 24 | } 25 | if(typeof this.options.label === 'function') { 26 | this.on('changing', function(ev, range) { 27 | self.writeLabel( 28 | self.options.label.call(self, range.map($.proxy(self.perant.normalise, self.perant))) 29 | ); 30 | }); 31 | } else { 32 | this.writeLabel(this.options.label); 33 | } 34 | 35 | this.range = []; 36 | this.hasChanged = false; 37 | 38 | if(this.options.value) this.val(this.options.value); 39 | 40 | }, 41 | 42 | writeLabel: function(text) { 43 | this.$el.find('.elessar-barlabel')[this.options.htmlLabel ? 'html' : 'text'](text); 44 | }, 45 | 46 | isVertical: function() { 47 | return this.perant.options.vertical; 48 | }, 49 | 50 | removePhantom: function() { 51 | this.perant.removePhantom(); 52 | }, 53 | 54 | readonly: function() { 55 | if(typeof this.options.readonly === 'function') { 56 | return this.options.readonly.call(this.perant, this); 57 | } 58 | return this.options.readonly; 59 | }, 60 | 61 | val: function(range, valOpts) { 62 | 63 | if(typeof range === 'undefined') { 64 | return this.range; 65 | } 66 | 67 | valOpts = $.extend({},{ 68 | dontApplyDelta: false, 69 | trigger: true 70 | }, valOpts || {}); 71 | 72 | var next = this.perant.nextRange(this.$el), 73 | prev = this.perant.prevRange(this.$el), 74 | delta = range[1] - range[0], 75 | self = this; 76 | 77 | if(this.options.snap) { 78 | range = range.map(snap); 79 | delta = snap(delta); 80 | } 81 | if (next && next.val()[0] <= range[1] && prev && prev.val()[1] >= range[0]) { 82 | range[1] = next.val()[0]; 83 | range[0] = prev.val()[1]; 84 | } 85 | if (next && next.val()[0] < range[1]) { 86 | if(!this.perant.options.allowSwap || next.val()[1] >= range[0]) { 87 | range[1] = next.val()[0]; 88 | if(!valOpts.dontApplyDelta) range[0] = range[1] - delta; 89 | } else { 90 | this.perant.repositionRange(this, range); 91 | } 92 | } 93 | if (prev && prev.val()[1] > range[0]) { 94 | if(!this.perant.options.allowSwap || prev.val()[0] <= range[1]) { 95 | range[0] = prev.val()[1]; 96 | if(!valOpts.dontApplyDelta) range[1] = range[0] + delta; 97 | } else { 98 | this.perant.repositionRange(this, range); 99 | } 100 | } 101 | if (range[1] >= 1) { 102 | range[1] = 1; 103 | if(!valOpts.dontApplyDelta) range[0] = 1 - delta; 104 | } 105 | if (range[0] <= 0) { 106 | range[0] = 0; 107 | if(!valOpts.dontApplyDelta) range[1] = delta; 108 | } 109 | if(this.perant.options.bound) { 110 | var bound = this.perant.options.bound(this); 111 | if(bound) { 112 | if(bound.upper && range[1] > this.perant.abnormalise(bound.upper)) { 113 | range[1] = this.perant.abnormalise(bound.upper); 114 | if(!valOpts.dontApplyDelta) range[0] = range[1] - delta; 115 | } 116 | if(bound.lower && range[0] < this.perant.abnormalise(bound.lower)) { 117 | range[0] = this.perant.abnormalise(bound.lower); 118 | if(!valOpts.dontApplyDelta) range[1] = range[0] + delta; 119 | } 120 | } 121 | } 122 | 123 | if(this.range[0] === range[0] && this.range[1] === range[1]) return this.$el; 124 | 125 | this.range = range; 126 | 127 | if(valOpts.trigger) { 128 | this.$el.triggerHandler('changing', [range, this.$el]); 129 | this.hasChanged = true; 130 | } 131 | 132 | var start = 100*range[0] + '%', 133 | size = 100*(range[1] - range[0]) + '%'; 134 | 135 | this.draw( 136 | this.perant.options.vertical ? 137 | {top: start, minHeight: size} : 138 | {left: start, minWidth: size} 139 | ); 140 | 141 | return this; 142 | 143 | function snap(val) { return Math.round(val / self.options.snap) * self.options.snap; } 144 | function sign(x) { return x ? x < 0 ? -1 : 1 : 0; } 145 | }, 146 | 147 | click: function(ev) { 148 | ev.stopPropagation(); 149 | ev.preventDefault(); 150 | 151 | var self = this; 152 | 153 | if(ev.which !== 2 || !this.perant.options.allowDelete) return; 154 | 155 | if(this.deleteConfirm) { 156 | this.perant.removeRange(this); 157 | clearTimeout(this.deleteTimeout); 158 | } else { 159 | this.$el.addClass('elessar-delete-confirm'); 160 | this.deleteConfirm = true; 161 | 162 | this.deleteTimeout = setTimeout(function() { 163 | self.$el.removeClass('elessar-delete-confirm'); 164 | self.deleteConfirm = false; 165 | }, this.perant.options.deleteTimeout); 166 | } 167 | }, 168 | 169 | mousedown: function(ev) { 170 | ev.stopPropagation(); 171 | ev.preventDefault(); 172 | this.hasChanged = false; 173 | if(ev.which > 1) return; 174 | 175 | if ($(ev.target).is('.elessar-handle:first-child')) { 176 | $('body').addClass('elessar-resizing').toggleClass('elessar-resizing-vertical', this.isVertical()); 177 | $(document).on('mousemove.elessar touchmove.elessar', this.resizeStart(ev)); 178 | } else if ($(ev.target).is('.elessar-handle:last-child')) { 179 | $('body').addClass('elessar-resizing').toggleClass('elessar-resizing-vertical', this.isVertical()); 180 | $(document).on('mousemove.elessar touchmove.elessar', this.resizeEnd(ev)); 181 | } else { 182 | $('body').addClass('elessar-dragging').toggleClass('elessar-dragging-vertical', this.isVertical()); 183 | $(document).on('mousemove.elessar touchmove.elessar', this.drag(ev)); 184 | } 185 | 186 | var self = this; 187 | 188 | $(document).one('mouseup.elessar touchend.elessar', function(ev) { 189 | ev.stopPropagation(); 190 | ev.preventDefault(); 191 | 192 | if(self.hasChanged && !self.swapping) self.trigger('change', [self.range, self.$el]); 193 | self.swapping = false; 194 | $(document).off('mouseup.elessar mousemove.elessar touchend.elessar touchmove.elessar'); 195 | $('body').removeClass('elessar-resizing elessar-dragging elessar-resizing-vertical elessar-dragging-vertical'); 196 | }); 197 | }, 198 | 199 | drag: function(origEv) { 200 | var self = this, 201 | beginStart = this.startProp('offset'), 202 | beginPosStart = this.startProp('position'), 203 | mousePos = getEventProperty(this.ifVertical('clientY','clientX'), origEv), 204 | mouseOffset = mousePos ? mousePos - beginStart : 0, 205 | beginSize = this.totalSize(), 206 | perant = this.options.perant, 207 | perantStart = perant.startProp('offset'), 208 | perantSize = perant.totalSize(); 209 | 210 | return function(ev) { 211 | ev.stopPropagation(); 212 | ev.preventDefault(); 213 | var mousePos = getEventProperty(self.ifVertical('clientY','clientX'), ev); 214 | if(typeof mousePos !== 'undefined') { 215 | var start = mousePos - perantStart - mouseOffset; 216 | 217 | if (start >= 0 && start <= perantSize - beginSize) { 218 | var rangeOffset = start / perantSize - self.range[0]; 219 | self.val([start / perantSize, self.range[1] + rangeOffset]); 220 | } else { 221 | mouseOffset = mousePos - self.startProp('offset'); 222 | } 223 | } 224 | }; 225 | }, 226 | resizeEnd: function(origEv) { 227 | var self = this, 228 | beginStart = this.startProp('offset'), 229 | beginPosStart = this.startProp('position'), 230 | mousePos = getEventProperty(this.ifVertical('clientY','clientX'), origEv), 231 | mouseOffset = mousePos ? mousePos - beginStart : 0, 232 | beginSize = this.totalSize(), 233 | perant = this.options.perant, 234 | perantStart = perant.startProp('offset'), 235 | perantSize = perant.totalSize(), 236 | minSize = this.options.minSize * perantSize; 237 | 238 | return function(ev) { 239 | var opposite = ev.type === 'touchmove' ? 'touchend' : 'mouseup', 240 | subsequent = ev.type === 'touchmove' ? 'touchstart' : 'mousedown'; 241 | ev.stopPropagation(); 242 | ev.preventDefault(); 243 | var mousePos = getEventProperty(self.ifVertical('clientY','clientX'), ev); 244 | var size = mousePos - beginStart; 245 | 246 | if(typeof mousePos !== 'undefined') { 247 | if (size > perantSize - beginPosStart) size = perantSize - beginPosStart; 248 | if (size >= minSize) { 249 | self.val([self.range[0], self.range[0] + size / perantSize], {dontApplyDelta: true}); 250 | } else if(size <= 10) { 251 | self.swapping = true; 252 | $(document).trigger(opposite + '.elessar'); 253 | self.$el.find('.elessar-handle:first-child').trigger(subsequent + '.elessar'); 254 | } 255 | } 256 | }; 257 | }, 258 | 259 | resizeStart: function(origEv) { 260 | var self = this, 261 | beginStart = this.startProp('offset'), 262 | beginPosStart = this.startProp('position'), 263 | mousePos = getEventProperty(this.ifVertical('clientY','clientX'), origEv), 264 | mouseOffset = mousePos ? mousePos - beginStart : 0, 265 | beginSize = this.totalSize(), 266 | perant = this.options.perant, 267 | perantStart = perant.startProp('offset'), 268 | perantSize = perant.totalSize(), 269 | minSize = this.options.minSize * perantSize; 270 | 271 | return function(ev) { 272 | var opposite = ev.type === 'touchmove' ? 'touchend' : 'mouseup', 273 | subsequent = ev.type === 'touchmove' ? 'touchstart' : 'mousedown'; 274 | 275 | ev.stopPropagation(); 276 | ev.preventDefault(); 277 | var mousePos = getEventProperty(self.ifVertical('clientY','clientX'), ev); 278 | var start = mousePos - perantStart - mouseOffset; 279 | var size = beginPosStart + beginSize - start; 280 | 281 | if(typeof mousePos !== 'undefined') { 282 | if (start < 0) { 283 | start = 0; 284 | size = beginPosStart + beginSize; 285 | } 286 | if (size >= minSize) { 287 | self.val([start / perantSize, self.range[1]], {dontApplyDelta: true}); 288 | } else if(size <= 10) { 289 | self.swapping = true; 290 | $(document).trigger(opposite + '.elessar'); 291 | self.$el.find('.elessar-handle:last-child').trigger(subsequent + '.elessar'); 292 | } 293 | } 294 | }; 295 | } 296 | }); 297 | 298 | module.exports = Range; 299 | 300 | -------------------------------------------------------------------------------- /dist/elessar.min.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.RangeBar=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o');if(options.indicatorClass)this.$el.addClass(options.indicatorClass);if(options.value)this.val(options.value);this.options=options},val:function(pos){if(pos){if(this.isVertical()){this.draw({top:100*pos+"%"})}else{this.draw({left:100*pos+"%"})}this.value=pos}return this.value}});module.exports=Indicator},{"./element":1,"./vertical":9}],4:[function(require,module,exports){var Element=require("./element.js");var Mark=Element.extend({initialize:function initialize(options){initialize.super$.call(this,'
');this.$el.css(options.perant.edge("start"),options.value*100+"%");if(typeof options.label==="function"){this.$el.text(options.label.call(this,options.perant.normalise(options.value)))}else if(typeof options.label==="string"){this.$el.text(options.label)}else{this.$el.text(options.perant.normalise(options.value))}}});module.exports=Mark},{"./element.js":1}],5:[function(require,module,exports){var $=window.$;var Range=require("./range");var requestAnimationFrame=require("./raf");var Phantom=Range.extend({initialize:function initialize(options){initialize.super$.call(this,$.extend({readonly:true,label:"+"},options));this.$el.addClass("elessar-phantom");this.on("mousedown.elessar touchstart.elessar",$.proxy(this.mousedown,this))},mousedown:function(ev){if(ev.which===1){var startX=ev.pageX;var newRange=this.options.perant.addRange(this.val());this.remove();this.options.perant.trigger("addrange",[newRange.val(),newRange]);requestAnimationFrame(function(){newRange.$el.find(".elessar-handle:first-child").trigger(ev.type)})}},removePhantom:function(){}});module.exports=Phantom},{"./raf":6,"./range":7}],6:[function(require,module,exports){var lastTime=0;var vendors=["webkit","moz"],requestAnimationFrame=window.requestAnimationFrame;for(var x=0;x');this.options=options;this.perant=options.perant;if(this.options.rangeClass)this.$el.addClass(this.options.rangeClass);if(!this.readonly()){this.$el.prepend('
').append('
');this.on("mouseenter.elessar touchstart.elessar",$.proxy(this.removePhantom,this));this.on("mousedown.elessar touchstart.elessar",$.proxy(this.mousedown,this));this.on("click",$.proxy(this.click,this))}else{this.$el.addClass("elessar-readonly")}if(typeof this.options.label==="function"){this.on("changing",function(ev,range){self.writeLabel(self.options.label.call(self,range.map($.proxy(self.perant.normalise,self.perant))))})}else{this.writeLabel(this.options.label)}this.range=[];this.hasChanged=false;if(this.options.value)this.val(this.options.value)},writeLabel:function(text){this.$el.find(".elessar-barlabel")[this.options.htmlLabel?"html":"text"](text)},isVertical:function(){return this.perant.options.vertical},removePhantom:function(){this.perant.removePhantom()},readonly:function(){if(typeof this.options.readonly==="function"){return this.options.readonly.call(this.perant,this)}return this.options.readonly},val:function(range,valOpts){if(typeof range==="undefined"){return this.range}valOpts=$.extend({},{dontApplyDelta:false,trigger:true},valOpts||{});var next=this.perant.nextRange(this.$el),prev=this.perant.prevRange(this.$el),delta=range[1]-range[0],self=this;if(this.options.snap){range=range.map(snap);delta=snap(delta)}if(next&&next.val()[0]<=range[1]&&prev&&prev.val()[1]>=range[0]){range[1]=next.val()[0];range[0]=prev.val()[1]}if(next&&next.val()[0]=range[0]){range[1]=next.val()[0];if(!valOpts.dontApplyDelta)range[0]=range[1]-delta}else{this.perant.repositionRange(this,range)}}if(prev&&prev.val()[1]>range[0]){if(!this.perant.options.allowSwap||prev.val()[0]<=range[1]){range[0]=prev.val()[1];if(!valOpts.dontApplyDelta)range[1]=range[0]+delta}else{this.perant.repositionRange(this,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(this.perant.options.bound){var bound=this.perant.options.bound(this);if(bound){if(bound.upper&&range[1]>this.perant.abnormalise(bound.upper)){range[1]=this.perant.abnormalise(bound.upper);if(!valOpts.dontApplyDelta)range[0]=range[1]-delta}if(bound.lower&&range[0]1)return;if($(ev.target).is(".elessar-handle:first-child")){$("body").addClass("elessar-resizing").toggleClass("elessar-resizing-vertical",this.isVertical());$(document).on("mousemove.elessar touchmove.elessar",this.resizeStart(ev))}else if($(ev.target).is(".elessar-handle:last-child")){$("body").addClass("elessar-resizing").toggleClass("elessar-resizing-vertical",this.isVertical());$(document).on("mousemove.elessar touchmove.elessar",this.resizeEnd(ev))}else{$("body").addClass("elessar-dragging").toggleClass("elessar-dragging-vertical",this.isVertical());$(document).on("mousemove.elessar touchmove.elessar",this.drag(ev))}var self=this;$(document).one("mouseup.elessar touchend.elessar",function(ev){ev.stopPropagation();ev.preventDefault();if(self.hasChanged&&!self.swapping)self.trigger("change",[self.range,self.$el]);self.swapping=false;$(document).off("mouseup.elessar mousemove.elessar touchend.elessar touchmove.elessar");$("body").removeClass("elessar-resizing elessar-dragging elessar-resizing-vertical elessar-dragging-vertical")})},drag:function(origEv){var self=this,beginStart=this.startProp("offset"),beginPosStart=this.startProp("position"),mousePos=getEventProperty(this.ifVertical("clientY","clientX"),origEv),mouseOffset=mousePos?mousePos-beginStart:0,beginSize=this.totalSize(),perant=this.options.perant,perantStart=perant.startProp("offset"),perantSize=perant.totalSize();return function(ev){ev.stopPropagation();ev.preventDefault();var mousePos=getEventProperty(self.ifVertical("clientY","clientX"),ev);if(typeof mousePos!=="undefined"){var start=mousePos-perantStart-mouseOffset;if(start>=0&&start<=perantSize-beginSize){var rangeOffset=start/perantSize-self.range[0];self.val([start/perantSize,self.range[1]+rangeOffset])}else{mouseOffset=mousePos-self.startProp("offset")}}}},resizeEnd:function(origEv){var self=this,beginStart=this.startProp("offset"),beginPosStart=this.startProp("position"),mousePos=getEventProperty(this.ifVertical("clientY","clientX"),origEv),mouseOffset=mousePos?mousePos-beginStart:0,beginSize=this.totalSize(),perant=this.options.perant,perantStart=perant.startProp("offset"),perantSize=perant.totalSize(),minSize=this.options.minSize*perantSize;return function(ev){var opposite=ev.type==="touchmove"?"touchend":"mouseup",subsequent=ev.type==="touchmove"?"touchstart":"mousedown";ev.stopPropagation();ev.preventDefault();var mousePos=getEventProperty(self.ifVertical("clientY","clientX"),ev);var size=mousePos-beginStart;if(typeof mousePos!=="undefined"){if(size>perantSize-beginPosStart)size=perantSize-beginPosStart;if(size>=minSize){self.val([self.range[0],self.range[0]+size/perantSize],{dontApplyDelta:true})}else if(size<=10){self.swapping=true;$(document).trigger(opposite+".elessar");self.$el.find(".elessar-handle:first-child").trigger(subsequent+".elessar")}}}},resizeStart:function(origEv){var self=this,beginStart=this.startProp("offset"),beginPosStart=this.startProp("position"),mousePos=getEventProperty(this.ifVertical("clientY","clientX"),origEv),mouseOffset=mousePos?mousePos-beginStart:0,beginSize=this.totalSize(),perant=this.options.perant,perantStart=perant.startProp("offset"),perantSize=perant.totalSize(),minSize=this.options.minSize*perantSize;return function(ev){var opposite=ev.type==="touchmove"?"touchend":"mouseup",subsequent=ev.type==="touchmove"?"touchstart":"mousedown";ev.stopPropagation();ev.preventDefault();var mousePos=getEventProperty(self.ifVertical("clientY","clientX"),ev);var start=mousePos-perantStart-mouseOffset;var size=beginPosStart+beginSize-start;if(typeof mousePos!=="undefined"){if(start<0){start=0;size=beginPosStart+beginSize}if(size>=minSize){self.val([start/perantSize,self.range[1]],{dontApplyDelta:true})}else if(size<=10){self.swapping=true;$(document).trigger(opposite+".elessar");self.$el.find(".elessar-handle:last-child").trigger(subsequent+".elessar")}}}}});module.exports=Range},{"./element":1,"./eventprop":2,"./vertical":9}],8:[function(require,module,exports){var Element=require("./element");var Range=require("./range");var Phantom=require("./phantom");var Indicator=require("./indicator");var getEventProperty=require("./eventprop");var vertical=require("./vertical");var $=window.$;var Mark=require("./mark.js");var RangeBar=Element.extend(vertical).extend({initialize:function initialize(options){options=options||{};initialize.super$.call(this,'
');this.options=$.extend({},RangeBar.defaults,options);this.options.min=this.options.valueParse(this.options.min);this.options.max=this.options.valueParse(this.options.max);if(this.options.barClass)this.$el.addClass(this.options.barClass);if(this.options.vertical)this.$el.addClass("elessar-vertical");this.ranges=[];this.on("mousemove.elessar touchmove.elessar",$.proxy(this.mousemove,this));this.on("mouseleave.elessar touchleave.elessar",$.proxy(this.removePhantom,this));if(options.values)this.setVal(options.values);if(options.bgLabels){options.bgMark={count:options.bgLabels}}if(options.bgMark){this.$markContainer=$('
').appendTo(this.$el);if(options.bgMark.count){for(var i=0;i=0)return this.ranges[idx-1]},nextRange:function(range){var idx=range.index();if(idx>=0)return this.ranges[range instanceof Phantom?idx:idx+1]},setVal:function(ranges){if(this.ranges.length>ranges.length){for(var i=ranges.length-1,l=this.ranges.length-1;i=this.options.minSize){this.insertRangeIndex(this.phantom,idx,true);this.phantom.val([val,val+w],{trigger:false})}}}});RangeBar.defaults={min:0,max:100,valueFormat:function(a){return a},valueParse:function(a){return a},maxRanges:Infinity,readonly:false,bgLabels:0,deleteTimeout:5e3,allowDelete:false,vertical:false,htmlLabel:false,allowSwap:true};module.exports=RangeBar},{"./element":1,"./eventprop":2,"./indicator":3,"./mark.js":4,"./phantom":5,"./range":7,"./vertical":9}],9:[function(require,module,exports){module.exports={isVertical:function(){return this.options.vertical},ifVertical:function(v,h){return this.isVertical()?v:h},edge:function(which){if(which==="start"){return this.ifVertical("top","left")}else if(which==="end"){return this.ifVertical("bottom","right")}throw new TypeError("What kind of an edge is "+which)},totalSize:function(){return this.$el[this.ifVertical("height","width")]()},edgeProp:function(edge,prop){var o=this.$el[prop]();return o[this.edge(edge)]},startProp:function(prop){return this.edgeProp("start",prop)},endProp:function(prop){return this.edgeProp("end",prop)}}},{}],10:[function(require,module,exports){(function(){(function(definition){switch(false){case!(typeof define==="function"&&define.amd!=null):return define([],definition);case typeof exports!=="object":return module.exports=definition();default:return this.Base=definition()}})(function(){var Base;return Base=function(){Base.displayName="Base";var attach,prototype=Base.prototype,constructor=Base;attach=function(obj,name,prop,super$,superclass$){return obj[name]=typeof prop==="function"?import$(function(){var this$=this;prop.superclass$=superclass$;prop.super$=function(){return super$.apply(this$,arguments)};return prop.apply(this,arguments)},prop):prop};Base.extend=function(displayName,proto){proto==null&&(proto=displayName);return function(superclass){var name,ref$,prop,prototype=extend$(import$(constructor,superclass),superclass).prototype;import$(constructor,Base);if(typeof displayName==="string"){constructor.displayName=displayName}function constructor(){var this$=this instanceof ctor$?this:new ctor$;this$.initialize.apply(this$,arguments);return this$}function ctor$(){}ctor$.prototype=prototype;prototype.initialize=function(){if(superclass.prototype.initialize!=null){return superclass.prototype.initialize.apply(this,arguments)}else{return superclass.apply(this,arguments)}};for(name in ref$=proto){prop=ref$[name];attach(prototype,name,prop,prototype[name],superclass)}return constructor}(this)};Base.meta=function(meta){var name,prop;for(name in meta){prop=meta[name];attach(this,name,prop,this[name],this)}return this};prototype.initialize=function(){};function Base(){}return Base}()});function import$(obj,src){var own={}.hasOwnProperty;for(var key in src)if(own.call(src,key))obj[key]=src[key];return obj}function extend$(sub,sup){function fun(){}fun.prototype=(sub.superclass=sup).prototype;(sub.prototype=new fun).constructor=sub;if(typeof sup.extended=="function")sup.extended(sub);return sub}}).call(this)},{}]},{},[8])(8)}); -------------------------------------------------------------------------------- /dist/elessar.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.RangeBar = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o'); 78 | if(options.indicatorClass) this.$el.addClass(options.indicatorClass); 79 | if(options.value) this.val(options.value); 80 | this.options = options; 81 | }, 82 | 83 | val: function(pos) { 84 | if(pos) { 85 | if(this.isVertical()) { 86 | this.draw({top: 100*pos + '%'}); 87 | } else { 88 | this.draw({left: 100*pos + '%'}); 89 | } 90 | this.value = pos; 91 | } 92 | return this.value; 93 | } 94 | }); 95 | 96 | module.exports = Indicator; 97 | 98 | },{"./element":1,"./vertical":9}],4:[function(require,module,exports){ 99 | var Element = require('./element.js'); 100 | 101 | var Mark = Element.extend({ 102 | initialize: function initialize(options) { 103 | initialize.super$.call(this, '
'); 104 | this.$el.css(options.perant.edge('start'), (options.value * 100) + '%'); 105 | 106 | if(typeof options.label === 'function') { 107 | this.$el.text(options.label.call(this, options.perant.normalise(options.value))); 108 | } else if(typeof options.label === 'string') { 109 | this.$el.text(options.label); 110 | } else { 111 | this.$el.text(options.perant.normalise(options.value)); 112 | } 113 | } 114 | }); 115 | 116 | module.exports = Mark; 117 | 118 | },{"./element.js":1}],5:[function(require,module,exports){ 119 | var $ = (window.$); 120 | var Range = require('./range'); 121 | var requestAnimationFrame = require('./raf'); 122 | 123 | var Phantom = Range.extend({ 124 | initialize: function initialize(options) { 125 | initialize.super$.call(this, $.extend({ 126 | readonly: true, 127 | label: '+' 128 | }, options)); 129 | this.$el.addClass('elessar-phantom'); 130 | this.on('mousedown.elessar touchstart.elessar', $.proxy(this.mousedown, this)); 131 | }, 132 | 133 | mousedown: function(ev) { 134 | if(ev.which === 1) { // left mouse button 135 | var startX = ev.pageX; 136 | var newRange = this.options.perant.addRange(this.val()); 137 | this.remove(); 138 | this.options.perant.trigger('addrange', [newRange.val(), newRange]); 139 | requestAnimationFrame(function() { 140 | newRange.$el.find('.elessar-handle:first-child').trigger(ev.type); 141 | }); 142 | } 143 | }, 144 | 145 | removePhantom: function() { 146 | // NOOP 147 | } 148 | }); 149 | 150 | module.exports = Phantom; 151 | 152 | },{"./raf":6,"./range":7}],6:[function(require,module,exports){ 153 | // thanks to http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating 154 | var lastTime = 0; 155 | var vendors = ['webkit', 'moz'], requestAnimationFrame = window.requestAnimationFrame; 156 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 157 | requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 158 | } 159 | 160 | if (!requestAnimationFrame) { 161 | requestAnimationFrame = function(callback, element) { 162 | var currTime = new Date().getTime(); 163 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 164 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 165 | timeToCall); 166 | lastTime = currTime + timeToCall; 167 | return id; 168 | }; 169 | } 170 | 171 | module.exports = requestAnimationFrame; 172 | 173 | },{}],7:[function(require,module,exports){ 174 | var $ = (window.$); 175 | var Element = require('./element'); 176 | var getEventProperty = require('./eventprop'); 177 | var vertical = require('./vertical'); 178 | 179 | var Range = Element.extend(vertical).extend({ 180 | initialize: function initialize(options) { 181 | var self = this; 182 | initialize.super$.call(this, 183 | '
' 184 | ); 185 | this.options = options; 186 | this.perant = options.perant; 187 | 188 | if(this.options.rangeClass) this.$el.addClass(this.options.rangeClass); 189 | 190 | if(!this.readonly()) { 191 | this.$el.prepend('
').append('
'); 192 | this.on('mouseenter.elessar touchstart.elessar', $.proxy(this.removePhantom, this)); 193 | this.on('mousedown.elessar touchstart.elessar', $.proxy(this.mousedown, this)); 194 | this.on('click', $.proxy(this.click, this)); 195 | } else { 196 | this.$el.addClass('elessar-readonly'); 197 | } 198 | if(typeof this.options.label === 'function') { 199 | this.on('changing', function(ev, range) { 200 | self.writeLabel( 201 | self.options.label.call(self, range.map($.proxy(self.perant.normalise, self.perant))) 202 | ); 203 | }); 204 | } else { 205 | this.writeLabel(this.options.label); 206 | } 207 | 208 | this.range = []; 209 | this.hasChanged = false; 210 | 211 | if(this.options.value) this.val(this.options.value); 212 | 213 | }, 214 | 215 | writeLabel: function(text) { 216 | this.$el.find('.elessar-barlabel')[this.options.htmlLabel ? 'html' : 'text'](text); 217 | }, 218 | 219 | isVertical: function() { 220 | return this.perant.options.vertical; 221 | }, 222 | 223 | removePhantom: function() { 224 | this.perant.removePhantom(); 225 | }, 226 | 227 | readonly: function() { 228 | if(typeof this.options.readonly === 'function') { 229 | return this.options.readonly.call(this.perant, this); 230 | } 231 | return this.options.readonly; 232 | }, 233 | 234 | val: function(range, valOpts) { 235 | 236 | if(typeof range === 'undefined') { 237 | return this.range; 238 | } 239 | 240 | valOpts = $.extend({},{ 241 | dontApplyDelta: false, 242 | trigger: true 243 | }, valOpts || {}); 244 | 245 | var next = this.perant.nextRange(this.$el), 246 | prev = this.perant.prevRange(this.$el), 247 | delta = range[1] - range[0], 248 | self = this; 249 | 250 | if(this.options.snap) { 251 | range = range.map(snap); 252 | delta = snap(delta); 253 | } 254 | if (next && next.val()[0] <= range[1] && prev && prev.val()[1] >= range[0]) { 255 | range[1] = next.val()[0]; 256 | range[0] = prev.val()[1]; 257 | } 258 | if (next && next.val()[0] < range[1]) { 259 | if(!this.perant.options.allowSwap || next.val()[1] >= range[0]) { 260 | range[1] = next.val()[0]; 261 | if(!valOpts.dontApplyDelta) range[0] = range[1] - delta; 262 | } else { 263 | this.perant.repositionRange(this, range); 264 | } 265 | } 266 | if (prev && prev.val()[1] > range[0]) { 267 | if(!this.perant.options.allowSwap || prev.val()[0] <= range[1]) { 268 | range[0] = prev.val()[1]; 269 | if(!valOpts.dontApplyDelta) range[1] = range[0] + delta; 270 | } else { 271 | this.perant.repositionRange(this, range); 272 | } 273 | } 274 | if (range[1] >= 1) { 275 | range[1] = 1; 276 | if(!valOpts.dontApplyDelta) range[0] = 1 - delta; 277 | } 278 | if (range[0] <= 0) { 279 | range[0] = 0; 280 | if(!valOpts.dontApplyDelta) range[1] = delta; 281 | } 282 | if(this.perant.options.bound) { 283 | var bound = this.perant.options.bound(this); 284 | if(bound) { 285 | if(bound.upper && range[1] > this.perant.abnormalise(bound.upper)) { 286 | range[1] = this.perant.abnormalise(bound.upper); 287 | if(!valOpts.dontApplyDelta) range[0] = range[1] - delta; 288 | } 289 | if(bound.lower && range[0] < this.perant.abnormalise(bound.lower)) { 290 | range[0] = this.perant.abnormalise(bound.lower); 291 | if(!valOpts.dontApplyDelta) range[1] = range[0] + delta; 292 | } 293 | } 294 | } 295 | 296 | if(this.range[0] === range[0] && this.range[1] === range[1]) return this.$el; 297 | 298 | this.range = range; 299 | 300 | if(valOpts.trigger) { 301 | this.$el.triggerHandler('changing', [range, this.$el]); 302 | this.hasChanged = true; 303 | } 304 | 305 | var start = 100*range[0] + '%', 306 | size = 100*(range[1] - range[0]) + '%'; 307 | 308 | this.draw( 309 | this.perant.options.vertical ? 310 | {top: start, minHeight: size} : 311 | {left: start, minWidth: size} 312 | ); 313 | 314 | return this; 315 | 316 | function snap(val) { return Math.round(val / self.options.snap) * self.options.snap; } 317 | function sign(x) { return x ? x < 0 ? -1 : 1 : 0; } 318 | }, 319 | 320 | click: function(ev) { 321 | ev.stopPropagation(); 322 | ev.preventDefault(); 323 | 324 | var self = this; 325 | 326 | if(ev.which !== 2 || !this.perant.options.allowDelete) return; 327 | 328 | if(this.deleteConfirm) { 329 | this.perant.removeRange(this); 330 | clearTimeout(this.deleteTimeout); 331 | } else { 332 | this.$el.addClass('elessar-delete-confirm'); 333 | this.deleteConfirm = true; 334 | 335 | this.deleteTimeout = setTimeout(function() { 336 | self.$el.removeClass('elessar-delete-confirm'); 337 | self.deleteConfirm = false; 338 | }, this.perant.options.deleteTimeout); 339 | } 340 | }, 341 | 342 | mousedown: function(ev) { 343 | ev.stopPropagation(); 344 | ev.preventDefault(); 345 | this.hasChanged = false; 346 | if(ev.which > 1) return; 347 | 348 | if ($(ev.target).is('.elessar-handle:first-child')) { 349 | $('body').addClass('elessar-resizing').toggleClass('elessar-resizing-vertical', this.isVertical()); 350 | $(document).on('mousemove.elessar touchmove.elessar', this.resizeStart(ev)); 351 | } else if ($(ev.target).is('.elessar-handle:last-child')) { 352 | $('body').addClass('elessar-resizing').toggleClass('elessar-resizing-vertical', this.isVertical()); 353 | $(document).on('mousemove.elessar touchmove.elessar', this.resizeEnd(ev)); 354 | } else { 355 | $('body').addClass('elessar-dragging').toggleClass('elessar-dragging-vertical', this.isVertical()); 356 | $(document).on('mousemove.elessar touchmove.elessar', this.drag(ev)); 357 | } 358 | 359 | var self = this; 360 | 361 | $(document).one('mouseup.elessar touchend.elessar', function(ev) { 362 | ev.stopPropagation(); 363 | ev.preventDefault(); 364 | 365 | if(self.hasChanged && !self.swapping) self.trigger('change', [self.range, self.$el]); 366 | self.swapping = false; 367 | $(document).off('mouseup.elessar mousemove.elessar touchend.elessar touchmove.elessar'); 368 | $('body').removeClass('elessar-resizing elessar-dragging elessar-resizing-vertical elessar-dragging-vertical'); 369 | }); 370 | }, 371 | 372 | drag: function(origEv) { 373 | var self = this, 374 | beginStart = this.startProp('offset'), 375 | beginPosStart = this.startProp('position'), 376 | mousePos = getEventProperty(this.ifVertical('clientY','clientX'), origEv), 377 | mouseOffset = mousePos ? mousePos - beginStart : 0, 378 | beginSize = this.totalSize(), 379 | perant = this.options.perant, 380 | perantStart = perant.startProp('offset'), 381 | perantSize = perant.totalSize(); 382 | 383 | return function(ev) { 384 | ev.stopPropagation(); 385 | ev.preventDefault(); 386 | var mousePos = getEventProperty(self.ifVertical('clientY','clientX'), ev); 387 | if(typeof mousePos !== 'undefined') { 388 | var start = mousePos - perantStart - mouseOffset; 389 | 390 | if (start >= 0 && start <= perantSize - beginSize) { 391 | var rangeOffset = start / perantSize - self.range[0]; 392 | self.val([start / perantSize, self.range[1] + rangeOffset]); 393 | } else { 394 | mouseOffset = mousePos - self.startProp('offset'); 395 | } 396 | } 397 | }; 398 | }, 399 | resizeEnd: function(origEv) { 400 | var self = this, 401 | beginStart = this.startProp('offset'), 402 | beginPosStart = this.startProp('position'), 403 | mousePos = getEventProperty(this.ifVertical('clientY','clientX'), origEv), 404 | mouseOffset = mousePos ? mousePos - beginStart : 0, 405 | beginSize = this.totalSize(), 406 | perant = this.options.perant, 407 | perantStart = perant.startProp('offset'), 408 | perantSize = perant.totalSize(), 409 | minSize = this.options.minSize * perantSize; 410 | 411 | return function(ev) { 412 | var opposite = ev.type === 'touchmove' ? 'touchend' : 'mouseup', 413 | subsequent = ev.type === 'touchmove' ? 'touchstart' : 'mousedown'; 414 | ev.stopPropagation(); 415 | ev.preventDefault(); 416 | var mousePos = getEventProperty(self.ifVertical('clientY','clientX'), ev); 417 | var size = mousePos - beginStart; 418 | 419 | if(typeof mousePos !== 'undefined') { 420 | if (size > perantSize - beginPosStart) size = perantSize - beginPosStart; 421 | if (size >= minSize) { 422 | self.val([self.range[0], self.range[0] + size / perantSize], {dontApplyDelta: true}); 423 | } else if(size <= 10) { 424 | self.swapping = true; 425 | $(document).trigger(opposite + '.elessar'); 426 | self.$el.find('.elessar-handle:first-child').trigger(subsequent + '.elessar'); 427 | } 428 | } 429 | }; 430 | }, 431 | 432 | resizeStart: function(origEv) { 433 | var self = this, 434 | beginStart = this.startProp('offset'), 435 | beginPosStart = this.startProp('position'), 436 | mousePos = getEventProperty(this.ifVertical('clientY','clientX'), origEv), 437 | mouseOffset = mousePos ? mousePos - beginStart : 0, 438 | beginSize = this.totalSize(), 439 | perant = this.options.perant, 440 | perantStart = perant.startProp('offset'), 441 | perantSize = perant.totalSize(), 442 | minSize = this.options.minSize * perantSize; 443 | 444 | return function(ev) { 445 | var opposite = ev.type === 'touchmove' ? 'touchend' : 'mouseup', 446 | subsequent = ev.type === 'touchmove' ? 'touchstart' : 'mousedown'; 447 | 448 | ev.stopPropagation(); 449 | ev.preventDefault(); 450 | var mousePos = getEventProperty(self.ifVertical('clientY','clientX'), ev); 451 | var start = mousePos - perantStart - mouseOffset; 452 | var size = beginPosStart + beginSize - start; 453 | 454 | if(typeof mousePos !== 'undefined') { 455 | if (start < 0) { 456 | start = 0; 457 | size = beginPosStart + beginSize; 458 | } 459 | if (size >= minSize) { 460 | self.val([start / perantSize, self.range[1]], {dontApplyDelta: true}); 461 | } else if(size <= 10) { 462 | self.swapping = true; 463 | $(document).trigger(opposite + '.elessar'); 464 | self.$el.find('.elessar-handle:last-child').trigger(subsequent + '.elessar'); 465 | } 466 | } 467 | }; 468 | } 469 | }); 470 | 471 | module.exports = Range; 472 | 473 | 474 | },{"./element":1,"./eventprop":2,"./vertical":9}],8:[function(require,module,exports){ 475 | var Element = require('./element'); 476 | var Range = require('./range'); 477 | var Phantom = require('./phantom'); 478 | var Indicator = require('./indicator'); 479 | var getEventProperty = require('./eventprop'); 480 | var vertical = require('./vertical'); 481 | var $ = (window.$); 482 | var Mark = require('./mark.js'); 483 | 484 | var RangeBar = Element.extend(vertical).extend({ 485 | initialize: function initialize(options) { 486 | options = options || {}; 487 | initialize.super$.call(this, '
'); 488 | this.options = $.extend({}, RangeBar.defaults, options); 489 | this.options.min = this.options.valueParse(this.options.min); 490 | this.options.max = this.options.valueParse(this.options.max); 491 | if(this.options.barClass) this.$el.addClass(this.options.barClass); 492 | if(this.options.vertical) this.$el.addClass('elessar-vertical'); 493 | 494 | this.ranges = []; 495 | this.on('mousemove.elessar touchmove.elessar', $.proxy(this.mousemove, this)); 496 | this.on('mouseleave.elessar touchleave.elessar', $.proxy(this.removePhantom, this)); 497 | 498 | if(options.values) this.setVal(options.values); 499 | 500 | if(options.bgLabels) { 501 | options.bgMark = { count: options.bgLabels }; 502 | } 503 | 504 | if(options.bgMark) { 505 | this.$markContainer = $('
').appendTo(this.$el); 506 | if(options.bgMark.count) { 507 | for(var i = 0; i < options.bgMark.count; ++i) { 508 | this.$markContainer.append((new Mark({ 509 | label: options.bgMark.label, 510 | value: i / options.bgMark.count, 511 | perant: this 512 | })).$el); 513 | } 514 | } else if(options.bgMark.interval) { 515 | for(var i = this.abnormalise(this.options.min); i < this.abnormalise(this.options.max); i += this.abnormalise(options.bgMark.interval)) { 516 | this.$markContainer.append((new Mark({ 517 | label: options.bgMark.label, 518 | value: i, 519 | perant: this 520 | })).$el); 521 | } 522 | } 523 | } 524 | 525 | var self = this; 526 | 527 | if(options.indicator) { 528 | var indicator = this.indicator = new Indicator({ 529 | perant: this, 530 | vertical: this.options.vertical, 531 | indicatorClass: options.indicatorClass 532 | }); 533 | indicator.val(this.abnormalise(options.indicator(this, indicator, function() { 534 | indicator.val(self.abnormalise(options.indicator(self, indicator))); 535 | }))); 536 | this.$el.append(indicator.$el); 537 | } 538 | }, 539 | 540 | normaliseRaw: function (value) { 541 | return this.options.min + value * (this.options.max - this.options.min); 542 | }, 543 | 544 | normalise: function (value) { 545 | return this.options.valueFormat(this.normaliseRaw(value)); 546 | }, 547 | 548 | abnormaliseRaw: function (value) { 549 | return (value - this.options.min)/(this.options.max - this.options.min); 550 | }, 551 | 552 | abnormalise: function (value) { 553 | return this.abnormaliseRaw(this.options.valueParse(value)); 554 | }, 555 | 556 | findGap: function(range) { 557 | var newIndex = 0; 558 | this.ranges.forEach(function($r, i) { 559 | if($r.val()[0] < range[0] && $r.val()[1] < range[1]) newIndex = i + 1; 560 | }); 561 | 562 | return newIndex; 563 | }, 564 | 565 | insertRangeIndex: function(range, index, avoidList) { 566 | if(!avoidList) this.ranges.splice(index, 0, range); 567 | 568 | if(this.ranges[index - 1]) { 569 | this.ranges[index - 1].$el.after(range.$el); 570 | } else { 571 | this.$el.prepend(range.$el); 572 | } 573 | }, 574 | 575 | addRange: function(range, data) { 576 | var $range = Range({ 577 | perant: this, 578 | snap: this.options.snap ? this.abnormaliseRaw(this.options.snap + this.options.min) : null, 579 | label: this.options.label, 580 | rangeClass: this.options.rangeClass, 581 | minSize: this.options.minSize ? this.abnormaliseRaw(this.options.minSize + this.options.min) : null, 582 | readonly: this.options.readonly, 583 | htmlLabel: this.options.htmlLabel 584 | }); 585 | 586 | if (this.options.data) { 587 | $range.data(this.options.data.call($range, this)); 588 | } 589 | 590 | if (data) { 591 | $range.data(data); 592 | } 593 | 594 | this.insertRangeIndex($range, this.findGap(range)); 595 | $range.val(range); 596 | 597 | var self = this; 598 | 599 | $range.on('changing', function(ev, nrange, changed) { 600 | ev.stopPropagation(); 601 | self.trigger('changing', [self.val(), changed]); 602 | }).on('change', function(ev, nrange, changed) { 603 | ev.stopPropagation(); 604 | self.trigger('change', [self.val(), changed]); 605 | }); 606 | return $range; 607 | }, 608 | 609 | prevRange: function(range) { 610 | var idx = range.index(); 611 | if(idx >= 0) return this.ranges[idx - 1]; 612 | }, 613 | 614 | nextRange: function(range) { 615 | var idx = range.index(); 616 | if(idx >= 0) return this.ranges[range instanceof Phantom ? idx : idx + 1]; 617 | }, 618 | 619 | setVal: function(ranges) { 620 | if (this.ranges.length > ranges.length) { 621 | for (var i = ranges.length-1, l = this.ranges.length-1; i < l; --l) { 622 | this.removeRange(l); 623 | } 624 | this.ranges.length = ranges.length; 625 | } 626 | 627 | var self = this; 628 | 629 | ranges.forEach(function(range, i) { 630 | if(self.ranges[i]) { 631 | self.ranges[i].val(range.map($.proxy(self.abnormalise, self))); 632 | } else { 633 | self.addRange(range.map($.proxy(self.abnormalise, self))); 634 | } 635 | }); 636 | 637 | return this; 638 | }, 639 | 640 | val: function(ranges) { 641 | var self = this; 642 | if(typeof ranges === 'undefined') { 643 | return this.ranges.map(function(range) { 644 | return range.val().map($.proxy(self.normalise, self)); 645 | }); 646 | } 647 | 648 | if(!this.readonly()) this.setVal(ranges); 649 | return this; 650 | }, 651 | 652 | removePhantom: function() { 653 | if(this.phantom) { 654 | this.phantom.remove(); 655 | this.phantom = null; 656 | } 657 | }, 658 | 659 | removeRange: function(i, noTrigger, preserveEvents) { 660 | if(i instanceof Range) { 661 | i = this.ranges.indexOf(i); 662 | } 663 | this.ranges.splice(i,1)[0][preserveEvents ? 'detach' : 'remove'](); 664 | if(!noTrigger) { 665 | this.trigger('change', [this.val()]); 666 | } 667 | }, 668 | 669 | repositionRange: function(range, val) { 670 | this.removeRange(range, true, true); 671 | this.insertRangeIndex(range, this.findGap(val)); 672 | }, 673 | 674 | calcGap: function(index) { 675 | var start = this.ranges[index - 1] ? this.ranges[index - 1].val()[1] : 0; 676 | var end = this.ranges[index] ? this.ranges[index].val()[0] : 1; 677 | return this.normaliseRaw(end) - this.normaliseRaw(start); 678 | }, 679 | 680 | readonly: function() { 681 | if(typeof this.options.readonly === 'function') { 682 | return this.options.readonly.call(this); 683 | } 684 | return this.options.readonly; 685 | }, 686 | 687 | mousemove: function(ev) { 688 | var w = this.options.minSize ? this.abnormaliseRaw(this.options.minSize + this.options.min) : 0.05; 689 | var pageStart = getEventProperty(this.ifVertical('pageY','pageX'), ev); 690 | var val = (pageStart - this.startProp('offset'))/this.totalSize() - w/2; 691 | 692 | var direct = ev.target === ev.currentTarget; 693 | var phantom = $(ev.target).is('.elessar-phantom'); 694 | 695 | if((direct || phantom) && this.ranges.length < this.options.maxRanges && !$('body').is('.elessar-dragging, .elessar-resizing') && !this.readonly()) { 696 | if(!this.phantom) this.phantom = Phantom({ 697 | perant: this, 698 | snap: this.options.snap ? this.abnormaliseRaw(this.options.snap + this.options.min) : null, 699 | label: "+", 700 | minSize: this.options.minSize ? this.abnormaliseRaw(this.options.minSize + this.options.min) : null, 701 | rangeClass: this.options.rangeClass 702 | }); 703 | var idx = this.findGap([val,val + w]); 704 | var self = this; 705 | this.one('addrange', function(ev, val, range) { 706 | range.one('mouseup', function() { 707 | self.trigger('change', [self.val(), range]); 708 | }); 709 | }); 710 | 711 | if(!this.options.minSize || this.calcGap(idx) >= this.options.minSize) { 712 | this.insertRangeIndex(this.phantom, idx, true); 713 | this.phantom.val([val,val + w], {trigger: false}); 714 | } 715 | } 716 | } 717 | }); 718 | 719 | RangeBar.defaults = { 720 | min: 0, 721 | max: 100, 722 | valueFormat: function (a) {return a;}, 723 | valueParse: function (a) {return a;}, 724 | maxRanges: Infinity, 725 | readonly: false, 726 | bgLabels: 0, 727 | deleteTimeout: 5000, 728 | allowDelete: false, 729 | vertical: false, 730 | htmlLabel: false, 731 | allowSwap: true 732 | }; 733 | 734 | module.exports = RangeBar; 735 | 736 | },{"./element":1,"./eventprop":2,"./indicator":3,"./mark.js":4,"./phantom":5,"./range":7,"./vertical":9}],9:[function(require,module,exports){ 737 | module.exports = { 738 | isVertical: function() { 739 | return this.options.vertical; 740 | }, 741 | 742 | ifVertical: function(v, h) { 743 | return this.isVertical() ? v : h; 744 | }, 745 | edge: function(which) { 746 | if(which === 'start') { 747 | return this.ifVertical('top', 'left'); 748 | } else if(which === 'end') { 749 | return this.ifVertical('bottom', 'right'); 750 | } 751 | throw new TypeError('What kind of an edge is ' + which); 752 | }, 753 | totalSize: function() { 754 | return this.$el[this.ifVertical('height','width')](); 755 | }, 756 | edgeProp: function(edge, prop) { 757 | var o = this.$el[prop](); 758 | return o[this.edge(edge)]; 759 | }, 760 | startProp: function(prop) { 761 | return this.edgeProp('start', prop); 762 | }, 763 | endProp: function(prop) { 764 | return this.edgeProp('end', prop); 765 | } 766 | }; 767 | 768 | },{}],10:[function(require,module,exports){ 769 | (function(){ 770 | (function(definition){ 771 | switch (false) { 772 | case !(typeof define === 'function' && define.amd != null): 773 | return define([], definition); 774 | case typeof exports !== 'object': 775 | return module.exports = definition(); 776 | default: 777 | return this.Base = definition(); 778 | } 779 | })(function(){ 780 | var Base; 781 | return Base = (function(){ 782 | Base.displayName = 'Base'; 783 | var attach, prototype = Base.prototype, constructor = Base; 784 | attach = function(obj, name, prop, super$, superclass$){ 785 | return obj[name] = typeof prop === 'function' ? import$(function(){ 786 | var this$ = this; 787 | prop.superclass$ = superclass$; 788 | prop.super$ = function(){ 789 | return super$.apply(this$, arguments); 790 | }; 791 | return prop.apply(this, arguments); 792 | }, prop) : prop; 793 | }; 794 | Base.extend = function(displayName, proto){ 795 | proto == null && (proto = displayName); 796 | return (function(superclass){ 797 | var name, ref$, prop, prototype = extend$(import$(constructor, superclass), superclass).prototype; 798 | import$(constructor, Base); 799 | if (typeof displayName === 'string') { 800 | constructor.displayName = displayName; 801 | } 802 | function constructor(){ 803 | var this$ = this instanceof ctor$ ? this : new ctor$; 804 | this$.initialize.apply(this$, arguments); 805 | return this$; 806 | } function ctor$(){} ctor$.prototype = prototype; 807 | prototype.initialize = function(){ 808 | if (superclass.prototype.initialize != null) { 809 | return superclass.prototype.initialize.apply(this, arguments); 810 | } else { 811 | return superclass.apply(this, arguments); 812 | } 813 | }; 814 | for (name in ref$ = proto) { 815 | prop = ref$[name]; 816 | attach(prototype, name, prop, prototype[name], superclass); 817 | } 818 | return constructor; 819 | }(this)); 820 | }; 821 | Base.meta = function(meta){ 822 | var name, prop; 823 | for (name in meta) { 824 | prop = meta[name]; 825 | attach(this, name, prop, this[name], this); 826 | } 827 | return this; 828 | }; 829 | prototype.initialize = function(){}; 830 | function Base(){} 831 | return Base; 832 | }()); 833 | }); 834 | function import$(obj, src){ 835 | var own = {}.hasOwnProperty; 836 | for (var key in src) if (own.call(src, key)) obj[key] = src[key]; 837 | return obj; 838 | } 839 | function extend$(sub, sup){ 840 | function fun(){} fun.prototype = (sub.superclass = sup).prototype; 841 | (sub.prototype = new fun).constructor = sub; 842 | if (typeof sup.extended == 'function') sup.extended(sub); 843 | return sub; 844 | } 845 | }).call(this); 846 | 847 | },{}]},{},[8])(8) 848 | }); -------------------------------------------------------------------------------- /test/functional.js: -------------------------------------------------------------------------------- 1 | var tape = require('tape'); 2 | var $ = require('jquery'); 3 | var RangeBar = require('../lib/rangebar.js'); 4 | var Indicator = require('../lib/indicator.js'); 5 | var raf = require('../lib/raf.js'); 6 | var utils = require('./utils.js'); 7 | var waitForAnimation = utils.waitForAnimation; 8 | var drag = utils.drag; 9 | var move = utils.move; 10 | 11 | require('../elessar.css'); 12 | 13 | tape.test('Range bar functional tests', function(t) { 14 | t.test('dragging', function(t) { 15 | t.test('to the right', function(t) { 16 | var r = new RangeBar({values: [[0, 10]]}); 17 | r.$el.css({width: '100px'}).appendTo('body'); 18 | waitForAnimation(function() { 19 | drag(r.ranges[0].$el, {x: 10, y: 0}, function() { 20 | t.rangebarValuesEqual(r.val(), [[10, 20]], 'dragging updates the value'); 21 | t.end(); 22 | }); 23 | }); 24 | }); 25 | 26 | t.test('to the left', function(t) { 27 | var r = new RangeBar({values: [[10, 20]]}); 28 | r.$el.css({width: '100px'}).appendTo('body'); 29 | waitForAnimation(function() { 30 | drag(r.ranges[0].$el, {x: -10, y: 0}, function() { 31 | t.rangebarValuesEqual(r.val(), [[0, 10]], 'dragging updates the value'); 32 | t.end(); 33 | }); 34 | }); 35 | }); 36 | 37 | t.test('to collide with end', function(t) { 38 | var r = new RangeBar({values: [[85, 95]]}); 39 | r.$el.css({width: '100px'}).appendTo('body'); 40 | waitForAnimation(function() { 41 | drag(r.ranges[0].$el, {x: 10, y: 0, step: true}, function() { 42 | t.rangebarValuesEqual(r.val(), [[90, 100]], 'dragging updates the value'); 43 | t.end(); 44 | }); 45 | }); 46 | }); 47 | 48 | t.test('to collide with start', function(t) { 49 | var r = new RangeBar({values: [[5, 15]]}); 50 | r.$el.css({width: '100px'}).appendTo('body'); 51 | waitForAnimation(function() { 52 | drag(r.ranges[0].$el, {x: -10, y: 0, step: true}, function() { 53 | t.rangebarValuesEqual(r.val(), [[0, 10]], 'dragging updates the value'); 54 | t.end(); 55 | }); 56 | }); 57 | }); 58 | 59 | t.test('to collide with another range to the left', function(t) { 60 | var r = new RangeBar({values: [[5, 15], [20, 30]]}); 61 | r.$el.css({width: '100px'}).appendTo('body'); 62 | waitForAnimation(function() { 63 | drag(r.ranges[1].$el, {x: -10, y: 0, step: true}, function() { 64 | t.rangebarValuesEqual(r.val(), [[5, 15],[15,25]], 'dragging updates the value'); 65 | t.end(); 66 | }); 67 | }); 68 | }); 69 | 70 | t.test('to collide with another range to the right', function(t) { 71 | var r = new RangeBar({values: [[5, 15], [20, 30]]}); 72 | r.$el.css({width: '100px'}).appendTo('body'); 73 | waitForAnimation(function() { 74 | drag(r.ranges[0].$el, {x: 10, y: 0, step: true}, function() { 75 | t.rangebarValuesEqual(r.val(), [[10, 20],[20,30]], 'dragging updates the value'); 76 | t.end(); 77 | }); 78 | }); 79 | }); 80 | 81 | t.test('past another range to the right', function(t) { 82 | t.test('swaps the ranges', function(t) { 83 | var r = new RangeBar({values: [[5, 15], [20, 30]]}); 84 | r.$el.css({width: '100px'}).appendTo('body'); 85 | waitForAnimation(function() { 86 | drag(r.ranges[0].$el, {x: 50, y: 0, step: true}, function() { 87 | t.rangebarValuesEqual(r.val(), [[20, 30],[55,65]], 'swaps the ranges'); 88 | t.end(); 89 | }); 90 | }); 91 | }); 92 | 93 | t.test('doesn\'t swap if allowSwap is false', function(t) { 94 | var r = new RangeBar({values: [[5, 15], [20, 30]], allowSwap: false}); 95 | r.$el.css({width: '100px'}).appendTo('body'); 96 | waitForAnimation(function() { 97 | drag(r.ranges[0].$el, {x: 50, y: 0, step: true}, function() { 98 | t.rangebarValuesEqual(r.val(), [[10, 20],[20, 30]], 'swaps the ranges'); 99 | t.end(); 100 | }); 101 | }); 102 | }); 103 | 104 | t.test('fires the change event once', function(t) { 105 | t.plan(1); 106 | 107 | var r = new RangeBar({values: [[5, 15], [20, 30]]}), once = true; 108 | r.$el.css({width: '100px'}).appendTo('body'); 109 | 110 | var timeout = setTimeout(function() { 111 | t.fail('timed out'); 112 | }, 10000); 113 | 114 | r.on('change', function() { 115 | clearTimeout(timeout); 116 | t.ok(once, 'fired ' + (once ? '' : 'more than ') + 'once'); 117 | once = false; 118 | }); 119 | 120 | waitForAnimation(function() { 121 | drag(r.ranges[0].$el, {x: 50, y: 0, step: true}); 122 | }); 123 | }); 124 | 125 | t.end(); 126 | }); 127 | 128 | t.test('past another range to the left', function(t) { 129 | t.test('swaps the ranges', function(t) { 130 | var r = new RangeBar({values: [[20, 30],[55,65]]}); 131 | r.$el.css({width: '100px'}).appendTo('body'); 132 | waitForAnimation(function() { 133 | drag(r.ranges[1].$el, {x: -50, y: 0, step: true}, function() { 134 | t.rangebarValuesEqual(r.val(), [[5, 15], [20, 30]], 'swaps the ranges'); 135 | t.end(); 136 | }); 137 | }); 138 | }); 139 | 140 | t.test('doesn\'t swap if allowSwap is false', function(t) { 141 | var r = new RangeBar({values: [[20, 30],[55,65]], allowSwap: false}); 142 | r.$el.css({width: '100px'}).appendTo('body'); 143 | waitForAnimation(function() { 144 | drag(r.ranges[1].$el, {x: -50, y: 0, step: true}, function() { 145 | t.rangebarValuesEqual(r.val(), [[20, 30],[30, 40]], 'swaps the ranges'); 146 | t.end(); 147 | }); 148 | }); 149 | }); 150 | 151 | t.end(); 152 | }); 153 | 154 | t.test('past another range to the right and back', function(t) { 155 | var r = new RangeBar({values: [[5, 15], [20, 30]]}); 156 | r.$el.css({width: '100px'}).appendTo('body'); 157 | waitForAnimation(function() { 158 | drag(r.ranges[0].$el, {x: 50, y: 0, step: true}, function() { 159 | t.rangebarValuesEqual(r.val(), [[20, 30],[55,65]], 'swaps the ranges'); 160 | 161 | waitForAnimation(function() { 162 | drag(r.ranges[1].$el, {x: -50, y: 0, step: true}, function() { 163 | t.rangebarValuesEqual(r.val(), [[5, 15], [20, 30]], 'and back'); 164 | t.end(); 165 | }); 166 | }); 167 | }); 168 | }); 169 | }); 170 | 171 | t.test('past another range to the left and back', function(t) { 172 | var r = new RangeBar({values: [[20, 30],[55,65]]}); 173 | r.$el.css({width: '100px'}).appendTo('body'); 174 | waitForAnimation(function() { 175 | drag(r.ranges[1].$el, {x: -50, y: 0, step: true}, function() { 176 | t.rangebarValuesEqual(r.val(), [[5, 15], [20, 30]], 'swaps the ranges'); 177 | 178 | waitForAnimation(function() { 179 | drag(r.ranges[0].$el, {x: 50, y: 0, step: true}, function() { 180 | t.rangebarValuesEqual(r.val(), [[20, 30],[55,65]], 'and back'); 181 | t.end(); 182 | }); 183 | }); 184 | }); 185 | }); 186 | }); 187 | 188 | t.test('with an upper bound', function(t) { 189 | var r = new RangeBar({ 190 | values: [[20, 30]], 191 | bound: function(range) { 192 | return {upper: 40}; 193 | } 194 | }); 195 | r.$el.css({width: '100px'}).appendTo('body'); 196 | waitForAnimation(function() { 197 | drag(r.ranges[0].$el, {x: 20, y: 0, step: true}, function() { 198 | t.rangebarValuesEqual(r.val(), [[30,40]], 'dragging updates the value'); 199 | t.end(); 200 | }); 201 | }); 202 | }) 203 | 204 | t.test('with a lower bound', function(t) { 205 | var r = new RangeBar({ 206 | values: [[20, 30]], 207 | bound: function(range) { 208 | return {lower: 10}; 209 | } 210 | }); 211 | r.$el.css({width: '100px'}).appendTo('body'); 212 | waitForAnimation(function() { 213 | drag(r.ranges[0].$el, {x: -20, y: 0, step: true}, function() { 214 | t.rangebarValuesEqual(r.val(), [[10, 20]], 'dragging updates the value'); 215 | t.end(); 216 | }); 217 | }); 218 | }); 219 | 220 | t.test('fires change', function(t) { 221 | t.test('for a range somewhere in the middle', function(t) { 222 | t.plan(1); 223 | var r = new RangeBar({values: [[10, 20]]}); 224 | r.$el.css({width: '100px'}).appendTo('body'); 225 | 226 | var timeout = setTimeout(function() { 227 | t.fail('timed out'); 228 | }, 2000); 229 | 230 | r.on('change', function() { 231 | clearTimeout(timeout); 232 | t.pass('fires an event'); 233 | }); 234 | 235 | waitForAnimation(function() { 236 | drag(r.ranges[0].$el, {x: 10, y: 0}); 237 | }); 238 | }); 239 | 240 | t.test('for a range at the start', function(t) { 241 | t.plan(1); 242 | var r = new RangeBar({values: [[0, 10]]}); 243 | r.$el.css({width: '100px'}).appendTo('body'); 244 | 245 | var timeout = setTimeout(function() { 246 | t.fail('timed out'); 247 | }, 2000); 248 | 249 | r.on('change', function() { 250 | clearTimeout(timeout); 251 | t.pass('fires an event'); 252 | }); 253 | 254 | waitForAnimation(function() { 255 | drag(r.ranges[0].$el, {x: 10, y: 0}); 256 | }); 257 | }); 258 | 259 | t.test('for a range at the end', function(t) { 260 | t.plan(1); 261 | var r = new RangeBar({values: [[90, 100]]}); 262 | r.$el.css({width: '100px'}).appendTo('body'); 263 | 264 | var timeout = setTimeout(function() { 265 | t.fail('timed out'); 266 | }, 2000); 267 | 268 | r.on('change', function() { 269 | clearTimeout(timeout); 270 | t.pass('fires an event'); 271 | }); 272 | 273 | waitForAnimation(function() { 274 | drag(r.ranges[0].$el, {x: -10, y: 0}); 275 | }); 276 | }); 277 | 278 | t.end(); 279 | }); 280 | 281 | t.end(); 282 | }); 283 | 284 | t.test('right resizing', function(t) { 285 | t.test('to the right', function(t) { 286 | var r = new RangeBar({values: [[0, 10]]}); 287 | r.$el.css({width: '100px'}).appendTo('body'); 288 | waitForAnimation(function() { 289 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {x: 10, y: 0, rightEdge: true}, function() { 290 | t.rangebarValuesEqual(r.val(), [[0, 20]], 'dragging right handle updates the value'); 291 | t.end(); 292 | }); 293 | }); 294 | }); 295 | 296 | t.test('to the left', function(t) { 297 | var r = new RangeBar({values: [[0, 20]]}); 298 | r.$el.css({width: '100px'}).appendTo('body'); 299 | waitForAnimation(function() { 300 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {x: -10, y: 0, rightEdge: true}, function() { 301 | t.rangebarValuesEqual(r.val(), [[0, 10]], 'dragging right handle updates the value'); 302 | t.end(); 303 | }); 304 | }); 305 | }); 306 | 307 | t.test('beyond the end', function(t) { 308 | var r = new RangeBar({values: [[85, 95]]}); 309 | r.$el.css({width: '100px'}).appendTo('body'); 310 | waitForAnimation(function() { 311 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {x: 10, y: 0, rightEdge: true}, function() { 312 | t.rangebarValuesEqual(r.val(), [[85, 100]], 'dragging right handle updates the value'); 313 | t.end(); 314 | }); 315 | }); 316 | }); 317 | 318 | t.test('to overlap another range', function(t) { 319 | var r = new RangeBar({values: [[0, 10], [15, 25]]}); 320 | r.$el.css({width: '100px'}).appendTo('body'); 321 | waitForAnimation(function() { 322 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {x: 10, y: 0, rightEdge: true}, function() { 323 | t.rangebarValuesEqual(r.val(), [[0, 15], [15, 25]], 'dragging right handle updates the value'); 324 | t.end(); 325 | }); 326 | }); 327 | }); 328 | 329 | t.test('beyond the start of the range', function(t) { 330 | t.test('resizes left', function(t) { 331 | var r = new RangeBar({values: [[20, 30]]}); 332 | r.$el.css({width: '100px'}).appendTo('body'); 333 | waitForAnimation(function() { 334 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {x: -20, y: 0, rightEdge: true, step: true}, function() { 335 | t.rangebarValuesEqual(r.val(), [[10, 20]], 'dragging right handle updates the value'); 336 | t.end(); 337 | }); 338 | }); 339 | }); 340 | 341 | t.test('fires change event once', function(t) { 342 | t.plan(1); 343 | 344 | var r = new RangeBar({values: [[20, 30]]}), once = true; 345 | r.$el.css({width: '100px'}).appendTo('body'); 346 | 347 | var timeout = setTimeout(function() { 348 | t.fail('event timed out'); 349 | }, 5000); 350 | 351 | r.on('change', function() { 352 | t.ok(once, 'fired ' + (once ? '' : 'more than ') + 'once'); 353 | clearTimeout(timeout); 354 | once = false; 355 | }); 356 | 357 | waitForAnimation(function() { 358 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {x: -20, y: 0, rightEdge: true, step: true}); 359 | }); 360 | }); 361 | 362 | t.end(); 363 | }); 364 | 365 | t.test('doesn\'t resize below minimum size', function(t) { 366 | var r = new RangeBar({values: [[20, 40]], minSize: 10}); 367 | r.$el.css({width: '100px'}).appendTo('body'); 368 | waitForAnimation(function() { 369 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {x: -15, y: 0, rightEdge: true, step: true}, function() { 370 | t.rangebarValuesEqual(r.val(), [[20, 30]], 'dragging right handle updates the value'); 371 | t.end(); 372 | }); 373 | }); 374 | }); 375 | 376 | t.test('with an upper bound', function(t) { 377 | var r = new RangeBar({ 378 | values: [[20, 30]], 379 | bound: function(range) { 380 | return {upper: 40}; 381 | } 382 | }); 383 | r.$el.css({width: '100px'}).appendTo('body'); 384 | waitForAnimation(function() { 385 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {x: 20, y: 0, step: true}, function() { 386 | t.rangebarValuesEqual(r.val(), [[20,40]], 'dragging right handle updates the value'); 387 | t.end(); 388 | }); 389 | }); 390 | }); 391 | 392 | t.end(); 393 | }); 394 | 395 | t.test('left resizing', function(t) { 396 | t.test('to the right', function(t) { 397 | var r = new RangeBar({values: [[0, 20]]}); 398 | r.$el.css({width: '100px'}).appendTo('body'); 399 | waitForAnimation(function() { 400 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {x: 10, y: 0}, function() { 401 | t.rangebarValuesEqual(r.val(), [[10, 20]], 'dragging left handle updates the value'); 402 | t.end(); 403 | }); 404 | }); 405 | }); 406 | 407 | t.test('to the left', function(t) { 408 | var r = new RangeBar({values: [[20, 30]]}); 409 | r.$el.css({width: '100px'}).appendTo('body'); 410 | waitForAnimation(function() { 411 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {x: -10, y: 0}, function() { 412 | t.rangebarValuesEqual(r.val(), [[10, 30]], 'dragging left handle updates the value'); 413 | t.end(); 414 | }); 415 | }); 416 | }); 417 | 418 | t.test('beyond the end', function(t) { 419 | var r = new RangeBar({values: [[5, 15]]}); 420 | r.$el.css({width: '100px'}).appendTo('body'); 421 | waitForAnimation(function() { 422 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {x: -10, y: 0}, function() { 423 | t.rangebarValuesEqual(r.val(), [[0, 15]], 'dragging left handle updates the value'); 424 | t.end(); 425 | }); 426 | }); 427 | }); 428 | 429 | t.test('to overlap another range', function(t) { 430 | var r = new RangeBar({values: [[0, 10], [15, 25]]}); 431 | r.$el.css({width: '100px'}).appendTo('body'); 432 | waitForAnimation(function() { 433 | drag(r.ranges[1].$el.find('.elessar-handle:first-child'), {x: -10, y: 0}, function() { 434 | t.rangebarValuesEqual(r.val(), [[0, 10], [10, 25]], 'dragging left handle updates the value'); 435 | t.end(); 436 | }); 437 | }); 438 | }); 439 | 440 | t.test('beyond the end of the range', function(t) { 441 | t.test('resizes right', function(t) { 442 | var r = new RangeBar({values: [[20, 30]]}); 443 | r.$el.css({width: '100px'}).appendTo('body'); 444 | waitForAnimation(function() { 445 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {x: 20, y: 0, step: true}, function() { 446 | t.rangebarValuesEqual(r.val(), [[30, 40]], 'dragging left handle updates the value'); 447 | t.end(); 448 | }); 449 | }); 450 | }); 451 | 452 | t.test('fires change event once', function(t) { 453 | t.plan(1); 454 | 455 | var r = new RangeBar({values: [[20, 30]]}), once = true; 456 | r.$el.css({width: '100px'}).appendTo('body'); 457 | 458 | var timeout = setTimeout(function() { 459 | t.fail('event timed out'); 460 | }, 5000); 461 | 462 | r.on('change', function() { 463 | t.ok(once, 'fired ' + (once ? '' : 'more than ') + 'once'); 464 | clearTimeout(timeout); 465 | once = false; 466 | }); 467 | 468 | waitForAnimation(function() { 469 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {x: 20, y: 0, rightEdge: true, step: true}); 470 | }); 471 | }); 472 | 473 | t.end(); 474 | }); 475 | 476 | t.test('doesn\'t resize below minimum size', function(t) { 477 | var r = new RangeBar({values: [[20, 40]], minSize: 10}); 478 | r.$el.css({width: '100px'}).appendTo('body'); 479 | waitForAnimation(function() { 480 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {x: 15, y: 0, step: true}, function() { 481 | t.rangebarValuesEqual(r.val(), [[30, 40]], 'dragging left handle updates the value'); 482 | t.end(); 483 | }); 484 | }); 485 | }); 486 | 487 | t.test('with a lower bound', function(t) { 488 | var r = new RangeBar({ 489 | values: [[20, 30]], 490 | bound: function(range) { 491 | return {lower: 10}; 492 | } 493 | }); 494 | r.$el.css({width: '100px'}).appendTo('body'); 495 | waitForAnimation(function() { 496 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {x: -20, y: 0, step: true}, function() { 497 | t.rangebarValuesEqual(r.val(), [[10,30]], 'dragging left handle updates the value'); 498 | t.end(); 499 | }); 500 | }); 501 | }); 502 | 503 | t.end(); 504 | }); 505 | 506 | t.test('phantoms', function(t) { 507 | t.test('hovering on a blank area', function(t) { 508 | var r = new RangeBar(); 509 | r.$el.css({width: '100px'}).appendTo('body'); 510 | 511 | waitForAnimation(function() { 512 | move({ 513 | x: 50, 514 | y: r.$el.offset().top + r.$el.height() / 2 515 | }, r.$el); 516 | 517 | waitForAnimation(function() { 518 | t.ok( 519 | r.$el.contains('.elessar-phantom'), 520 | 'creates a phantom range' 521 | ); 522 | 523 | t.end(); 524 | }); 525 | }); 526 | }); 527 | 528 | t.test('hovering on a range', function(t) { 529 | var r = new RangeBar({values: [[50, 55]]}); 530 | r.$el.css({width: '100px'}).appendTo('body'); 531 | 532 | waitForAnimation(function() { 533 | move({ 534 | x: 50, 535 | y: r.$el.offset().top + r.$el.height() / 2 536 | }, r.$el.find('.elessar-range')); 537 | 538 | waitForAnimation(function() { 539 | t.ok( 540 | !r.$el.contains('.elessar-phantom'), 541 | 'doesn\'t create a phantom range' 542 | ); 543 | 544 | t.end(); 545 | }); 546 | }); 547 | }); 548 | 549 | t.test('clicking the phantom', function(t) { 550 | t.test('with no minSize', function(t) { 551 | var r = new RangeBar(); 552 | r.$el.css({width: '100px'}).appendTo('body'); 553 | move({ 554 | x: r.$el.offset().left + 52.5, 555 | y: r.$el.offset().top + r.$el.height() / 2 556 | }, r.$el); 557 | 558 | r.$el.find('.elessar-phantom').btnClick(); 559 | 560 | waitForAnimation(function() { 561 | t.rangebarValuesEqual( 562 | r.val(), 563 | [[50,55]], 564 | 'creates a new range' 565 | ); 566 | 567 | r.$el.find('.elessar-phantom').mouseup(); 568 | t.end(); 569 | }); 570 | }); 571 | 572 | t.test('after moving a little', function(t) { 573 | var r = new RangeBar(); 574 | r.$el.css({width: '100px'}).appendTo('body'); 575 | 576 | move({ 577 | x: r.$el.offset().left + 52.5, 578 | y: r.$el.offset().top + r.$el.height() / 2 579 | }, r.$el); 580 | 581 | waitForAnimation(function() { 582 | 583 | move({ 584 | x: r.$el.offset().left + 54.5, 585 | y: r.$el.offset().top + r.$el.height() / 2 586 | }, r.$el.find('.elessar-phantom')); 587 | 588 | waitForAnimation(function() { 589 | r.$el.find('.elessar-phantom').btnClick(); 590 | 591 | waitForAnimation(function() { 592 | t.rangebarValuesEqual( 593 | r.val(), 594 | [[52,57]], 595 | 'moved the phantom with the mouse' 596 | ); 597 | 598 | r.$el.find('.elessar-phantom').mouseup(); 599 | t.end(); 600 | }); 601 | }); 602 | }); 603 | }); 604 | 605 | t.test('fires change', function(t) { 606 | t.plan(1); 607 | var r = new RangeBar(); 608 | r.$el.css({width: '100px'}).appendTo('body'); 609 | move({ 610 | x: r.$el.offset().left + 52.5, 611 | y: r.$el.offset().top + r.$el.height() / 2 612 | }, r.$el); 613 | 614 | var timeout = setTimeout(function() { 615 | t.fail('timed out'); 616 | }, 2000); 617 | 618 | r.on('change', function() { 619 | clearTimeout(timeout); 620 | t.pass('fires an event'); 621 | }); 622 | 623 | r.$el.find('.elessar-phantom').btnClick(); 624 | 625 | waitForAnimation(function() { 626 | r.ranges[0].$el.mouseup(); 627 | }); 628 | }); 629 | 630 | t.test('with a minSize', function(t) { 631 | var r = new RangeBar({minSize: 10}); 632 | r.$el.css({width: '100px'}).appendTo('body'); 633 | move({ 634 | x: r.$el.offset().left + 55, 635 | y: r.$el.offset().top + r.$el.height() / 2 636 | }, r.$el); 637 | 638 | r.$el.find('.elessar-phantom').btnClick(); 639 | 640 | waitForAnimation(function() { 641 | t.rangebarValuesEqual( 642 | r.val(), 643 | [[50,60]], 644 | 'creates a new range of the minsize' 645 | ); 646 | 647 | r.$el.find('.elessar-phantom').mouseup(); 648 | t.end(); 649 | }); 650 | }); 651 | 652 | t.test('next to the end', function(t) { 653 | var r = new RangeBar(); 654 | r.$el.css({width: '100px'}).appendTo('body'); 655 | move({ 656 | x: r.$el.offset().left + 98, 657 | y: r.$el.offset().top + r.$el.height() / 2 658 | }, r.$el); 659 | 660 | r.$el.find('.elessar-phantom').btnClick(); 661 | 662 | waitForAnimation(function() { 663 | t.rangebarValuesEqual( 664 | r.val(), 665 | [[95,100]], 666 | 'creates a new range at the end' 667 | ); 668 | 669 | r.$el.find('.elessar-phantom').mouseup(); 670 | t.end(); 671 | }); 672 | }); 673 | 674 | t.test('next to the start', function(t) { 675 | var r = new RangeBar(); 676 | r.$el.css({width: '100px'}).appendTo('body'); 677 | move({ 678 | x: r.$el.offset().left + 2, 679 | y: r.$el.offset().top + r.$el.height() / 2 680 | }, r.$el); 681 | 682 | r.$el.find('.elessar-phantom').btnClick(); 683 | 684 | waitForAnimation(function() { 685 | t.rangebarValuesEqual( 686 | r.val(), 687 | [[0,5]], 688 | 'creates a new range at the start' 689 | ); 690 | 691 | r.$el.find('.elessar-phantom').mouseup(); 692 | t.end(); 693 | }); 694 | }); 695 | 696 | t.test('when available space is less than the minsize', function(t) { 697 | var r = new RangeBar({values: [[0, 10],[12, 22]], minSize: 10}); 698 | r.$el.css({width: '100px'}).appendTo('body'); 699 | move({ 700 | x: r.$el.offset().left + 11, 701 | y: r.$el.offset().top + r.$el.height() / 2 702 | }, r.$el); 703 | 704 | t.ok( 705 | !r.$el.contains('.elessar-phantom'), 706 | 'doesn\'t add a phantom' 707 | ); 708 | 709 | t.end(); 710 | }); 711 | 712 | t.test('when available space is smaller than default', function(t) { 713 | var r = new RangeBar({values: [[0, 10],[12, 22]]}); 714 | r.$el.css({width: '100px'}).appendTo('body'); 715 | move({ 716 | x: r.$el.offset().left + 11, 717 | y: r.$el.offset().top + r.$el.height() / 2 718 | }, r.$el); 719 | 720 | 721 | r.$el.find('.elessar-phantom').btnClick(); 722 | 723 | waitForAnimation(function() { 724 | t.rangebarValuesEqual( 725 | r.val(), 726 | [[0, 10],[10,12],[12, 22]], 727 | 'fills the available space' 728 | ); 729 | 730 | r.$el.find('.elessar-phantom').mouseup(); 731 | t.end(); 732 | }); 733 | }); 734 | 735 | 736 | t.end(); 737 | }); 738 | 739 | t.end(); 740 | }); 741 | 742 | t.test('vertical dragging', function(t) { 743 | t.test('downwards', function(t) { 744 | var r = new RangeBar({vertical: true, values: [[0, 10]]}); 745 | r.$el.css({height: '100px'}).appendTo('body'); 746 | waitForAnimation(function() { 747 | drag(r.ranges[0].$el, {y: 10, x: 0}, function() { 748 | t.rangebarValuesEqual(r.val(), [[10, 20]], 'dragging updates the value'); 749 | t.end(); 750 | }); 751 | }); 752 | }); 753 | 754 | t.test('upwards', function(t) { 755 | var r = new RangeBar({vertical: true, values: [[10, 20]]}); 756 | r.$el.css({height: '100px'}).appendTo('body'); 757 | waitForAnimation(function() { 758 | drag(r.ranges[0].$el, {y: -10, x: 0}, function() { 759 | t.rangebarValuesEqual(r.val(), [[0, 10]], 'dragging updates the value'); 760 | t.end(); 761 | }); 762 | }); 763 | }); 764 | 765 | t.test('to collide with end', function(t) { 766 | var r = new RangeBar({vertical: true, values: [[85, 95]]}); 767 | r.$el.css({height: '100px'}).appendTo('body'); 768 | waitForAnimation(function() { 769 | drag(r.ranges[0].$el, {y: 10, x: 0, step: true}, function() { 770 | t.rangebarValuesEqual(r.val(), [[90, 100]], 'dragging updates the value'); 771 | t.end(); 772 | }); 773 | }); 774 | }); 775 | 776 | t.test('to collide with start', function(t) { 777 | var r = new RangeBar({vertical: true, values: [[5, 15]]}); 778 | r.$el.css({height: '100px'}).appendTo('body'); 779 | waitForAnimation(function() { 780 | drag(r.ranges[0].$el, {y: -10, x: 0, step: true}, function() { 781 | t.rangebarValuesEqual(r.val(), [[0, 10]], 'dragging updates the value'); 782 | t.end(); 783 | }); 784 | }); 785 | }); 786 | 787 | t.test('to collide with another range upwards', function(t) { 788 | var r = new RangeBar({vertical: true, values: [[5, 15], [20, 30]]}); 789 | r.$el.css({height: '100px'}).appendTo('body'); 790 | waitForAnimation(function() { 791 | drag(r.ranges[1].$el, {y: -10, x: 0, step: true}, function() { 792 | t.rangebarValuesEqual(r.val(), [[5, 15],[15,25]], 'dragging updates the value'); 793 | t.end(); 794 | }); 795 | }); 796 | }); 797 | 798 | t.test('to collide with another range downwards', function(t) { 799 | var r = new RangeBar({vertical: true, values: [[5, 15], [20, 30]]}); 800 | r.$el.css({height: '100px'}).appendTo('body'); 801 | waitForAnimation(function() { 802 | drag(r.ranges[0].$el, {y: 10, x: 0, step: true}, function() { 803 | t.rangebarValuesEqual(r.val(), [[10, 20],[20,30]], 'dragging updates the value'); 804 | t.end(); 805 | }); 806 | }); 807 | }); 808 | 809 | t.end(); 810 | }); 811 | 812 | t.test('bottom resizing', function(t) { 813 | t.test('downwards', function(t) { 814 | var r = new RangeBar({values: [[0, 10]], vertical: true}); 815 | r.$el.css({height: '100px'}).appendTo('body'); 816 | waitForAnimation(function() { 817 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {y: 10, x: 0, bottomEdge: true}, function() { 818 | t.rangebarValuesEqual(r.val(), [[0, 20]], 'dragging bottom handle updates the value'); 819 | t.end(); 820 | }); 821 | }); 822 | }); 823 | 824 | t.test('upwards', function(t) { 825 | var r = new RangeBar({values: [[0, 20]], vertical: true}); 826 | r.$el.css({height: '100px'}).appendTo('body'); 827 | waitForAnimation(function() { 828 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {y: -10, x: 0, bottomEdge: true}, function() { 829 | t.rangebarValuesEqual(r.val(), [[0, 10]], 'dragging bottom handle updates the value'); 830 | t.end(); 831 | }); 832 | }); 833 | }); 834 | 835 | t.test('beyond the end', function(t) { 836 | var r = new RangeBar({values: [[85, 95]], vertical: true}); 837 | r.$el.css({height: '100px'}).appendTo('body'); 838 | waitForAnimation(function() { 839 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {y: 10, x: 0, bottomEdge: true}, function() { 840 | t.rangebarValuesEqual(r.val(), [[85, 100]], 'dragging bottom handle updates the value'); 841 | t.end(); 842 | }); 843 | }); 844 | }); 845 | 846 | t.test('to overlap another range', function(t) { 847 | var r = new RangeBar({values: [[0, 10], [15, 25]], vertical: true}); 848 | r.$el.css({height: '100px'}).appendTo('body'); 849 | waitForAnimation(function() { 850 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {y: 10, x: 0, bottomEdge: true}, function() { 851 | t.rangebarValuesEqual(r.val(), [[0, 15], [15, 25]], 'dragging bottom handle updates the value'); 852 | t.end(); 853 | }); 854 | }); 855 | }); 856 | 857 | t.test('beyond the start of the range resizes upwards', function(t) { 858 | var r = new RangeBar({values: [[20, 30]], vertical: true}); 859 | r.$el.css({height: '100px'}).appendTo('body'); 860 | waitForAnimation(function() { 861 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {y: -20, x: 0, bottomEdge: true, step: true}, function() { 862 | t.rangebarValuesEqual(r.val(), [[10, 20]], 'dragging bottom handle updates the value'); 863 | t.end(); 864 | }); 865 | }); 866 | }); 867 | 868 | t.test('doesn\'t resize below minimum size', function(t) { 869 | var r = new RangeBar({values: [[20, 40]], minSize: 10, vertical: true}); 870 | r.$el.css({height: '100px'}).appendTo('body'); 871 | waitForAnimation(function() { 872 | drag(r.ranges[0].$el.find('.elessar-handle:last-child'), {y: -15, x: 0, bottomEdge: true, step: true}, function() { 873 | t.rangebarValuesEqual(r.val(), [[20, 30]], 'dragging bottom handle updates the value'); 874 | t.end(); 875 | }); 876 | }); 877 | }); 878 | 879 | t.end(); 880 | }); 881 | 882 | t.test('top resizing', function(t) { 883 | t.test('downwards', function(t) { 884 | var r = new RangeBar({values: [[0, 20]], vertical: true}); 885 | r.$el.css({height: '100px'}).appendTo('body'); 886 | waitForAnimation(function() { 887 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {y: 10, x: 0}, function() { 888 | t.rangebarValuesEqual(r.val(), [[10, 20]], 'dragging top handle updates the value'); 889 | t.end(); 890 | }); 891 | }); 892 | }); 893 | 894 | t.test('to the top', function(t) { 895 | var r = new RangeBar({values: [[20, 30]], vertical: true}); 896 | r.$el.css({height: '100px'}).appendTo('body'); 897 | waitForAnimation(function() { 898 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {y: -10, x: 0}, function() { 899 | t.rangebarValuesEqual(r.val(), [[10, 30]], 'dragging top handle updates the value'); 900 | t.end(); 901 | }); 902 | }); 903 | }); 904 | 905 | t.test('beyond the end', function(t) { 906 | var r = new RangeBar({values: [[5, 15]], vertical: true}); 907 | r.$el.css({height: '100px'}).appendTo('body'); 908 | waitForAnimation(function() { 909 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {y: -10, x: 0}, function() { 910 | t.rangebarValuesEqual(r.val(), [[0, 15]], 'dragging top handle updates the value'); 911 | t.end(); 912 | }); 913 | }); 914 | }); 915 | 916 | t.test('to overlap another range', function(t) { 917 | var r = new RangeBar({values: [[0, 10], [15, 25]], vertical: true}); 918 | r.$el.css({height: '100px'}).appendTo('body'); 919 | waitForAnimation(function() { 920 | drag(r.ranges[1].$el.find('.elessar-handle:first-child'), {y: -10, x: 0}, function() { 921 | t.rangebarValuesEqual(r.val(), [[0, 10], [10, 25]], 'dragging top handle updates the value'); 922 | t.end(); 923 | }); 924 | }); 925 | }); 926 | 927 | t.test('beyond the end of the range resizes right', function(t) { 928 | var r = new RangeBar({values: [[20, 30]], vertical: true}); 929 | r.$el.css({height: '100px'}).appendTo('body'); 930 | waitForAnimation(function() { 931 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {y: 20, x: 0, step: true}, function() { 932 | t.rangebarValuesEqual(r.val(), [[30, 40]], 'dragging top handle updates the value'); 933 | t.end(); 934 | }); 935 | }); 936 | }); 937 | 938 | t.test('doesn\'t resize below minimum size', function(t) { 939 | var r = new RangeBar({values: [[20, 40]], minSize: 10, vertical: true}); 940 | r.$el.css({height: '100px'}).appendTo('body'); 941 | waitForAnimation(function() { 942 | drag(r.ranges[0].$el.find('.elessar-handle:first-child'), {y: 15, x: 0, step: true}, function() { 943 | t.rangebarValuesEqual(r.val(), [[30, 40]], 'dragging top handle updates the value'); 944 | t.end(); 945 | }); 946 | }); 947 | }); 948 | 949 | t.end(); 950 | }); 951 | 952 | t.test('marks', function(t) { 953 | t.test('behave like labels with count', function(t) { 954 | var r = new RangeBar({ 955 | bgMark: { 956 | count: 5 957 | } 958 | }); 959 | r.$el.css({width: '100px'}).appendTo('body'); 960 | 961 | t.equals(r.$el.find('.elessar-label').length, 5, 'adds `count` marks'); 962 | 963 | waitForAnimation(function() { 964 | r.$el.find('.elessar-label').each(function(i) { 965 | t.floatEqual($(this).offset().left - r.$el.offset().left, i * 20); 966 | t.floatEqual(parseFloat($(this).text()), i * 20); 967 | }); 968 | 969 | t.end(); 970 | }); 971 | }); 972 | 973 | t.test('labels falls back to marks', function(t) { 974 | var r = new RangeBar({ 975 | bgLabels: 5 976 | }); 977 | r.$el.css({width: '100px'}).appendTo('body'); 978 | 979 | t.equals(r.$el.find('.elessar-label').length, 5, 'adds `count` marks'); 980 | 981 | waitForAnimation(function() { 982 | r.$el.find('.elessar-label').each(function(i) { 983 | t.floatEqual($(this).offset().left - r.$el.offset().left, i * 20); 984 | t.floatEqual(parseFloat($(this).text()), i * 20); 985 | }); 986 | 987 | t.end(); 988 | }); 989 | }); 990 | 991 | t.test('interval', function(t) { 992 | var r = new RangeBar({ 993 | bgMark: { interval: 30 } 994 | }); 995 | r.$el.css({width: '100px'}).appendTo('body'); 996 | 997 | t.equals(r.$el.find('.elessar-label').length, 4, 'adds `(max - min)/interval` marks'); 998 | 999 | waitForAnimation(function() { 1000 | r.$el.find('.elessar-label').each(function(i) { 1001 | t.floatEqual($(this).offset().left - r.$el.offset().left, i * 30); 1002 | t.floatEqual(parseFloat($(this).text()), i * 30); 1003 | }); 1004 | 1005 | t.end(); 1006 | }); 1007 | }); 1008 | 1009 | t.test('string label', function(t) { 1010 | var r = new RangeBar({ 1011 | bgMark: { 1012 | count: 5, 1013 | label: 'foo' 1014 | } 1015 | }); 1016 | 1017 | r.$el.find('.elessar-label').each(function(i) { 1018 | t.equal($(this).text(), 'foo'); 1019 | }); 1020 | 1021 | t.end(); 1022 | }); 1023 | 1024 | t.test('function label', function(t) { 1025 | var r = new RangeBar({ 1026 | bgMark: { 1027 | count: 5, 1028 | label: function(val) { 1029 | return 'foo ' + val 1030 | } 1031 | } 1032 | }); 1033 | 1034 | r.$el.find('.elessar-label').each(function(i) { 1035 | t.equal($(this).text(), 'foo ' + (i * 20)); 1036 | }); 1037 | 1038 | t.end(); 1039 | }); 1040 | 1041 | 1042 | 1043 | t.end(); 1044 | }); 1045 | 1046 | t.end(); 1047 | }); 1048 | 1049 | --------------------------------------------------------------------------------