├── .gitignore ├── images └── range-picker.png ├── package.json ├── dist ├── css │ ├── range-picker.min.css │ └── range-picker.css └── js │ ├── range_picker.min.js │ ├── range_picker.min.js.map │ └── range_picker.js ├── bower.json ├── sample ├── sample.html └── sample.js ├── LICENSE ├── .jshintrc ├── css └── range-picker.css ├── test ├── range_picker_test.html └── spec │ └── rangepickerSpec.js ├── Gruntfile.js ├── README.md └── src └── range_picker.js /.gitignore: -------------------------------------------------------------------------------- 1 | bower_components 2 | node_modules 3 | *.swp 4 | *.~ 5 | .bowerrc 6 | -------------------------------------------------------------------------------- /images/range-picker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhangtasdq/range-picker/HEAD/images/range-picker.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "range-picker", 3 | "version": "1.0.0", 4 | "description": "simple range picker", 5 | "main": "dist/js/range_picker.js", 6 | "scripts": { 7 | "test": "grunt test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git@git.oschina.net:syjefbz/range-picker.git" 12 | }, 13 | "keywords": [ 14 | "range", 15 | "picker" 16 | ], 17 | "author": "zhang", 18 | "license": "MIT", 19 | "dependencies": { 20 | "grunt": "^0.4.5", 21 | "grunt-contrib-concat": "^1.0.0", 22 | "grunt-contrib-copy": "^1.0.0", 23 | "grunt-contrib-cssmin": "^1.0.0", 24 | "grunt-contrib-jshint": "^1.0.0", 25 | "grunt-contrib-uglify": "^1.0.0", 26 | "grunt-mocha": "^0.4.15", 27 | "jshint-stylish": "^2.1.0" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /dist/css/range-picker.min.css: -------------------------------------------------------------------------------- 1 | .range-picker,.range-picker-wrapper{position:relative}.range-picker .not-select-process{display:block;height:5px;background-color:#d1d6d0;border-top:1px solid #afb3bb;border-radius:3px}.range-picker .process{position:absolute;height:5px;top:0;left:0;background-color:#20b426;border-radius:3px}.range-picker .label{display:inline-block;padding:2px 4px;border-radius:5px;cursor:pointer}.range-picker .range-label{margin-top:4px;background-color:#e1e4e9;color:#9ba0b9}.range-picker .end-label{float:right}.range-picker .select-label{display:block;position:absolute;z-index:0;bottom:100%;font-family:monospace;margin-bottom:4px;white-space:nowrap;background-color:#20b426;color:#fff}.range-picker .select-label:after{content:"";position:absolute;top:100%;left:50%;width:0;height:0;margin-left:-4px;border:4px solid transparent;border-top-color:#20b426} -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "range-picker", 3 | "authors": [ 4 | "zhang " 5 | ], 6 | "description": "simple range picker", 7 | "main": [ 8 | "dist/js/range_picker.js", 9 | "dist/css/range-picker.css" 10 | ], 11 | "repository": { 12 | "type": "git", 13 | "url": "git@git.oschina.net:syjefbz/range-picker.git" 14 | }, 15 | "moduleType": [ 16 | "globals", 17 | "amd" 18 | ], 19 | "keywords": [ 20 | "range", 21 | "picker" 22 | ], 23 | "license": "MIT", 24 | "homepage": "https://git.oschina.net/syjefbz/range-picker.git", 25 | "ignore": [ 26 | "**/.*", 27 | "node_modules", 28 | "bower_components", 29 | "test", 30 | "css/", 31 | "images", 32 | "sample", 33 | "src", 34 | "Gruntfile.js", 35 | "package.json" 36 | ], 37 | "dependencies": { 38 | "jquery": ">=1.7.0" 39 | }, 40 | "devDependencies": { 41 | "mocha": "^2.4.5", 42 | "sinonjs": "^1.17.1", 43 | "chai": "^3.5.0", 44 | "jquery-simulate-ext": "^1.3.0" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /sample/sample.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | range picker sample 7 | 8 | 9 | 10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 随便syjefbz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "devel" : true, 3 | "camelcase" : true, 4 | "curly" : true, 5 | "eqeqeq" : true, 6 | "indent" : 4, 7 | "noarg" : true, 8 | "noempty" : true, 9 | "undef" : true, 10 | "unused" : true, 11 | "strict" : true, 12 | "evil" : false, 13 | "globalstrict" : false, 14 | "loopfunc" : false, 15 | "notypeof" : false, 16 | "sub" : false, 17 | "maxlen" : 100, 18 | "shadow" : false, 19 | "smarttabs" : false, 20 | "proto" : false, 21 | "multistr" : false, 22 | "laxbreak" : false, 23 | "laxcomma" : false, 24 | "maxstatements" : 15, 25 | "node" : true, 26 | "browser" : true, 27 | 28 | "predef": [ 29 | "$", 30 | "jQuery", 31 | "define", 32 | "describe", 33 | "expect", 34 | "it", 35 | "before", 36 | "beforeEach", 37 | "afterEach", 38 | "after", 39 | "sinon" 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /css/range-picker.css: -------------------------------------------------------------------------------- 1 | .range-picker-wrapper { 2 | position: relative; 3 | } 4 | 5 | .range-picker { 6 | position: relative; 7 | } 8 | 9 | .range-picker .not-select-process { 10 | display: block; 11 | height: 5px; 12 | background-color: #d1d6d0; 13 | border-top: 1px solid #afb3bb; 14 | border-radius: 3px; 15 | } 16 | 17 | .range-picker .process { 18 | position: absolute; 19 | height: 5px; 20 | top: 0; 21 | left: 0; 22 | background-color: #20b426; 23 | border-radius: 3px; 24 | } 25 | 26 | .range-picker .label { 27 | display: inline-block; 28 | padding: 2px 4px; 29 | border-radius: 5px; 30 | cursor: pointer; 31 | } 32 | 33 | .range-picker .range-label { 34 | margin-top: 4px; 35 | background-color: #e1e4e9; 36 | color: #9ba0b9; 37 | } 38 | 39 | .range-picker .end-label { 40 | float: right; 41 | } 42 | 43 | .range-picker .select-label { 44 | display: block; 45 | position: absolute; 46 | z-index: 0; 47 | bottom: 100%; 48 | font-family: monospace; 49 | margin-bottom: 4px; 50 | white-space: nowrap; 51 | background-color: #20b426; 52 | color: #fff; 53 | } 54 | 55 | .range-picker .select-label:after { 56 | content: ""; 57 | position: absolute; 58 | top: 100%; 59 | left: 50%; 60 | width: 0; 61 | height: 0; 62 | margin-left: -4px; 63 | border: 4px solid transparent; 64 | border-top-color: #20b426; 65 | } 66 | -------------------------------------------------------------------------------- /dist/css/range-picker.css: -------------------------------------------------------------------------------- 1 | .range-picker-wrapper { 2 | position: relative; 3 | } 4 | 5 | .range-picker { 6 | position: relative; 7 | } 8 | 9 | .range-picker .not-select-process { 10 | display: block; 11 | height: 5px; 12 | background-color: #d1d6d0; 13 | border-top: 1px solid #afb3bb; 14 | border-radius: 3px; 15 | } 16 | 17 | .range-picker .process { 18 | position: absolute; 19 | height: 5px; 20 | top: 0; 21 | left: 0; 22 | background-color: #20b426; 23 | border-radius: 3px; 24 | } 25 | 26 | .range-picker .label { 27 | display: inline-block; 28 | padding: 2px 4px; 29 | border-radius: 5px; 30 | cursor: pointer; 31 | } 32 | 33 | .range-picker .range-label { 34 | margin-top: 4px; 35 | background-color: #e1e4e9; 36 | color: #9ba0b9; 37 | } 38 | 39 | .range-picker .end-label { 40 | float: right; 41 | } 42 | 43 | .range-picker .select-label { 44 | display: block; 45 | position: absolute; 46 | z-index: 0; 47 | bottom: 100%; 48 | font-family: monospace; 49 | margin-bottom: 4px; 50 | white-space: nowrap; 51 | background-color: #20b426; 52 | color: #fff; 53 | } 54 | 55 | .range-picker .select-label:after { 56 | content: ""; 57 | position: absolute; 58 | top: 100%; 59 | left: 50%; 60 | width: 0; 61 | height: 0; 62 | margin-left: -4px; 63 | border: 4px solid transparent; 64 | border-top-color: #20b426; 65 | } 66 | -------------------------------------------------------------------------------- /test/range_picker_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | RangePicker test 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |
15 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* global module */ 2 | 3 | module.exports = function(grunt) { 4 | "use strict"; 5 | 6 | grunt.initConfig({ 7 | pkg: grunt.file.readJSON("package.json"), 8 | 9 | jshint: { 10 | options: { 11 | jshintrc: true, 12 | reporter: require("jshint-stylish") 13 | }, 14 | 15 | check: { 16 | files: { 17 | src: ["src/**/*.js"] 18 | } 19 | } 20 | }, 21 | 22 | concat: { 23 | dist: { 24 | src: ["src/range_picker.js"], 25 | dest: "dist/js/range_picker.js", 26 | options: { 27 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 28 | '<%= grunt.template.today("yyyy-mm-dd") %> */\n' 29 | } 30 | } 31 | }, 32 | 33 | uglify: { 34 | compress: { 35 | options: { 36 | sourceMap: true, 37 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 38 | '<%= grunt.template.today("yyyy-mm-dd") %> */' 39 | }, 40 | files: { 41 | "dist/js/range_picker.min.js" : ["dist/js/range_picker.js"] 42 | } 43 | } 44 | }, 45 | 46 | cssmin: { 47 | target: { 48 | files: { 49 | "dist/css/range-picker.min.css" : ["css/range-picker.css"] 50 | } 51 | } 52 | }, 53 | copy: { 54 | main: { 55 | src: "css/range-picker.css", 56 | dest: "dist/css/range-picker.css" 57 | } 58 | }, 59 | 60 | mocha: { 61 | 62 | test: { 63 | options: { 64 | run: true, 65 | log: true, 66 | logErrors: true, 67 | reporter: "Nyan", 68 | }, 69 | src: ["test/**/*.html"] 70 | } 71 | } 72 | 73 | }); 74 | 75 | grunt.loadNpmTasks("grunt-contrib-jshint"); 76 | grunt.loadNpmTasks("grunt-contrib-concat"); 77 | grunt.loadNpmTasks("grunt-contrib-uglify"); 78 | grunt.loadNpmTasks("grunt-contrib-cssmin"); 79 | grunt.loadNpmTasks("grunt-contrib-copy"); 80 | grunt.loadNpmTasks("grunt-mocha"); 81 | 82 | grunt.registerTask("test", ["mocha:test"]); 83 | grunt.registerTask("compile", ["jshint:check", "mocha:test", 84 | "concat:dist", "uglify:compress", "cssmin", "copy"]); 85 | grunt.registerTask("default", ["compile"]); 86 | }; 87 | -------------------------------------------------------------------------------- /sample/sample.js: -------------------------------------------------------------------------------- 1 | ;(function() { 2 | "use strict"; 3 | 4 | function dateFormat(date, fmt) { 5 | var o = { 6 | "M+": date.getMonth() + 1, 7 | "d+": date.getDate(), 8 | }; 9 | if (/(y+)/.test(fmt)){ 10 | fmt = fmt.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length)); 11 | } 12 | for (var k in o) { 13 | if (new RegExp("(" + k + ")").test(fmt)){ 14 | fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); 15 | } 16 | } 17 | return fmt; 18 | } 19 | 20 | var startDate = new Date("2013/11/12"), 21 | endDate = new Date("2013/12/22"), 22 | offset = endDate - startDate; 23 | 24 | $("#date_range").rangepicker({ 25 | startValue: dateFormat(startDate, "yyyy/MM/dd"), 26 | endValue: dateFormat(endDate, "yyyy/MM/dd"), 27 | translateSelectLabel: function(currentPosition, totalPosition) { 28 | var timeOffset = offset * ( currentPosition / totalPosition); 29 | var date = new Date(+startDate + parseInt(timeOffset)); 30 | return dateFormat(date, "yyyy/MM/dd"); 31 | } 32 | }); 33 | 34 | window.hello = $("#double_date_range").rangepicker({ 35 | type: "double", 36 | startValue: dateFormat(startDate, "yyyy/MM/dd"), 37 | endValue: dateFormat(endDate, "yyyy/MM/dd"), 38 | translateSelectLabel: function(currentPosition, totalPosition) { 39 | var timeOffset = offset * ( currentPosition / totalPosition); 40 | var date = new Date(+startDate + parseInt(timeOffset)); 41 | return dateFormat(date, "yyyy/MM/dd"); 42 | } 43 | }); 44 | 45 | $("#number_range").rangepicker({ 46 | startValue: 0, 47 | endValue: 100, 48 | translateSelectLabel: function(currentPosition, totalPosition) { 49 | return parseInt(100 * (currentPosition / totalPosition)); 50 | } 51 | }); 52 | 53 | $("#double_number_range").rangepicker({ 54 | type: "double", 55 | startValue: 0, 56 | endValue: 100, 57 | translateSelectLabel: function(currentPosition, totalPosition) { 58 | return parseInt(100 * (currentPosition / totalPosition)); 59 | } 60 | }); 61 | 62 | var week = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"]; 63 | $("#week_range").rangepicker({ 64 | type: "double", 65 | startValue: week[0], 66 | endValue: week[6], 67 | translateSelectLabel: function(currentPosition, totalPosition) { 68 | var index = parseInt(6 * (currentPosition / totalPosition)); 69 | return week[index]; 70 | } 71 | }); 72 | 73 | var month = ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]; 74 | $("#month_range").rangepicker({ 75 | type: "double", 76 | startValue: month[0], 77 | endValue: month[11], 78 | translateSelectLabel: function(currentPosition, totalPosition) { 79 | var index = parseInt(11 * (currentPosition / totalPosition)); 80 | return month[index]; 81 | } 82 | }); 83 | 84 | }()); 85 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #range-picker 2 | 3 | a range picker of JQuery plugins 4 | 5 | Screenshot 6 | --- 7 | ![date-picker](./images/range-picker.png) 8 | 9 | Examples 10 | --- 11 | 1. see `sample` folder 12 | 2. [jsfiddle](https://jsfiddle.net/cqmyg/me1dmz9e/6/) 13 | 14 | Browser Support 15 | --- 16 | 1. chrome 17 | 2. firefox 18 | 3. IE8+ 19 | 20 | Requirements 21 | --- 22 | 1. JQuery 1.7+ 23 | 24 | Usage 25 | --- 26 | 27 | ###Install 28 | 29 | ######download 30 | 31 | [download range-picker](http://git.oschina.net/syjefbz/range-picker/repository/archive/master) 32 | 33 | ######bower 34 | 35 | ```shell 36 | bower install --save range-picker 37 | ``` 38 | 39 | ###Import css 40 | 41 | ```html 42 | 43 | 44 | ``` 45 | 46 | ###Import javascript 47 | 48 | ```html 49 | 50 | 51 | 52 | ``` 53 | ###Call 54 | ```javascript 55 | $("#date_picker").rangepicker(); 56 | ``` 57 | 58 | Options 59 | --- 60 | 61 | ####startValue 62 | 63 | default: `none` 64 | 65 | require: `true` 66 | 67 | the left label. eg: "2016/01/03" 68 | 69 | ####endValue 70 | 71 | default: `none` 72 | 73 | require: `true` 74 | 75 | the right label. eg: "2016/03/12" 76 | 77 | ####type 78 | 79 | type: `String` 80 | 81 | default: `single` 82 | 83 | require: `false` 84 | 85 | Choose picker type, could be `single` - for one cursor, or `double` for two cursors 86 | 87 | ######example 88 | ```js 89 | $("#number_range").rangepicker({ 90 | type: "double", 91 | startValue: 0, 92 | endValue: 100, 93 | translateSelectLabel: function(currentPosition, totalPosition) { 94 | return parseInt(100 * (currentPosition / totalPosition)); 95 | } 96 | }); 97 | ``` 98 | 99 | ####translateSelectLabel 100 | 101 | type: `Function` 102 | 103 | default: `none` 104 | 105 | require: `true` 106 | 107 | ######parameter 108 | 1. `currentPosition` cursor position 109 | 2. `totalWidth` the width of process bar 110 | 111 | get the text for cursor 112 | 113 | 114 | ######example 115 | ```js 116 | $("#number_range").rangepicker({ 117 | startValue: 0, 118 | endValue: 100, 119 | translateSelectLabel: function(currentPosition, totalPosition) { 120 | return parseInt(100 * (currentPosition / totalPosition)); 121 | } 122 | }); 123 | ``` 124 | 125 | Function 126 | --- 127 | 128 | ####getSelectValue 129 | 130 | get selected range 131 | 132 | ###updatePosition 133 | 134 | ######parameter 135 | 1. `endPosition` the right cursor position 136 | 2. `startPosition` the left cursor position 137 | 138 | set the cursor position 139 | 140 | ######example 141 | 142 | ```js 143 | var rangePicker = $("#number_range").rangepicker({ 144 | startValue: 0, 145 | endValue: 100, 146 | translateSelectLabel: function(currentPosition, totalPosition) { 147 | return parseInt(100 * (currentPosition / totalPosition)); 148 | } 149 | }); 150 | 151 | rangePicker.updatePosition("-50px", "10%"); 152 | // rangePicker.updatePosition("-50px"); 153 | // rangePicker.updatePosition("-50px", "50px"); 154 | // rangePicker.updatePosition("90%", "10%"); 155 | ``` 156 | 157 | 158 | Build 159 | --- 160 | 161 | First, get a copy of the git repo by running: 162 | 163 | ```shell 164 | git clone https://git.oschina.net/syjefbz/range-picker.git 165 | ``` 166 | 167 | Enter the directory and install the dependencies: 168 | 169 | ```shell 170 | cd range-picker && npm install && bower install 171 | ``` 172 | 173 | Build 174 | 175 | ```shell 176 | grunt 177 | ``` 178 | 179 | Test 180 | 181 | ```shell 182 | grunt test 183 | ``` 184 | -------------------------------------------------------------------------------- /dist/js/range_picker.min.js: -------------------------------------------------------------------------------- 1 | /*! range-picker - v0.0.3 - 2016-03-22 */ 2 | !function(a){"use strict";"function"==typeof define&&define.amd?define(["jquery"],a):a(jQuery)}(function(a){"use strict";function b(a){return"undefined"==typeof a}function c(a,b){return a.replace(g,function(a,c){return b[c]})}function d(a,c){if(b(c.startValue)||b(c.endValue))throw new Error("startValue and endValue is need");if(b(c.translateSelectLabel))throw new Error(" RangePicker: translateSelectLabel is need");this.__init(a,c)}function e(a){this.__init(a)}function f(a){this.__init(a)}var g=/<%=\s*(\w+)\s*%>/g;d.prototype={constructor:d,__defaultOptions:{type:"single"},__template:"
<%= startValue %><%= endValue %>
",__init:function(b,c){this.__options=a.extend({},this.__defaultOptions,c),this.__$containerElement=b,this.__render(),this.__$rangepickerElement=this.__$containerElement.find(".range-picker"),this.__addWidget(),this.__setContainerToWrapperWidget(),this.__setCursorInitialPosition(),this.__updateProcessBarView()},__render:function(){var a={startValue:this.__options.startValue,endValue:this.__options.endValue},b=c(this.__template,a);this.__$containerElement.html(b)},__addWidget:function(){var b=a.proxy(this.__handleLabelPositionChange,this);this.__selectCursors=[],this.__selectCursors.push(new e({positionChange:b})),"double"===this.__options.type&&this.__selectCursors.push(new e({positionChange:b})),this.__processBar=new f,this.__$rangepickerElement.append(this.__processBar.getJQueryElement());for(var c=0;ce&&(e=-c[1].getJQueryElement().position().top),a.css("paddingTop",e+"px");var f=c[0].getJQueryElement(),g=f.outerWidth()/2,h=null;c[0].render(this.__options.translateSelectLabel(0,d)),h=f.outerWidth()/2,c[0].render(this.__options.translateSelectLabel(d,d)),a.css({paddingLeft:h+"px",paddingRight:g+"px"})},__handleLabelPositionChange:function(a){this.__updateView(a.left)},__updateView:function(){this.__updateCursorView(),this.__updateProcessBarView()},__updateCursorView:function(){for(var a=0,b="",c=null;aa.end?(a.start=a.end,a.startLabel=a.endLabel,a.end=c.left,a.endLabel=c.positionLabel):(a.start=c.left,a.startLabel=c.positionLabel)),a},__formatPositionValue:function(a,b){var c=this.__$rangepickerElement.width(),d=0;return a=a.replace(/\s+/,""),d="%"===a[a.length-1]?c*parseInt(a,10)/100:b+parseInt(a,10)},getSelectValue:function(){var a=this.__getCursorPosition();return a.totalWidth=this.__$rangepickerElement.width(),a},updatePosition:function(a,c){var d=this.__selectCursors;d[0].updateArrowPosition(this.__formatPositionValue(a,d[0].getArrowPosition().left)),b(d[1])||b(c)||d[1].updateArrowPosition(this.__formatPositionValue(c,d[1].getArrowPosition().left)),this.__updateView()}},e.prototype={constructor:e,__defaultOptions:{positionChange:a.loop},__template:"",__init:function(b){this.__options=a.extend({},this.__defaultOptions,b),this.__$element=a(this.__template),this.__bindDragEventHandler()},__bindDragEventHandler:function(){var b=this;this.__$element.on("mousedown",function(b){this.__rangepicker={isMouseDown:!0,mouseStartX:b.clientX,previousMoveDistance:0},a(this).css("zIndex",1e3)}).on("mouseup",function(){this.__rangepicker=null,a(this).css("zIndex",1)}).on("mousemove",function(a){this.__rangepicker&&this.__rangepicker.isMouseDown&&b.__handleDragEvent(a.clientX,this.__rangepicker)}).on("mouseout",function(){a(this).css("zIndex",1),this.__rangepicker=null})},__handleDragEvent:function(a,b){var c=a-b.mouseStartX-b.previousMoveDistance;b.previousMoveDistance=a-b.mouseStartX;var d=this.__calculatePosition(c);this.updateArrowPosition(d),this.__options.positionChange(this.getArrowPosition(),this.__$element)},__calculatePosition:function(a){var b=this.__arrowPosition+a;return b>this.__totalWidth?b=this.__totalWidth:0>b&&(b=0),b},__updatePosition:function(a){for(var b in a)a.hasOwnProperty(b)&&this.__$element.css(b,a[b]+"px")},render:function(a){this.__$element.text(a)},updateArrowPosition:function(a){this.__arrowPosition=a,this.__updatePosition({left:a-this.__$element.outerWidth()/2})},getJQueryElement:function(){return this.__$element},getArrowPosition:function(){return{left:this.__arrowPosition,positionLabel:this.__$element.text()}},setTotalWidth:function(a){this.__totalWidth=a}},f.prototype={constructor:f,__template:"",__init:function(){this.__$element=a(this.__template)},updatePosition:function(a){for(var b in a)a.hasOwnProperty(b)&&this.__$element.css(b,a[b]+"px")},getJQueryElement:function(){return this.__$element}},a.fn.rangepicker=function(a){return new d(this,a)}}); 3 | //# sourceMappingURL=range_picker.min.js.map -------------------------------------------------------------------------------- /dist/js/range_picker.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["range_picker.js"],"names":["factory","define","amd","jQuery","$","isUndefined","target","replace","str","value","STR_REPLACE_REG","match","key","RangePicker","container","options","startValue","endValue","Error","translateSelectLabel","this","__init","Cursor","ProcessBar","prototype","constructor","__defaultOptions","type","__template","__options","extend","__$containerElement","__render","__$rangepickerElement","find","__addWidget","__setContainerToWrapperWidget","__setCursorInitialPosition","__updateProcessBarView","templateValue","viewStr","html","positionChangeCallback","proxy","__handleLabelPositionChange","__selectCursors","push","positionChange","__processBar","append","getJQueryElement","i","length","__setWidgetInitialValue","totalWidth","width","render","cursor","cursors","updateArrowPosition","setTotalWidth","wrapperElement","cursorHeight","position","css","endCursorElement","paddingRight","outerWidth","paddingLeft","__updateView","left","__updateCursorView","labelText","getArrowPosition","cursorPosition","__getCursorPosition","processBarPosition","start","right","end","updatePosition","startLabel","tmpPosition","endLabel","positionLabel","__formatPositionValue","cursorLeftPosition","offset","parseInt","getSelectValue","loop","__$element","__bindDragEventHandler","self","on","event","__rangepicker","isMouseDown","mouseStartX","clientX","previousMoveDistance","__handleDragEvent","elementData","distance","__calculatePosition","newPosition","__arrowPosition","__totalWidth","__updatePosition","hasOwnProperty","textValue","text","fn","rangepicker"],"mappings":"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","file":"range_picker.min.js"} -------------------------------------------------------------------------------- /src/range_picker.js: -------------------------------------------------------------------------------- 1 | ;(function(factory) { 2 | "use strict"; 3 | 4 | if (typeof define === "function" && define.amd) { 5 | // amd 6 | define(["jquery"], factory); 7 | } else { 8 | factory(jQuery); 9 | } 10 | }(function($) { 11 | "use strict"; 12 | 13 | var STR_REPLACE_REG = /<%=\s*(\w+)\s*%>/g; 14 | 15 | function isUndefined(target) { 16 | return typeof target === "undefined"; 17 | } 18 | 19 | function replace(str, value) { 20 | return str.replace(STR_REPLACE_REG, function(match, key) { 21 | return value[key]; 22 | }); 23 | } 24 | 25 | function RangePicker(container, options) { 26 | if (isUndefined(options.startValue) || isUndefined(options.endValue)) { 27 | throw new Error("startValue and endValue is need"); 28 | } 29 | 30 | if(isUndefined(options.translateSelectLabel)) { 31 | throw new Error(" RangePicker: translateSelectLabel is need"); 32 | } 33 | this.__init(container, options); 34 | } 35 | 36 | RangePicker.prototype = { 37 | constructor: RangePicker, 38 | __defaultOptions: { 39 | type: "single" 40 | }, 41 | __template: "
" + 42 | "
" + 43 | "" + 44 | "<%= startValue %>" + 45 | "<%= endValue %>" + 46 | "
" + 47 | "
", 48 | __init: function(container, options) { 49 | this.__options = $.extend({}, this.__defaultOptions, options); 50 | this.__$containerElement = container; 51 | this.__render(); 52 | this.__$rangepickerElement = this.__$containerElement.find(".range-picker"); 53 | this.__addWidget(); 54 | this.__setContainerToWrapperWidget(); 55 | this.__setCursorInitialPosition(); 56 | this.__updateProcessBarView(); 57 | }, 58 | 59 | __render: function() { 60 | var templateValue = { 61 | startValue: this.__options.startValue, 62 | endValue: this.__options.endValue 63 | }, 64 | viewStr = replace(this.__template, templateValue); 65 | this.__$containerElement.html(viewStr); 66 | }, 67 | 68 | __addWidget: function() { 69 | var positionChangeCallback = $.proxy(this.__handleLabelPositionChange, this); 70 | 71 | this.__selectCursors = []; 72 | this.__selectCursors.push(new Cursor({positionChange: positionChangeCallback})); 73 | // 如果类型是 double 则添加两个游标 74 | if (this.__options.type === "double") { 75 | this.__selectCursors.push(new Cursor({ positionChange: positionChangeCallback})); 76 | } 77 | this.__processBar = new ProcessBar(); 78 | 79 | this.__$rangepickerElement.append(this.__processBar.getJQueryElement()); 80 | for(var i = 0; i < this.__selectCursors.length; i++) { 81 | this.__$rangepickerElement.append(this.__selectCursors[i].getJQueryElement()); 82 | } 83 | this.__setWidgetInitialValue(); 84 | }, 85 | 86 | __setWidgetInitialValue: function() { 87 | var totalWidth = this.__$rangepickerElement.width(); 88 | // 游标位置需要偏移半个游标的宽度, 所以先设置游标的文本,才能计算游标的位置 89 | this.__selectCursors[0].render( 90 | this.__options.translateSelectLabel(totalWidth, totalWidth) 91 | ); 92 | 93 | if (!isUndefined(this.__selectCursors[1])) { 94 | var cursor = this.__selectCursors[1]; 95 | cursor.render(this.__options.translateSelectLabel(0, totalWidth)); 96 | } 97 | }, 98 | 99 | __setCursorInitialPosition: function() { 100 | var totalWidth = this.__$rangepickerElement.width(), 101 | cursors = this.__selectCursors; 102 | 103 | cursors[0].updateArrowPosition(totalWidth); 104 | cursors[0].setTotalWidth(totalWidth); 105 | if (!isUndefined(cursors[1])) { 106 | cursors[1].updateArrowPosition(0); 107 | cursors[1].setTotalWidth(totalWidth); 108 | } 109 | }, 110 | 111 | __setContainerToWrapperWidget: function() { 112 | // 添加容器的 paddint-top 以包含游标 113 | var wrapperElement = this.__$containerElement.find(".range-picker-wrapper"), 114 | cursors = this.__selectCursors, 115 | totalWidth = this.__$rangepickerElement.width(), 116 | cursorHeight = -(cursors[0].getJQueryElement().position().top); 117 | 118 | if (!isUndefined(cursors[1]) && 119 | -(cursors[1].getJQueryElement().position().top) > cursorHeight) { 120 | cursorHeight = -(cursors[1].getJQueryElement().position().top); 121 | } 122 | wrapperElement.css("paddingTop", cursorHeight + "px"); 123 | 124 | // 增加 padding-left 和 padding-right 以包含绝对定位后的游标 125 | var endCursorElement = cursors[0].getJQueryElement(), 126 | paddingRight = endCursorElement.outerWidth() / 2, 127 | paddingLeft = null; 128 | cursors[0].render(this.__options.translateSelectLabel(0, totalWidth)); 129 | paddingLeft = endCursorElement.outerWidth() / 2; 130 | // 恢复原来的值 131 | cursors[0].render(this.__options.translateSelectLabel(totalWidth, totalWidth)); 132 | 133 | wrapperElement.css({ 134 | paddingLeft: paddingLeft + "px", 135 | paddingRight: paddingRight + "px" 136 | }); 137 | 138 | }, 139 | 140 | __handleLabelPositionChange: function(position) { 141 | this.__updateView(position.left); 142 | }, 143 | 144 | __updateView: function() { 145 | this.__updateCursorView(); 146 | this.__updateProcessBarView(); 147 | }, 148 | 149 | __updateCursorView: function() { 150 | var i = 0, 151 | labelText = "", 152 | position = null; 153 | 154 | for(; i < this.__selectCursors.length; i++) { 155 | position = this.__selectCursors[i].getArrowPosition(); 156 | labelText = this.__options.translateSelectLabel(position.left, 157 | this.__$rangepickerElement.width()); 158 | this.__selectCursors[i].render(labelText); 159 | } 160 | 161 | }, 162 | 163 | __updateProcessBarView: function() { 164 | var cursorPosition = this.__getCursorPosition(), 165 | processBarPosition = { 166 | left: cursorPosition.start, 167 | right: this.__$rangepickerElement.width() - cursorPosition.end 168 | }; 169 | this.__processBar.updatePosition(processBarPosition); 170 | }, 171 | 172 | __getCursorPosition: function() { 173 | var position = { 174 | start: 0, 175 | startLabel: "" 176 | }, 177 | tmpPosition = this.__selectCursors[0].getArrowPosition(); 178 | 179 | // 先将第一个游标设置为结束位置 180 | position.end = tmpPosition.left; 181 | position.endLabel = tmpPosition.positionLabel; 182 | 183 | if (!isUndefined(this.__selectCursors[1])) { 184 | tmpPosition = this.__selectCursors[1].getArrowPosition(); 185 | // 当存在第二个光标时且第二个光标距离更远,将第二个光标设置为结束位置,否则第二个光标设置为起始位置 186 | if (tmpPosition.left > position.end) { 187 | position.start = position.end; 188 | position.startLabel = position.endLabel; 189 | position.end = tmpPosition.left; 190 | position.endLabel = tmpPosition.positionLabel; 191 | } else { 192 | position.start = tmpPosition.left; 193 | position.startLabel = tmpPosition.positionLabel; 194 | } 195 | } 196 | 197 | return position; 198 | }, 199 | 200 | __formatPositionValue: function(value, cursorLeftPosition) { 201 | var totalWidth = this.__$rangepickerElement.width(), 202 | offset = 0; 203 | value = value.replace(/\s+/, ""); 204 | 205 | if (value[value.length - 1] === "%") { 206 | offset = totalWidth * parseInt(value, 10) / 100; 207 | } else { 208 | offset = cursorLeftPosition + parseInt(value, 10); 209 | } 210 | 211 | return offset; 212 | }, 213 | 214 | getSelectValue: function() { 215 | var position = this.__getCursorPosition(); 216 | position.totalWidth = this.__$rangepickerElement.width(); 217 | 218 | return position; 219 | }, 220 | 221 | updatePosition: function(endValue, startValue) { 222 | var cursors = this.__selectCursors; 223 | cursors[0].updateArrowPosition(this.__formatPositionValue(endValue, 224 | cursors[0].getArrowPosition().left)); 225 | if (!isUndefined(cursors[1]) && !isUndefined(startValue)) { 226 | cursors[1].updateArrowPosition(this.__formatPositionValue(startValue, 227 | cursors[1].getArrowPosition().left)); 228 | } 229 | this.__updateView(); 230 | } 231 | }; 232 | 233 | function Cursor(options) { 234 | this.__init(options); 235 | } 236 | 237 | Cursor.prototype = { 238 | constructor: Cursor, 239 | __defaultOptions: { 240 | positionChange: $.loop 241 | }, 242 | __template: "", 243 | 244 | __init: function(options) { 245 | this.__options = $.extend({}, this.__defaultOptions, options); 246 | this.__$element = $(this.__template); 247 | this.__bindDragEventHandler(); 248 | }, 249 | 250 | __bindDragEventHandler: function() { 251 | var self = this; 252 | 253 | this.__$element.on("mousedown", function(event) { 254 | this.__rangepicker = { 255 | isMouseDown: true, 256 | mouseStartX: event.clientX, 257 | previousMoveDistance: 0 258 | }; 259 | // 增加 z-index 的值,避免两个游标时被另一个游标遮挡 260 | $(this).css("zIndex", 1000); 261 | }).on("mouseup", function() { 262 | this.__rangepicker = null; 263 | $(this).css("zIndex", 1); 264 | }).on("mousemove", function(event) { 265 | if (this.__rangepicker && this.__rangepicker.isMouseDown) { 266 | self.__handleDragEvent(event.clientX, this.__rangepicker); 267 | } 268 | }).on("mouseout", function() { 269 | $(this).css("zIndex", 1); 270 | this.__rangepicker = null; 271 | }); 272 | }, 273 | 274 | __handleDragEvent: function(clientX, elementData) { 275 | var distance = clientX - elementData.mouseStartX - elementData.previousMoveDistance; 276 | elementData.previousMoveDistance = clientX - elementData.mouseStartX; 277 | var position = this.__calculatePosition(distance); 278 | this.updateArrowPosition(position); 279 | // 获取游标下面箭头的位置,并传递给回调函数 280 | this.__options.positionChange(this.getArrowPosition(), this.__$element); 281 | 282 | }, 283 | 284 | __calculatePosition: function(offset) { 285 | var newPosition = this.__arrowPosition + offset; 286 | // 如果拖动后游标的位置超过了左右边界,则设置为左右边界的位置 287 | if (newPosition > this.__totalWidth) { 288 | newPosition = this.__totalWidth; 289 | } else if (newPosition < 0) { 290 | newPosition = 0; 291 | } 292 | 293 | return newPosition; 294 | }, 295 | 296 | __updatePosition: function(position) { 297 | for(var key in position) { 298 | if (position.hasOwnProperty(key)) { 299 | this.__$element.css(key, position[key] + "px"); 300 | } 301 | } 302 | }, 303 | 304 | render: function(textValue) { 305 | this.__$element.text(textValue); 306 | }, 307 | 308 | updateArrowPosition: function(position) { 309 | this.__arrowPosition = position; 310 | this.__updatePosition({ 311 | left: position - this.__$element.outerWidth() / 2 // 游标的位置减去半个游标的宽度才是游标左边的位置 312 | }); 313 | }, 314 | 315 | getJQueryElement: function() { 316 | return this.__$element; 317 | }, 318 | 319 | getArrowPosition: function() { 320 | return { 321 | left: this.__arrowPosition, 322 | positionLabel: this.__$element.text() 323 | }; 324 | }, 325 | 326 | setTotalWidth: function(totalWidth) { 327 | this.__totalWidth = totalWidth; 328 | } 329 | }; 330 | 331 | function ProcessBar(options) { 332 | this.__init(options); 333 | } 334 | 335 | ProcessBar.prototype = { 336 | constructor: ProcessBar, 337 | __template: "", 338 | __init: function() { 339 | this.__$element = $(this.__template); 340 | }, 341 | 342 | updatePosition: function(position) { 343 | for(var key in position) { 344 | if (position.hasOwnProperty(key)) { 345 | this.__$element.css(key, position[key] + "px"); 346 | } 347 | } 348 | }, 349 | 350 | getJQueryElement: function() { 351 | return this.__$element; 352 | } 353 | }; 354 | 355 | $.fn.rangepicker = function(options) { 356 | return new RangePicker(this, options); 357 | }; 358 | })); 359 | -------------------------------------------------------------------------------- /dist/js/range_picker.js: -------------------------------------------------------------------------------- 1 | /*! range-picker - v0.0.3 - 2016-03-22 */ 2 | ;(function(factory) { 3 | "use strict"; 4 | 5 | if (typeof define === "function" && define.amd) { 6 | // amd 7 | define(["jquery"], factory); 8 | } else { 9 | factory(jQuery); 10 | } 11 | }(function($) { 12 | "use strict"; 13 | 14 | var STR_REPLACE_REG = /<%=\s*(\w+)\s*%>/g; 15 | 16 | function isUndefined(target) { 17 | return typeof target === "undefined"; 18 | } 19 | 20 | function replace(str, value) { 21 | return str.replace(STR_REPLACE_REG, function(match, key) { 22 | return value[key]; 23 | }); 24 | } 25 | 26 | function RangePicker(container, options) { 27 | if (isUndefined(options.startValue) || isUndefined(options.endValue)) { 28 | throw new Error("startValue and endValue is need"); 29 | } 30 | 31 | if(isUndefined(options.translateSelectLabel)) { 32 | throw new Error(" RangePicker: translateSelectLabel is need"); 33 | } 34 | this.__init(container, options); 35 | } 36 | 37 | RangePicker.prototype = { 38 | constructor: RangePicker, 39 | __defaultOptions: { 40 | type: "single" 41 | }, 42 | __template: "
" + 43 | "
" + 44 | "" + 45 | "<%= startValue %>" + 46 | "<%= endValue %>" + 47 | "
" + 48 | "
", 49 | __init: function(container, options) { 50 | this.__options = $.extend({}, this.__defaultOptions, options); 51 | this.__$containerElement = container; 52 | this.__render(); 53 | this.__$rangepickerElement = this.__$containerElement.find(".range-picker"); 54 | this.__addWidget(); 55 | this.__setContainerToWrapperWidget(); 56 | this.__setCursorInitialPosition(); 57 | this.__updateProcessBarView(); 58 | }, 59 | 60 | __render: function() { 61 | var templateValue = { 62 | startValue: this.__options.startValue, 63 | endValue: this.__options.endValue 64 | }, 65 | viewStr = replace(this.__template, templateValue); 66 | this.__$containerElement.html(viewStr); 67 | }, 68 | 69 | __addWidget: function() { 70 | var positionChangeCallback = $.proxy(this.__handleLabelPositionChange, this); 71 | 72 | this.__selectCursors = []; 73 | this.__selectCursors.push(new Cursor({positionChange: positionChangeCallback})); 74 | // 如果类型是 double 则添加两个游标 75 | if (this.__options.type === "double") { 76 | this.__selectCursors.push(new Cursor({ positionChange: positionChangeCallback})); 77 | } 78 | this.__processBar = new ProcessBar(); 79 | 80 | this.__$rangepickerElement.append(this.__processBar.getJQueryElement()); 81 | for(var i = 0; i < this.__selectCursors.length; i++) { 82 | this.__$rangepickerElement.append(this.__selectCursors[i].getJQueryElement()); 83 | } 84 | this.__setWidgetInitialValue(); 85 | }, 86 | 87 | __setWidgetInitialValue: function() { 88 | var totalWidth = this.__$rangepickerElement.width(); 89 | // 游标位置需要偏移半个游标的宽度, 所以先设置游标的文本,才能计算游标的位置 90 | this.__selectCursors[0].render( 91 | this.__options.translateSelectLabel(totalWidth, totalWidth) 92 | ); 93 | 94 | if (!isUndefined(this.__selectCursors[1])) { 95 | var cursor = this.__selectCursors[1]; 96 | cursor.render(this.__options.translateSelectLabel(0, totalWidth)); 97 | } 98 | }, 99 | 100 | __setCursorInitialPosition: function() { 101 | var totalWidth = this.__$rangepickerElement.width(), 102 | cursors = this.__selectCursors; 103 | 104 | cursors[0].updateArrowPosition(totalWidth); 105 | cursors[0].setTotalWidth(totalWidth); 106 | if (!isUndefined(cursors[1])) { 107 | cursors[1].updateArrowPosition(0); 108 | cursors[1].setTotalWidth(totalWidth); 109 | } 110 | }, 111 | 112 | __setContainerToWrapperWidget: function() { 113 | // 添加容器的 paddint-top 以包含游标 114 | var wrapperElement = this.__$containerElement.find(".range-picker-wrapper"), 115 | cursors = this.__selectCursors, 116 | totalWidth = this.__$rangepickerElement.width(), 117 | cursorHeight = -(cursors[0].getJQueryElement().position().top); 118 | 119 | if (!isUndefined(cursors[1]) && 120 | -(cursors[1].getJQueryElement().position().top) > cursorHeight) { 121 | cursorHeight = -(cursors[1].getJQueryElement().position().top); 122 | } 123 | wrapperElement.css("paddingTop", cursorHeight + "px"); 124 | 125 | // 增加 padding-left 和 padding-right 以包含绝对定位后的游标 126 | var endCursorElement = cursors[0].getJQueryElement(), 127 | paddingRight = endCursorElement.outerWidth() / 2, 128 | paddingLeft = null; 129 | cursors[0].render(this.__options.translateSelectLabel(0, totalWidth)); 130 | paddingLeft = endCursorElement.outerWidth() / 2; 131 | // 恢复原来的值 132 | cursors[0].render(this.__options.translateSelectLabel(totalWidth, totalWidth)); 133 | 134 | wrapperElement.css({ 135 | paddingLeft: paddingLeft + "px", 136 | paddingRight: paddingRight + "px" 137 | }); 138 | 139 | }, 140 | 141 | __handleLabelPositionChange: function(position) { 142 | this.__updateView(position.left); 143 | }, 144 | 145 | __updateView: function() { 146 | this.__updateCursorView(); 147 | this.__updateProcessBarView(); 148 | }, 149 | 150 | __updateCursorView: function() { 151 | var i = 0, 152 | labelText = "", 153 | position = null; 154 | 155 | for(; i < this.__selectCursors.length; i++) { 156 | position = this.__selectCursors[i].getArrowPosition(); 157 | labelText = this.__options.translateSelectLabel(position.left, 158 | this.__$rangepickerElement.width()); 159 | this.__selectCursors[i].render(labelText); 160 | } 161 | 162 | }, 163 | 164 | __updateProcessBarView: function() { 165 | var cursorPosition = this.__getCursorPosition(), 166 | processBarPosition = { 167 | left: cursorPosition.start, 168 | right: this.__$rangepickerElement.width() - cursorPosition.end 169 | }; 170 | this.__processBar.updatePosition(processBarPosition); 171 | }, 172 | 173 | __getCursorPosition: function() { 174 | var position = { 175 | start: 0, 176 | startLabel: "" 177 | }, 178 | tmpPosition = this.__selectCursors[0].getArrowPosition(); 179 | 180 | // 先将第一个游标设置为结束位置 181 | position.end = tmpPosition.left; 182 | position.endLabel = tmpPosition.positionLabel; 183 | 184 | if (!isUndefined(this.__selectCursors[1])) { 185 | tmpPosition = this.__selectCursors[1].getArrowPosition(); 186 | // 当存在第二个光标时且第二个光标距离更远,将第二个光标设置为结束位置,否则第二个光标设置为起始位置 187 | if (tmpPosition.left > position.end) { 188 | position.start = position.end; 189 | position.startLabel = position.endLabel; 190 | position.end = tmpPosition.left; 191 | position.endLabel = tmpPosition.positionLabel; 192 | } else { 193 | position.start = tmpPosition.left; 194 | position.startLabel = tmpPosition.positionLabel; 195 | } 196 | } 197 | 198 | return position; 199 | }, 200 | 201 | __formatPositionValue: function(value, cursorLeftPosition) { 202 | var totalWidth = this.__$rangepickerElement.width(), 203 | offset = 0; 204 | value = value.replace(/\s+/, ""); 205 | 206 | if (value[value.length - 1] === "%") { 207 | offset = totalWidth * parseInt(value, 10) / 100; 208 | } else { 209 | offset = cursorLeftPosition + parseInt(value, 10); 210 | } 211 | 212 | return offset; 213 | }, 214 | 215 | getSelectValue: function() { 216 | var position = this.__getCursorPosition(); 217 | position.totalWidth = this.__$rangepickerElement.width(); 218 | 219 | return position; 220 | }, 221 | 222 | updatePosition: function(endValue, startValue) { 223 | var cursors = this.__selectCursors; 224 | cursors[0].updateArrowPosition(this.__formatPositionValue(endValue, 225 | cursors[0].getArrowPosition().left)); 226 | if (!isUndefined(cursors[1]) && !isUndefined(startValue)) { 227 | cursors[1].updateArrowPosition(this.__formatPositionValue(startValue, 228 | cursors[1].getArrowPosition().left)); 229 | } 230 | this.__updateView(); 231 | } 232 | }; 233 | 234 | function Cursor(options) { 235 | this.__init(options); 236 | } 237 | 238 | Cursor.prototype = { 239 | constructor: Cursor, 240 | __defaultOptions: { 241 | positionChange: $.loop 242 | }, 243 | __template: "", 244 | 245 | __init: function(options) { 246 | this.__options = $.extend({}, this.__defaultOptions, options); 247 | this.__$element = $(this.__template); 248 | this.__bindDragEventHandler(); 249 | }, 250 | 251 | __bindDragEventHandler: function() { 252 | var self = this; 253 | 254 | this.__$element.on("mousedown", function(event) { 255 | this.__rangepicker = { 256 | isMouseDown: true, 257 | mouseStartX: event.clientX, 258 | previousMoveDistance: 0 259 | }; 260 | // 增加 z-index 的值,避免两个游标时被另一个游标遮挡 261 | $(this).css("zIndex", 1000); 262 | }).on("mouseup", function() { 263 | this.__rangepicker = null; 264 | $(this).css("zIndex", 1); 265 | }).on("mousemove", function(event) { 266 | if (this.__rangepicker && this.__rangepicker.isMouseDown) { 267 | self.__handleDragEvent(event.clientX, this.__rangepicker); 268 | } 269 | }).on("mouseout", function() { 270 | $(this).css("zIndex", 1); 271 | this.__rangepicker = null; 272 | }); 273 | }, 274 | 275 | __handleDragEvent: function(clientX, elementData) { 276 | var distance = clientX - elementData.mouseStartX - elementData.previousMoveDistance; 277 | elementData.previousMoveDistance = clientX - elementData.mouseStartX; 278 | var position = this.__calculatePosition(distance); 279 | this.updateArrowPosition(position); 280 | // 获取游标下面箭头的位置,并传递给回调函数 281 | this.__options.positionChange(this.getArrowPosition(), this.__$element); 282 | 283 | }, 284 | 285 | __calculatePosition: function(offset) { 286 | var newPosition = this.__arrowPosition + offset; 287 | // 如果拖动后游标的位置超过了左右边界,则设置为左右边界的位置 288 | if (newPosition > this.__totalWidth) { 289 | newPosition = this.__totalWidth; 290 | } else if (newPosition < 0) { 291 | newPosition = 0; 292 | } 293 | 294 | return newPosition; 295 | }, 296 | 297 | __updatePosition: function(position) { 298 | for(var key in position) { 299 | if (position.hasOwnProperty(key)) { 300 | this.__$element.css(key, position[key] + "px"); 301 | } 302 | } 303 | }, 304 | 305 | render: function(textValue) { 306 | this.__$element.text(textValue); 307 | }, 308 | 309 | updateArrowPosition: function(position) { 310 | this.__arrowPosition = position; 311 | this.__updatePosition({ 312 | left: position - this.__$element.outerWidth() / 2 // 游标的位置减去半个游标的宽度才是游标左边的位置 313 | }); 314 | }, 315 | 316 | getJQueryElement: function() { 317 | return this.__$element; 318 | }, 319 | 320 | getArrowPosition: function() { 321 | return { 322 | left: this.__arrowPosition, 323 | positionLabel: this.__$element.text() 324 | }; 325 | }, 326 | 327 | setTotalWidth: function(totalWidth) { 328 | this.__totalWidth = totalWidth; 329 | } 330 | }; 331 | 332 | function ProcessBar(options) { 333 | this.__init(options); 334 | } 335 | 336 | ProcessBar.prototype = { 337 | constructor: ProcessBar, 338 | __template: "", 339 | __init: function() { 340 | this.__$element = $(this.__template); 341 | }, 342 | 343 | updatePosition: function(position) { 344 | for(var key in position) { 345 | if (position.hasOwnProperty(key)) { 346 | this.__$element.css(key, position[key] + "px"); 347 | } 348 | } 349 | }, 350 | 351 | getJQueryElement: function() { 352 | return this.__$element; 353 | } 354 | }; 355 | 356 | $.fn.rangepicker = function(options) { 357 | return new RangePicker(this, options); 358 | }; 359 | })); 360 | -------------------------------------------------------------------------------- /test/spec/rangepickerSpec.js: -------------------------------------------------------------------------------- 1 | describe("rangepicker 测试", function() { 2 | "use strict"; 3 | 4 | it("rangepicker 是一个函数", function() { 5 | expect($.fn.rangepicker).is.an("function"); 6 | }); 7 | 8 | describe("必须参数测试", function() { 9 | it("没有 startValue 应该抛出错误", function() { 10 | var options = { 11 | endValue: "100", 12 | translateSelectLabel: function() { 13 | return "test"; 14 | } 15 | }; 16 | 17 | expect(function() { 18 | $("#range_picker").rangepicker(options); 19 | }).to.throw(Error); 20 | }); 21 | 22 | it("没有 endValue 应该抛出错误", function() { 23 | var options = { 24 | startValue: 0, 25 | translateSelectLabel: function() { 26 | return "test"; 27 | } 28 | }; 29 | 30 | expect(function() { 31 | $("#range_picker").rangepicker(options); 32 | }).to.throw(Error); 33 | }); 34 | 35 | it("没有 translateSelectLabel 应该抛出错误", function() { 36 | var options = { startValue: 0, endValue: 100 }; 37 | expect(function() { 38 | $("#range_picker").rangepicker(options); 39 | }).to.throw(Error); 40 | }); 41 | }); 42 | 43 | describe("功能测试", function() { 44 | var startValue = 0, 45 | endValue = 100, 46 | translateSelectLabel = function(currentPosition, totalPosition) { 47 | return parseInt(100 * (currentPosition / totalPosition)); 48 | }, 49 | totalWidth = 400, 50 | rangePicker = null; 51 | 52 | describe("单个游标测试", function() { 53 | before(function() { 54 | rangePicker = $("#range_picker").rangepicker({ 55 | startValue: startValue, 56 | endValue: endValue, 57 | translateSelectLabel: translateSelectLabel 58 | }); 59 | }); 60 | 61 | afterEach(function() { 62 | $(".select-label").simulate("drag-n-drop", {dx: totalWidth}); 63 | }); 64 | 65 | it("应该只有一个游标", function() { 66 | expect($(".select-label").length).to.equal(1); 67 | }); 68 | 69 | it("初始时游标应该位于最左端", function() { 70 | var position = rangePicker.getSelectValue(); 71 | 72 | expect(position.start).to.equal(startValue); 73 | expect(position.startLabel).to.equal(""); 74 | expect(position.endLabel).to. 75 | equal("" + translateSelectLabel(totalWidth, totalWidth)); 76 | expect(position.end).to.equal(totalWidth); 77 | }); 78 | 79 | it("向左拖动 100px 时", function() { 80 | $(".select-label").simulate("drag-n-drop", {dx: -100}); 81 | var position = rangePicker.getSelectValue(), 82 | endLabel = translateSelectLabel(totalWidth - 100, totalWidth); 83 | 84 | expect(position.start).to.equal(0); 85 | expect(position.startLabel).to.equal(""); 86 | expect(position.end).to.equal(totalWidth - 100); 87 | expect(position.endLabel).to.equal("" + endLabel); 88 | }); 89 | 90 | it("当游标到左边界时", function() { 91 | $(".select-label").simulate("drag-n-drop", {dx: -totalWidth}); 92 | var position = rangePicker.getSelectValue(), 93 | endLabel = translateSelectLabel(0, totalWidth); 94 | 95 | expect(position.start).to.equal(0); 96 | expect(position.startLabel).to.equal(""); 97 | expect(position.end).to.equal(0); 98 | expect(position.endLabel).to.equal("" + endLabel); 99 | }); 100 | 101 | it("当游标超出左边界时", function() { 102 | $(".select-label").simulate("drag-n-drop", {dx: -(3 * totalWidth)}); 103 | var position = rangePicker.getSelectValue(), 104 | endLabel = translateSelectLabel(0, totalWidth); 105 | 106 | expect(position.start).to.equal(0); 107 | expect(position.startLabel).to.equal(""); 108 | expect(position.end).to.equal(0); 109 | expect(position.endLabel).to.equal("" + endLabel); 110 | }); 111 | 112 | it("当游标超出右边界时", function() { 113 | $(".select-label").simulate("drag-n-drop", {dx: (3 * totalWidth)}); 114 | var position = rangePicker.getSelectValue(), 115 | endLabel = translateSelectLabel(totalWidth, totalWidth); 116 | 117 | expect(position.start).to.equal(0); 118 | expect(position.startLabel).to.equal(""); 119 | expect(position.end).to.equal(totalWidth); 120 | expect(position.endLabel).to.equal("" + endLabel); 121 | }); 122 | 123 | }); 124 | 125 | describe("两个游标测试", function() { 126 | before(function() { 127 | rangePicker = $("#range_picker").rangepicker({ 128 | type: "double", 129 | startValue: startValue, 130 | endValue: endValue, 131 | translateSelectLabel: translateSelectLabel 132 | }); 133 | }); 134 | 135 | afterEach(function() { 136 | $(".select-label:eq(0)").simulate("drag-n-drop", {dx: totalWidth}); 137 | $(".select-label:eq(1)").simulate("drag-n-drop", {dx: -totalWidth}); 138 | }); 139 | 140 | it("应该有两个游标", function() { 141 | expect($(".select-label").length).to.equal(2); 142 | }); 143 | 144 | it("初始时游标应该位于两端", function() { 145 | var position = rangePicker.getSelectValue(); 146 | 147 | expect(position.start).to.equal(startValue); 148 | expect(position.startLabel).to. 149 | equal("" + translateSelectLabel(startValue, totalWidth)); 150 | expect(position.end).to.equal(totalWidth); 151 | expect(position.endLabel).to. 152 | equal("" + translateSelectLabel(totalWidth, totalWidth)); 153 | }); 154 | 155 | it("当左边的游标向右拖动 100px 时", function() { 156 | var leftCursor = $(".select-label:eq(1)"), 157 | position = null; 158 | leftCursor.simulate("drag-n-drop", {dx: 100}); 159 | position = rangePicker.getSelectValue(); 160 | 161 | expect(position.start).to.equal(100); 162 | expect(position.startLabel).to.equal("" + translateSelectLabel(100, totalWidth)); 163 | expect(position.end).to.equal(totalWidth); 164 | expect(position.endLabel).to. 165 | equal("" + translateSelectLabel(totalWidth, totalWidth)); 166 | }); 167 | 168 | it("当左边的游标超出左边界时", function() { 169 | var leftCursor = $(".select-label:eq(1)"), 170 | position = null; 171 | leftCursor.simulate("drag-n-drop", {dx: -(4 * totalWidth)}); 172 | position = rangePicker.getSelectValue(); 173 | 174 | expect(position.start).to.equal(startValue); 175 | expect(position.startLabel).to. 176 | equal("" + translateSelectLabel(startValue, totalWidth)); 177 | expect(position.end).to.equal(totalWidth); 178 | expect(position.endLabel).to. 179 | equal("" + translateSelectLabel(totalWidth, totalWidth)); 180 | }); 181 | 182 | it("当左边的游标超出右边界时", function() { 183 | var leftCursor = $(".select-label:eq(1)"), 184 | position = null; 185 | leftCursor.simulate("drag-n-drop", {dx: (4 * totalWidth)}); 186 | position = rangePicker.getSelectValue(); 187 | 188 | expect(position.start).to.equal(totalWidth); 189 | expect(position.startLabel).to. 190 | equal("" + translateSelectLabel(totalWidth, totalWidth)); 191 | expect(position.end).to.equal(totalWidth); 192 | expect(position.endLabel).to. 193 | equal("" + translateSelectLabel(totalWidth, totalWidth)); 194 | }); 195 | 196 | it("当右边的游标向左拖动 100px 时", function() { 197 | var rightCursor = $(".select-label:eq(0)"), 198 | position = null; 199 | rightCursor.simulate("drag-n-drop", {dx: -100}); 200 | position = rangePicker.getSelectValue(); 201 | 202 | expect(position.start).to.equal(startValue); 203 | expect(position.startLabel).to. 204 | equal("" + translateSelectLabel(startValue, totalWidth)); 205 | expect(position.end).to.equal(totalWidth - 100); 206 | expect(position.endLabel).to. 207 | equal("" + translateSelectLabel(totalWidth - 100, totalWidth)); 208 | }); 209 | 210 | it("当右边的游标超出右边界时", function() { 211 | var rightCursor = $(".select-label:eq(0)"), 212 | position = null; 213 | rightCursor.simulate("drag-n-drop", {dx: (4 * totalWidth)}); 214 | position = rangePicker.getSelectValue(); 215 | 216 | expect(position.start).to.equal(0); 217 | expect(position.startLabel).to. 218 | equal("" + translateSelectLabel(startValue, totalWidth)); 219 | expect(position.end).to.equal(totalWidth); 220 | expect(position.endLabel).to. 221 | equal("" + translateSelectLabel(totalWidth, totalWidth)); 222 | }); 223 | 224 | it("当右边的游标超出左边界时", function() { 225 | var rightCursor = $(".select-label:eq(0)"), 226 | position = null; 227 | rightCursor.simulate("drag-n-drop", {dx: -(4 * totalWidth)}); 228 | position = rangePicker.getSelectValue(); 229 | 230 | expect(position.start).to.equal(startValue); 231 | expect(position.startLabel).to.equal("" + 232 | translateSelectLabel(startValue, totalWidth)); 233 | expect(position.end).to.equal(startValue); 234 | expect(position.endLabel).to.equal("" + 235 | translateSelectLabel(startValue, totalWidth)); 236 | }); 237 | 238 | it("当左右游标互换时", function() { 239 | var rightCursor = $(".select-label:eq(0)"), 240 | leftCursor = $(".select-label:eq(1)"), 241 | position = null; 242 | leftCursor.simulate("drag-n-drop", {dx: (4 * totalWidth)}); 243 | rightCursor.simulate("drag-n-drop", {dx: -(4 * totalWidth)}); 244 | position = rangePicker.getSelectValue(); 245 | 246 | expect(position.start).to.equal(startValue); 247 | expect(position.startLabel).to. 248 | equal("" + translateSelectLabel(startValue, totalWidth)); 249 | 250 | expect(position.end).to.equal(totalWidth); 251 | expect(position.endLabel).to.equal("" + 252 | translateSelectLabel(totalWidth, totalWidth)); 253 | }); 254 | 255 | }); 256 | }); 257 | 258 | describe("回调用函数测试", function() { 259 | var startValue = 0, 260 | endValue = 100, 261 | translateSelectLabel = function(currentPosition, totalPosition) { 262 | return parseInt(100 * (currentPosition / totalPosition)); 263 | }, 264 | translateSpy = sinon.spy(translateSelectLabel); 265 | 266 | before(function() { 267 | $("#range_picker").rangepicker({ 268 | startValue: startValue, 269 | endValue: endValue, 270 | translateSelectLabel: translateSpy 271 | }); 272 | }); 273 | 274 | it("translateSelectLabel 回调测试", function() { 275 | $(".select-label").simulate("drag-n-drop", {dx: 100}); 276 | expect(translateSpy.called).to.equal(true); 277 | }); 278 | }); 279 | 280 | describe("方法测试", function() { 281 | var startValue = 0, 282 | endValue = 100, 283 | totalWidth = 400, 284 | translateSelectLabel = function(currentPosition, totalPosition) { 285 | return parseInt(100 * (currentPosition / totalPosition)); 286 | }, 287 | rangePicker = null; 288 | 289 | before(function() { 290 | rangePicker = $("#range_picker").rangepicker({ 291 | type: "double", 292 | startValue: startValue, 293 | endValue: endValue, 294 | translateSelectLabel: translateSelectLabel 295 | }); 296 | }); 297 | 298 | afterEach(function() { 299 | $(".select-label:eq(0)").simulate("drag-n-drop", {dx: totalWidth}); 300 | $(".select-label:eq(1)").simulate("drag-n-drop", {dx: -totalWidth}); 301 | }); 302 | 303 | it("updatePosition 可以使用象素更新游标的位置", function() { 304 | rangePicker.updatePosition("-100px", "100px"); 305 | var position = rangePicker.getSelectValue(); 306 | 307 | expect(position.start).to.equal(100); 308 | expect(position.startLabel).to.equal("" + translateSelectLabel(100, totalWidth)); 309 | expect(position.end).to.equal(totalWidth - 100); 310 | expect(position.endLabel).to. 311 | equal("" + translateSelectLabel(totalWidth - 100, totalWidth)); 312 | }); 313 | 314 | it("updatePosition 可以使用百分比更新位置", function() { 315 | rangePicker.updatePosition("10%", "90%"); 316 | var position = rangePicker.getSelectValue(); 317 | 318 | expect(position.start).to.equal(totalWidth * 0.1); 319 | expect(position.startLabel).to. 320 | equal("" + translateSelectLabel(totalWidth * 0.1, totalWidth)); 321 | expect(position.end).to.equal(totalWidth * 0.9); 322 | expect(position.endLabel).to. 323 | equal("" + translateSelectLabel(0.9 * totalWidth, totalWidth)); 324 | }); 325 | }); 326 | 327 | }); 328 | --------------------------------------------------------------------------------