jquery-asRange.es.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840
  1. /**
  2. * asRange v0.3.4
  3. * https://github.com/amazingSurge/jquery-asRange
  4. *
  5. * Copyright (c) amazingSurge
  6. * Released under the LGPL-3.0 license
  7. */
  8. import $ from 'jquery';
  9. var DEFAULTS = {
  10. namespace: 'asRange',
  11. skin: null,
  12. max: 100,
  13. min: 0,
  14. value: null,
  15. step: 10,
  16. limit: true,
  17. range: false,
  18. direction: 'h', // 'v' or 'h'
  19. keyboard: true,
  20. replaceFirst: false, // false, 'inherit', {'inherit': 'default'}
  21. tip: true,
  22. scale: true,
  23. format(value) {
  24. return value;
  25. }
  26. };
  27. function getEventObject (event) {
  28. let e = event.originalEvent;
  29. if (e.touches && e.touches.length && e.touches[0]) {
  30. e = e.touches[0];
  31. }
  32. return e;
  33. }
  34. class Pointer {
  35. constructor ($element, id, parent) {
  36. this.$element = $element;
  37. this.uid = id;
  38. this.parent = parent;
  39. this.options = $.extend(true, {}, this.parent.options);
  40. this.direction = this.options.direction;
  41. this.value = null;
  42. this.classes = {
  43. active: `${this.parent.namespace}-pointer_active`
  44. };
  45. }
  46. mousedown(event) {
  47. const axis = this.parent.direction.axis;
  48. const position = this.parent.direction.position;
  49. const offset = this.parent.$wrap.offset();
  50. this.$element.trigger(`${this.parent.namespace}::moveStart`, this);
  51. this.data = {};
  52. this.data.start = event[axis];
  53. this.data.position = event[axis] - offset[position];
  54. const value = this.parent.getValueFromPosition(this.data.position);
  55. this.set(value);
  56. $.each(this.parent.pointer, (i, p) => {
  57. p.deactive();
  58. });
  59. this.active();
  60. this.mousemove = function(event) {
  61. const eventObj = getEventObject(event);
  62. const value = this.parent.getValueFromPosition(this.data.position + (eventObj[axis] || this.data.start) - this.data.start);
  63. this.set(value);
  64. event.preventDefault();
  65. return false;
  66. };
  67. this.mouseup = function() {
  68. $(document).off('.asRange mousemove.asRange touchend.asRange mouseup.asRange touchcancel.asRange');
  69. this.$element.trigger(`${this.parent.namespace}::moveEnd`, this);
  70. return false;
  71. };
  72. $(document).on('touchmove.asRange mousemove.asRange', $.proxy(this.mousemove, this))
  73. .on('touchend.asRange mouseup.asRange', $.proxy(this.mouseup, this));
  74. return false;
  75. }
  76. active() {
  77. this.$element.addClass(this.classes.active);
  78. }
  79. deactive() {
  80. this.$element.removeClass(this.classes.active);
  81. }
  82. set(value) {
  83. if (this.value === value) {
  84. return;
  85. }
  86. if (this.parent.step) {
  87. value = this.matchStep(value);
  88. }
  89. if (this.options.limit === true) {
  90. value = this.matchLimit(value);
  91. } else {
  92. if (value <= this.parent.min) {
  93. value = this.parent.min;
  94. }
  95. if (value >= this.parent.max) {
  96. value = this.parent.max;
  97. }
  98. }
  99. this.value = value;
  100. this.updatePosition();
  101. this.$element.focus();
  102. this.$element.trigger(`${this.parent.namespace}::move`, this);
  103. }
  104. updatePosition() {
  105. const position = {};
  106. position[this.parent.direction.position] = `${this.getPercent()}%`;
  107. this.$element.css(position);
  108. }
  109. getPercent() {
  110. return ((this.value - this.parent.min) / this.parent.interval) * 100;
  111. }
  112. get() {
  113. return this.value;
  114. }
  115. matchStep(value) {
  116. const step = this.parent.step;
  117. const decimal = step.toString().split('.')[1];
  118. value = Math.round(value / step) * step;
  119. if (decimal) {
  120. value = value.toFixed(decimal.length);
  121. }
  122. return parseFloat(value);
  123. }
  124. matchLimit(value) {
  125. let left;
  126. let right;
  127. const pointer = this.parent.pointer;
  128. if (this.uid === 1) {
  129. left = this.parent.min;
  130. } else {
  131. left = pointer[this.uid - 2].value;
  132. }
  133. if (pointer[this.uid] && pointer[this.uid].value !== null) {
  134. right = pointer[this.uid].value;
  135. } else {
  136. right = this.parent.max;
  137. }
  138. if (value <= left) {
  139. value = left;
  140. }
  141. if (value >= right) {
  142. value = right;
  143. }
  144. return value;
  145. }
  146. destroy() {
  147. this.$element.off('.asRange');
  148. this.$element.remove();
  149. }
  150. }
  151. var scale = {
  152. defaults: {
  153. scale: {
  154. valuesNumber: 3,
  155. gap: 1,
  156. grid: 5
  157. }
  158. },
  159. init(instance) {
  160. const opts = $.extend({}, this.defaults, instance.options.scale);
  161. const scale = opts.scale;
  162. scale.values = [];
  163. scale.values.push(instance.min);
  164. const part = (instance.max - instance.min) / (scale.valuesNumber - 1);
  165. for (let j = 1; j <= (scale.valuesNumber - 2); j++) {
  166. scale.values.push(part * j);
  167. }
  168. scale.values.push(instance.max);
  169. const classes = {
  170. scale: `${instance.namespace}-scale`,
  171. lines: `${instance.namespace}-scale-lines`,
  172. grid: `${instance.namespace}-scale-grid`,
  173. inlineGrid: `${instance.namespace}-scale-inlineGrid`,
  174. values: `${instance.namespace}-scale-values`
  175. };
  176. const len = scale.values.length;
  177. const num = ((scale.grid - 1) * (scale.gap + 1) + scale.gap) * (len - 1) + len;
  178. const perOfGrid = 100 / (num - 1);
  179. const perOfValue = 100 / (len - 1);
  180. this.$scale = $('<div></div>').addClass(classes.scale);
  181. this.$lines = $('<ul></ul>').addClass(classes.lines);
  182. this.$values = $('<ul></ul>').addClass(classes.values);
  183. for (let i = 0; i < num; i++) {
  184. let $list;
  185. if (i === 0 || i === num || i % ((num - 1) / (len - 1)) === 0) {
  186. $list = $(`<li class="${classes.grid}"></li>`);
  187. } else if (i % scale.grid === 0) {
  188. $list = $(`<li class="${classes.inlineGrid}"></li>`);
  189. } else {
  190. $list = $('<li></li>');
  191. }
  192. // position scale
  193. $list.css({
  194. left: `${perOfGrid * i}%`
  195. }).appendTo(this.$lines);
  196. }
  197. for (let v = 0; v < len; v++) {
  198. // position value
  199. $(`<li><span>${scale.values[v]}</span></li>`).css({
  200. left: `${perOfValue * v}%`
  201. }).appendTo(this.$values);
  202. }
  203. this.$lines.add(this.$values).appendTo(this.$scale);
  204. this.$scale.appendTo(instance.$wrap);
  205. },
  206. update(instance) {
  207. this.$scale.remove();
  208. this.init(instance);
  209. }
  210. };
  211. var selected = {
  212. defaults: {},
  213. init(instance) {
  214. this.$arrow = $('<span></span>').appendTo(instance.$wrap);
  215. this.$arrow.addClass(`${instance.namespace}-selected`);
  216. if (instance.options.range === false) {
  217. instance.p1.$element.on(`${instance.namespace}::move`, (e, pointer) => {
  218. this.$arrow.css({
  219. left: 0,
  220. width: `${pointer.getPercent()}%`
  221. });
  222. });
  223. }
  224. if (instance.options.range === true) {
  225. const onUpdate = () => {
  226. let width = instance.p2.getPercent() - instance.p1.getPercent();
  227. let left;
  228. if (width >= 0) {
  229. left = instance.p1.getPercent();
  230. } else {
  231. width = -width;
  232. left = instance.p2.getPercent();
  233. }
  234. this.$arrow.css({
  235. left: `${left}%`,
  236. width: `${width}%`
  237. });
  238. };
  239. instance.p1.$element.on(`${instance.namespace}::move`, onUpdate);
  240. instance.p2.$element.on(`${instance.namespace}::move`, onUpdate);
  241. }
  242. }
  243. };
  244. var tip = {
  245. defaults: {
  246. active: 'always' // 'always' 'onMove'
  247. },
  248. init(instance) {
  249. const that = this;
  250. const opts = $.extend({}, this.defaults, instance.options.tip);
  251. this.opts = opts;
  252. this.classes = {
  253. tip: `${instance.namespace}-tip`,
  254. show: `${instance.namespace}-tip-show`
  255. };
  256. $.each(instance.pointer, (i, p) => {
  257. const $tip = $('<span></span>').appendTo(instance.pointer[i].$element);
  258. $tip.addClass(that.classes.tip);
  259. if (that.opts.active === 'onMove') {
  260. $tip.css({
  261. display: 'none'
  262. });
  263. p.$element.on(`${instance.namespace}::moveEnd`, () => {
  264. that.hide($tip);
  265. return false;
  266. }).on(`${instance.namespace}::moveStart`, () => {
  267. that.show($tip);
  268. return false;
  269. });
  270. }
  271. p.$element.on(`${instance.namespace}::move`, () => {
  272. let value;
  273. if (instance.options.range) {
  274. value = instance.get()[i];
  275. } else {
  276. value = instance.get();
  277. }
  278. if (typeof instance.options.format === 'function') {
  279. if (instance.options.replaceFirst && typeof value !== 'number') {
  280. if (typeof instance.options.replaceFirst === 'string') {
  281. value = instance.options.replaceFirst;
  282. }
  283. if (typeof instance.options.replaceFirst === 'object') {
  284. for (const key in instance.options.replaceFirst) {
  285. if(Object.hasOwnProperty(instance.options.replaceFirst, key)){
  286. value = instance.options.replaceFirst[key];
  287. }
  288. }
  289. }
  290. } else {
  291. value = instance.options.format(value);
  292. }
  293. }
  294. $tip.text(value);
  295. return false;
  296. });
  297. });
  298. },
  299. show($tip) {
  300. $tip.addClass(this.classes.show);
  301. $tip.css({
  302. display: 'block'
  303. });
  304. },
  305. hide($tip) {
  306. $tip.removeClass(this.classes.show);
  307. $tip.css({
  308. display: 'none'
  309. });
  310. }
  311. };
  312. var keyboard = function() {
  313. const $doc = $(document);
  314. $doc.on('asRange::ready', (event, instance) => {
  315. let step;
  316. const keyboard = {
  317. keys: {
  318. 'UP': 38,
  319. 'DOWN': 40,
  320. 'LEFT': 37,
  321. 'RIGHT': 39,
  322. 'RETURN': 13,
  323. 'ESCAPE': 27,
  324. 'BACKSPACE': 8,
  325. 'SPACE': 32
  326. },
  327. map: {},
  328. bound: false,
  329. press(e) {
  330. /*eslint consistent-return: "off"*/
  331. const key = e.keyCode || e.which;
  332. if (key in keyboard.map && typeof keyboard.map[key] === 'function') {
  333. keyboard.map[key](e);
  334. return false;
  335. }
  336. },
  337. attach(map) {
  338. let key;
  339. let up;
  340. for (key in map) {
  341. if (map.hasOwnProperty(key)) {
  342. up = key.toUpperCase();
  343. if (up in keyboard.keys) {
  344. keyboard.map[keyboard.keys[up]] = map[key];
  345. } else {
  346. keyboard.map[up] = map[key];
  347. }
  348. }
  349. }
  350. if (!keyboard.bound) {
  351. keyboard.bound = true;
  352. $doc.bind('keydown', keyboard.press);
  353. }
  354. },
  355. detach() {
  356. keyboard.bound = false;
  357. keyboard.map = {};
  358. $doc.unbind('keydown', keyboard.press);
  359. }
  360. };
  361. if (instance.options.keyboard === true) {
  362. $.each(instance.pointer, (i, p) => {
  363. if (instance.options.step) {
  364. step = instance.options.step;
  365. } else {
  366. step = 1;
  367. }
  368. const left = () => {
  369. const value = p.value;
  370. p.set(value - step);
  371. };
  372. const right = () => {
  373. const value = p.value;
  374. p.set(value + step);
  375. };
  376. p.$element.attr('tabindex', '0').on('focus', () => {
  377. keyboard.attach({
  378. left,
  379. right
  380. });
  381. return false;
  382. }).on('blur', () => {
  383. keyboard.detach();
  384. return false;
  385. });
  386. });
  387. }
  388. });
  389. };
  390. let components = {};
  391. /**
  392. * Plugin constructor
  393. **/
  394. class asRange {
  395. constructor(element, options) {
  396. const metas = {};
  397. this.element = element;
  398. this.$element = $(element);
  399. if (this.$element.is('input')) {
  400. const value = this.$element.val();
  401. if (typeof value === 'string') {
  402. metas.value = value.split(',');
  403. }
  404. $.each(['min', 'max', 'step'], (index, key) => {
  405. const val = parseFloat(this.$element.attr(key));
  406. if (!isNaN(val)) {
  407. metas[key] = val;
  408. }
  409. });
  410. this.$element.css({
  411. display: 'none'
  412. });
  413. this.$wrap = $("<div></div>");
  414. this.$element.after(this.$wrap);
  415. } else {
  416. this.$wrap = this.$element;
  417. }
  418. this.options = $.extend({}, DEFAULTS, options, this.$element.data(), metas);
  419. this.namespace = this.options.namespace;
  420. this.components = $.extend(true, {}, components);
  421. if (this.options.range) {
  422. this.options.replaceFirst = false;
  423. }
  424. // public properties
  425. this.value = this.options.value;
  426. if (this.value === null) {
  427. this.value = this.options.min;
  428. }
  429. if (!this.options.range) {
  430. if ($.isArray(this.value)) {
  431. this.value = this.value[0];
  432. }
  433. } else if (!$.isArray(this.value)) {
  434. this.value = [this.value, this.value];
  435. } else if (this.value.length === 1) {
  436. this.value[1] = this.value[0];
  437. }
  438. this.min = this.options.min;
  439. this.max = this.options.max;
  440. this.step = this.options.step;
  441. this.interval = this.max - this.min;
  442. // flag
  443. this.initialized = false;
  444. this.updating = false;
  445. this.disabled = false;
  446. if (this.options.direction === 'v') {
  447. this.direction = {
  448. axis: 'pageY',
  449. position: 'top'
  450. };
  451. } else {
  452. this.direction = {
  453. axis: 'pageX',
  454. position: 'left'
  455. };
  456. }
  457. this.$wrap.addClass(this.namespace);
  458. if (this.options.skin) {
  459. this.$wrap.addClass(`${this.namespace}_${this.options.skin}`);
  460. }
  461. if (this.max < this.min || this.step >= this.interval) {
  462. throw new Error('error options about max min step');
  463. }
  464. this.init();
  465. }
  466. init() {
  467. this.$wrap.append(`<div class="${this.namespace}-bar" />`);
  468. // build pointers
  469. this.buildPointers();
  470. // initial components
  471. this.components.selected.init(this);
  472. if (this.options.tip !== false) {
  473. this.components.tip.init(this);
  474. }
  475. if (this.options.scale !== false) {
  476. this.components.scale.init(this);
  477. }
  478. // initial pointer value
  479. this.set(this.value);
  480. // Bind events
  481. this.bindEvents();
  482. this._trigger('ready');
  483. this.initialized = true;
  484. }
  485. _trigger(eventType, ...params) {
  486. let data = [this].concat(params);
  487. // event
  488. this.$element.trigger(this.namespace + `::${eventType}`, data);
  489. // callback
  490. eventType = eventType.replace(/\b\w+\b/g, (word) => {
  491. return word.substring(0, 1).toUpperCase() + word.substring(1);
  492. });
  493. let onFunction = `on${eventType}`;
  494. if (typeof this.options[onFunction] === 'function') {
  495. this.options[onFunction].apply(this, params);
  496. }
  497. }
  498. buildPointers() {
  499. this.pointer = [];
  500. let pointerCount = 1;
  501. if (this.options.range) {
  502. pointerCount = 2;
  503. }
  504. for (let i = 1; i <= pointerCount; i++) {
  505. const $pointer = $(`<div class="${this.namespace}-pointer ${this.namespace}-pointer-${i}"></div>`).appendTo(this.$wrap);
  506. const p = new Pointer($pointer, i, this);
  507. this.pointer.push(p);
  508. }
  509. // alias of pointer
  510. this.p1 = this.pointer[0];
  511. if (this.options.range) {
  512. this.p2 = this.pointer[1];
  513. }
  514. }
  515. bindEvents() {
  516. const that = this;
  517. this.$wrap.on('touchstart.asRange mousedown.asRange', event => {
  518. /*eslint consistent-return: "off"*/
  519. if (that.disabled === true) {
  520. return;
  521. }
  522. event = getEventObject(event);
  523. const rightclick = (event.which) ? (event.which === 3) : (event.button === 2);
  524. if (rightclick) {
  525. return false;
  526. }
  527. const offset = that.$wrap.offset();
  528. const start = event[that.direction.axis] - offset[that.direction.position];
  529. const p = that.getAdjacentPointer(start);
  530. p.mousedown(event);
  531. return false;
  532. });
  533. if (this.$element.is('input')) {
  534. this.$element.on(this.namespace + `::change`, () => {
  535. const value = this.get();
  536. this.$element.val(value);
  537. });
  538. }
  539. $.each(this.pointer, (i, p) => {
  540. p.$element.on(this.namespace + `::move`, () => {
  541. that.value = that.get();
  542. if (!that.initialized || that.updating) {
  543. return false;
  544. }
  545. that._trigger('change', that.value);
  546. return false;
  547. });
  548. });
  549. }
  550. getValueFromPosition(px) {
  551. if (px > 0) {
  552. return this.min + (px / this.getLength()) * this.interval;
  553. }
  554. return 0;
  555. }
  556. getAdjacentPointer(start) {
  557. const value = this.getValueFromPosition(start);
  558. if (this.options.range) {
  559. const p1 = this.p1.value;
  560. const p2 = this.p2.value;
  561. const diff = Math.abs(p1 - p2);
  562. if (p1 <= p2) {
  563. if (value > p1 + diff / 2) {
  564. return this.p2;
  565. }
  566. return this.p1;
  567. }
  568. if (value > p2 + diff / 2) {
  569. return this.p1;
  570. }
  571. return this.p2;
  572. }
  573. return this.p1;
  574. }
  575. getLength() {
  576. if (this.options.direction === 'v') {
  577. return this.$wrap.height();
  578. }
  579. return this.$wrap.width();
  580. }
  581. update(options) {
  582. this.updating = true;
  583. $.each(['max', 'min', 'step', 'limit', 'value'], (key, value) => {
  584. if (options[value]) {
  585. this[value] = options[value];
  586. }
  587. });
  588. if (options.max || options.min) {
  589. this.setInterval(options.min, options.max);
  590. }
  591. if (!options.value) {
  592. this.value = options.min;
  593. }
  594. $.each(this.components, (key, value) => {
  595. if (typeof value.update === "function") {
  596. value.update(this);
  597. }
  598. });
  599. this.set(this.value);
  600. this._trigger('update');
  601. this.updating = false;
  602. }
  603. get() {
  604. const value = [];
  605. $.each(this.pointer, (i, p) => {
  606. value[i] = p.get();
  607. });
  608. if (this.options.range) {
  609. return value;
  610. }
  611. if (value[0] === this.options.min) {
  612. if (typeof this.options.replaceFirst === 'string') {
  613. value[0] = this.options.replaceFirst;
  614. }
  615. if (typeof this.options.replaceFirst === 'object') {
  616. for (const key in this.options.replaceFirst) {
  617. if(Object.hasOwnProperty(this.options.replaceFirst, key)){
  618. value[0] = key;
  619. }
  620. }
  621. }
  622. }
  623. return value[0];
  624. }
  625. set(value) {
  626. if (this.options.range) {
  627. if (typeof value === 'number') {
  628. value = [value];
  629. }
  630. if (!$.isArray(value)) {
  631. return;
  632. }
  633. $.each(this.pointer, (i, p) => {
  634. p.set(value[i]);
  635. });
  636. } else {
  637. this.p1.set(value);
  638. }
  639. this.value = value;
  640. }
  641. val(value) {
  642. if (value) {
  643. this.set(value);
  644. return this;
  645. }
  646. return this.get();
  647. }
  648. setInterval(start, end) {
  649. this.min = start;
  650. this.max = end;
  651. this.interval = end - start;
  652. }
  653. enable() {
  654. this.disabled = false;
  655. this.$wrap.removeClass(`${this.namespace}_disabled`);
  656. this._trigger('enable');
  657. return this;
  658. }
  659. disable() {
  660. this.disabled = true;
  661. this.$wrap.addClass(`${this.namespace}_disabled`);
  662. this._trigger('disable');
  663. return this;
  664. }
  665. destroy() {
  666. $.each(this.pointer, (i, p) => {
  667. p.destroy();
  668. });
  669. this.$wrap.destroy();
  670. this._trigger('destroy');
  671. }
  672. static registerComponent(component, methods) {
  673. components[component] = methods;
  674. }
  675. static setDefaults(options) {
  676. $.extend(DEFAULTS, $.isPlainObject(options) && options);
  677. }
  678. }
  679. asRange.registerComponent('scale', scale);
  680. asRange.registerComponent('selected', selected);
  681. asRange.registerComponent('tip', tip);
  682. keyboard();
  683. var info = {
  684. version:'0.3.4'
  685. };
  686. const NAMESPACE = 'asRange';
  687. const OtherAsRange = $.fn.asRange;
  688. const jQueryAsRange = function(options, ...args) {
  689. if (typeof options === 'string') {
  690. const method = options;
  691. if (/^_/.test(method)) {
  692. return false;
  693. } else if ((/^(get)$/.test(method)) || (method === 'val' && args.length === 0)) {
  694. const instance = this.first().data(NAMESPACE);
  695. if (instance && typeof instance[method] === 'function') {
  696. return instance[method](...args);
  697. }
  698. } else {
  699. return this.each(function() {
  700. const instance = $.data(this, NAMESPACE);
  701. if (instance && typeof instance[method] === 'function') {
  702. instance[method](...args);
  703. }
  704. });
  705. }
  706. }
  707. return this.each(function() {
  708. if (!$(this).data(NAMESPACE)) {
  709. $(this).data(NAMESPACE, new asRange(this, options));
  710. }
  711. });
  712. };
  713. $.fn.asRange = jQueryAsRange;
  714. $.asRange = $.extend({
  715. setDefaults: asRange.setDefaults,
  716. noConflict: function() {
  717. $.fn.asRange = OtherAsRange;
  718. return jQueryAsRange;
  719. }
  720. }, info);