numinput.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. /**
  2. * Layui 数字输入组件
  3. *
  4. * @author iTanken
  5. * @since 2019-03-29
  6. * @version 2020-01-19:数字键盘纵向定位自适应
  7. * @version 2020-04-02:添加功能按钮悬浮提示开关参数;添加悬浮提示内部键盘按钮样式;修复 number 类型输入框小数输入问题
  8. */
  9. layui.define(['jquery'], function(exports) {
  10. var $ = layui.$, baseClassName = 'layui-input-number', keyClassName = 'layui-keyboard-number',
  11. style = ['<style type="text/css">',
  12. '.', baseClassName, ' + .', keyClassName, ' { position: absolute; display: block; ',
  13. ' background-color: #f2f2f2; border-radius: 2px; border: 1px solid #e6e6e6; outline: none; }',
  14. '.', keyClassName, ' .layui-key-btn { font-family: Consolas; font-size: 17px; font-weight: 600; ',
  15. ' text-align: center; background-color: #ffffff; cursor: pointer; overflow: hidden; padding: 10px; }',
  16. '.', keyClassName, ' .layui-key-btn:active { background-color: #f2f2f2; }',
  17. '.layui-layer-tips kbd { display: inline-block; padding: 3px 5px; font-size: 11px;',
  18. ' line-height: 10px; color: #24292e; vertical-align: middle; background-color: #fafbfc;',
  19. ' border: 1px solid #d1d5da; border-radius: 3px; box-shadow: inset 0 -1px 0 #d1d5da; }',
  20. '</style>'].join('');
  21. $('head link:last')[0] && $('head link:last').after(style) || $('head').append(style);
  22. var numberInput = {
  23. /** 默认配置选项 */
  24. options: {
  25. // 123:123键置顶, 789:789键置顶
  26. topBtns: 123,
  27. // 右侧功能按钮
  28. rightBtns: true,
  29. // 功能按钮提示
  30. showTips: true,
  31. // 监听键盘事件
  32. listening: true,
  33. // 批量配置默认小数精确度,-1 不处理精确度
  34. defaultPrec: -1,
  35. // 初始化回调,无参
  36. initEnd: $.noop,
  37. // 触发显示回调,参数为当前输入框和数字键盘的 jQuery 对象
  38. showEnd: $.noop,
  39. // 隐藏键盘回调,参数为当前输入框的 jQuery 对象
  40. hideEnd: $.noop,
  41. // z-index
  42. zIndex: 19999999
  43. },
  44. /** 初始化 */
  45. init: function(custom) {
  46. var _this = this;
  47. _this.options = $.extend(_this.options, custom);
  48. $('.' + baseClassName).attr({
  49. "readonly": "readonly"
  50. }).on('focus', function(e) {
  51. _this.showKeyboard(_this, $(this));
  52. });
  53. typeof _this.options.initEnd === 'function' && _this.options.initEnd();
  54. },
  55. /** 获取按键悬浮提示 */
  56. getTips: function(tip) {
  57. return this.options.showTips ? (' lay-tips="' + tip + '"') : '';
  58. },
  59. /** 显示数字键盘 */
  60. showKeyboard: function(_this, $input) {
  61. // 2020-04-02:修复小数输入问题,将 number 类型输入框一律设置为 text
  62. $input.prop('type') === 'number' && $input.attr('type', 'text');
  63. var $keyBoard = $input.next('.' + keyClassName);
  64. if (!$keyBoard[0]) {
  65. // 不存在,添加元素
  66. var sizeXS = _this.options.rightBtns ? 'xs3' : 'xs4',
  67. sizeZero = _this.options.rightBtns ? 'xs6' : 'xs4',
  68. // 按钮 123
  69. btn123 = [
  70. '<div class="layui-col-', sizeXS, '">',
  71. '<div class="layui-card">',
  72. '<div class="layui-key-btn" data-keycode="49 97">1</div>',
  73. '</div>',
  74. '</div>',
  75. '<div class="layui-col-', sizeXS, '">',
  76. '<div class="layui-card">',
  77. '<div class="layui-key-btn" data-keycode="50 98">2</div>',
  78. '</div>',
  79. '</div>',
  80. '<div class="layui-col-', sizeXS, '">',
  81. '<div class="layui-card">',
  82. '<div class="layui-key-btn" data-keycode="51 99">3</div>',
  83. '</div>',
  84. '</div>'
  85. ].join(''),
  86. // 按钮 789
  87. btn789 = [
  88. '<div class="layui-col-', sizeXS, '">',
  89. '<div class="layui-card">',
  90. '<div class="layui-key-btn" data-keycode="55 103">7</div>',
  91. '</div>',
  92. '</div>',
  93. '<div class="layui-col-', sizeXS, '">',
  94. '<div class="layui-card">',
  95. '<div class="layui-key-btn" data-keycode="56 104">8</div>',
  96. '</div>',
  97. '</div>',
  98. '<div class="layui-col-', sizeXS, '">',
  99. '<div class="layui-card">',
  100. '<div class="layui-key-btn" data-keycode="57 105">9</div>',
  101. '</div>',
  102. '</div>'
  103. ].join(''),
  104. /* 退格键 */
  105. backspace = [
  106. '<div class="layui-col-', sizeXS, '">',
  107. '<div class="layui-card">',
  108. '<div class="layui-key-btn" data-keycode="8">',
  109. '<i class="layui-icon layui-icon-return"', _this.getTips('退格 <kbd>Backspace</kbd>'), '></i>',
  110. '</div>',
  111. '</div>',
  112. '</div>'
  113. ].join(''),
  114. /* 增加键 */
  115. add = [
  116. '<div class="layui-col-', sizeXS, '">',
  117. '<div class="layui-card">',
  118. '<div class="layui-key-btn" data-keycode="38 39">',
  119. '<i class="layui-icon layui-icon-up"', _this.getTips('增加 <kbd>↑</kbd>'), '></i>',
  120. '</div>',
  121. '</div>',
  122. '</div>'
  123. ].join(''),
  124. /* 减小键 */
  125. reduce = [
  126. '<div class="layui-col-', sizeXS, '">',
  127. '<div class="layui-card">',
  128. '<div class="layui-key-btn" data-keycode="37 40">',
  129. '<i class="layui-icon layui-icon-down"', _this.getTips('减小 <kbd>↓</kbd>'), '></i>',
  130. '</div>',
  131. '</div>',
  132. '</div>'
  133. ].join(''),
  134. /* 清空键 */
  135. reset = [
  136. '<div class="layui-col-', sizeXS, '">',
  137. '<div class="layui-card">',
  138. '<div class="layui-key-btn" data-keycode="46">',
  139. '<i class="layui-icon layui-icon-refresh-1"', _this.getTips('清空 <kbd>Delete</kbd>'), '></i>',
  140. '</div>',
  141. '</div>',
  142. '</div>'
  143. ].join('');
  144. $input.after(['<div tabindex="0" hidefocus="true" class="', keyClassName,
  145. ' layui-unselect layui-anim layui-anim-upbit" ',
  146. 'style="width:', $input.width() + 10, 'px;">',
  147. '<div class="layui-row layui-col-space1">',
  148. _this.options.topBtns == 789 ? btn789 : btn123,
  149. _this.options.rightBtns ? backspace : '',
  150. '<div class="layui-col-', sizeXS, '">',
  151. '<div class="layui-card">',
  152. '<div class="layui-key-btn" data-keycode="52 100">4</div>',
  153. '</div>',
  154. '</div>',
  155. '<div class="layui-col-', sizeXS, '">',
  156. '<div class="layui-card">',
  157. '<div class="layui-key-btn" data-keycode="53 101">5</div>',
  158. '</div>',
  159. '</div>',
  160. '<div class="layui-col-', sizeXS, '">',
  161. '<div class="layui-card">',
  162. '<div class="layui-key-btn" data-keycode="54 102">6</div>',
  163. '</div>',
  164. '</div>',
  165. _this.options.rightBtns ? add : '',
  166. _this.options.topBtns == 789 ? btn123 : btn789,
  167. _this.options.rightBtns ? reduce : '',
  168. _this.options.rightBtns ? '' : backspace,
  169. '<div class="layui-col-', sizeZero, '">',
  170. '<div class="layui-card">',
  171. '<div class="layui-key-btn" data-keycode="48 96">0</div>',
  172. '</div>',
  173. '</div>',
  174. '<div class="layui-col-', sizeXS, '">',
  175. '<div class="layui-card">',
  176. '<div class="layui-key-btn" data-keycode="110 190">.</div>',
  177. '</div>',
  178. '</div>',
  179. _this.options.rightBtns ? reset : '',
  180. '</div>',
  181. '</div>'].join(''));
  182. $keyBoard = $input.next('.' + keyClassName);
  183. $keyBoard.on('touchstart click', '.layui-key-btn', function(e) {
  184. _this.setValue(_this, $input, $(this));
  185. layui.stope(e);
  186. return false;
  187. });
  188. $keyBoard.on('blur', function(e) {
  189. _this.setValueRange(_this, $input, _this.toFixedPrec(_this, $input));
  190. $keyBoard.remove(); // $keyBoard.hide();
  191. typeof _this.options.hideEnd === 'function' && _this.options.hideEnd($input);
  192. });
  193. _this.options.listening && _this.initKeyListening(_this, $input, $keyBoard);
  194. }
  195. _this.display(_this, $input, $keyBoard);
  196. },
  197. /** 设置数字键盘样式并显示 */
  198. display: function(_this, $input, $keyBoard) {
  199. var showTop = $input[0].offsetTop + $input[0].offsetHeight + 4, boardHeight = $keyBoard.height(),
  200. $win = $(window), topOffset = $keyBoard.offset().top + $keyBoard.outerHeight() + 4 - $win.scrollTop();
  201. // 数字键盘纵向定位自适应
  202. if (topOffset + boardHeight > $win.height() && topOffset >= boardHeight) {
  203. showTop = $input[0].offsetTop - boardHeight - 5;
  204. }
  205. $keyBoard.css({
  206. 'top': showTop + 'px',
  207. 'left': $input[0].offsetLeft + 'px',
  208. 'z-index': _this.options.zIndex
  209. });
  210. $keyBoard.show(200, function() {
  211. typeof _this.options.showEnd === 'function' && _this.options.showEnd($input, $keyBoard);
  212. }).focus();
  213. },
  214. /** 初始化键盘监听事件 */
  215. initKeyListening: function(_this, $input, $keyBoard) {
  216. var $key, code;
  217. $keyBoard.on('keydown', function(e) {
  218. code = e.keyCode;
  219. var inputNumber = parseInt($input.val(), 10) || 0;
  220. if (code === 107 || e.shiftKey && code === 187) {
  221. // 加号切换正数
  222. inputNumber < 0 && _this.setValueRange(_this, $input, Math.abs(inputNumber));
  223. } else if (code === 109 || e.shiftKey && code === 189) {
  224. // 减号切换负数
  225. inputNumber > 0 && _this.setValueRange(_this, $input, '-' + inputNumber);
  226. } else {
  227. // 监听数字键盘,退格键(Backspace)/重置键(Delete)
  228. $key = $keyBoard.find('.layui-key-btn[data-keycode~=' + code + ']');
  229. if ($key[0]) {
  230. $key.trigger('click').css("background-color", "#f2f2f2");
  231. $keyBoard.off('keyup').on('keyup', function(e) {
  232. $('.layui-key-btn[data-keycode]').css("background-color", "#ffffff");
  233. });
  234. }
  235. if (code > 36 && code < 41) {
  236. // 上下左右键,防止触发混动条滑动事件
  237. return false;
  238. }
  239. }
  240. return true;
  241. });
  242. },
  243. /** 处理精确度 */
  244. toFixedPrec: function(_this, $input, val1, val2) {
  245. var m, s, rs, prec = $.trim($input.data('prec'));
  246. // 2020-04-02:修复获取小数精确度配置值问题
  247. prec = parseInt(prec === '' || isNaN(prec) ? _this.options.defaultPrec : prec, 10);
  248. val1 = val1 === undefined ? $input.val() : val1;
  249. val1 = val1 == '' ? ($input.attr('min') || 0) : val1;
  250. rs = val1.toString().split('.')[1];
  251. if (prec < 0) {
  252. prec = rs && rs.length || prec;
  253. }
  254. val2 = val2 || 0;
  255. rs = val2.toString().split('.')[1];
  256. prec = Math.max(prec, rs ? rs.length : 0);
  257. m = Math.pow(10, prec);
  258. s = ((val1 * m + val2 * m).toFixed(0) / m).toString();
  259. rs = s.indexOf('.');
  260. if (rs < 0 && prec > 0) {
  261. rs = s.length;
  262. s += '.';
  263. }
  264. while (s.length <= rs + prec) {
  265. s += '0';
  266. }
  267. return s;
  268. },
  269. /** 设置值范围 */
  270. setValueRange: function(_this, $input, value) {
  271. var minVal = $input.attr('min') || Math.pow(-2, 63),
  272. maxVal = $input.attr('max') || Math.pow(2, 63) - 1;
  273. minVal = typeof minVal === 'string' && minVal.indexOf('.') > -1 ? parseFloat(minVal) : parseInt(minVal, 10);
  274. maxVal = typeof maxVal === 'string' && maxVal.indexOf('.') > -1 ? parseFloat(maxVal) : parseInt(maxVal, 10);
  275. if (value < minVal) {
  276. value = _this.toFixedPrec(_this, $input, minVal);
  277. _this.tips($input, '最小值为 <kbd>' + minVal + '</kbd>!');
  278. }
  279. if (value > maxVal) {
  280. value = _this.toFixedPrec(_this, $input, maxVal);
  281. _this.tips($input, '最大值为 <kbd>' + maxVal + '</kbd>!');
  282. }
  283. value = value < minVal ? minVal : (value > maxVal ? maxVal : value);
  284. $input.val(value);
  285. },
  286. /** 设置输入框值 */
  287. setValue: function(_this, $input, $key) {
  288. var inputVal = $.trim($input.val()), keyVal = $.trim($key.text()), changeVal,
  289. prec = $.trim($input.data('prec')), isDecimal = inputVal.indexOf('.') > -1;
  290. // 2020-04-02:修复获取小数精确度配置值问题
  291. prec = parseInt(prec === '' || isNaN(prec) ? _this.options.defaultPrec : prec, 10);
  292. if ($.inArray(keyVal, ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.']) > -1) {
  293. if (keyVal === '.') {
  294. if (inputVal === '' || isDecimal) {
  295. return;
  296. }
  297. if (prec === 0) {
  298. _this.tips($input, '当前字段不允许输入小数!');
  299. return;
  300. }
  301. }
  302. if (keyVal === '0' && inputVal.indexOf('0') === 0 && !isDecimal) {
  303. return;
  304. }
  305. if (inputVal.indexOf('.') > -1 && inputVal.split('.')[1].length >= prec && prec > 0) {
  306. _this.tips($input, '精确度为保留小数点后 <kbd>' + prec + '</kbd> 位!');
  307. return;
  308. }
  309. changeVal = inputVal = (keyVal !== '.' && inputVal === '0' ? '' : inputVal) + keyVal;
  310. $input.val(inputVal);
  311. } else {
  312. changeVal = inputVal === '' ? 0 : inputVal,
  313. step = $input.attr('step');
  314. if (isDecimal) {
  315. step = parseFloat(step) || 0.1;
  316. changeVal = parseFloat(changeVal);
  317. } else {
  318. step = parseInt(step, 10) || 1;
  319. changeVal = parseInt(changeVal, 10);
  320. }
  321. // right function buttons
  322. switch($key.data('keycode')) {
  323. case '38 39':
  324. // ↑、→ 键增加
  325. changeVal = _this.toFixedPrec(_this, $input, changeVal, step);
  326. break;
  327. case '37 40':
  328. // ↓、← 键减小
  329. changeVal = _this.toFixedPrec(_this, $input, changeVal, -step);
  330. break;
  331. case 8:
  332. // Backspace 键退格
  333. var valLength = inputVal.length;
  334. valLength && $input.val(inputVal.substring(0, valLength - 1));
  335. return;
  336. case 46:
  337. // Delete 键清空
  338. $input.val('');
  339. return;
  340. }
  341. }
  342. $input.val(changeVal);
  343. },
  344. /** 提示 */
  345. tips: function($input, msg) {
  346. return layer.tips(msg, $input, { tips: [1, '#01AAED'], time: 2e3, anim: 6, zIndex: this.options.zIndex });
  347. }
  348. };
  349. exports('numinput', numberInput);
  350. });