限制小數位數輸入框 Directive 實作
Fast-Input Directive which provide k,m converting function with changeable decimal place.
前言
在開發金融相關系統時,對於輸入數字的精確度要求會因選擇標的有所差異,因此就需要可隨意切換小數位數上限的功能;此外,金融從業人員習慣鍵入k及m來快速完成1000或1000000倍數轉換,並且數字加註千分為更是必要的顯示方式。
整理一下預計實作之功能如下:
- 輸入數字時自動加註千分位 (ex. 1,000)
- 在數字末端輸入k或m時,自動轉換數字內容 (ex. 12k => 12,000)
- 限制輸入小數位數,且可以動態即時切換。
環境
- AngularJS 1.4.7
- Chrome 46.0.2490.86
設計要點
整個Directive的設計上約略包括下列三大主軸。約略來說就是將model數值(ex. 1000.1234)轉換為view顯示文字(ex. 1,000.1234),接著在view中輸入數字時,即時動態地加註千分位以及處理輸入k或m時所需的數值轉換,並且依據傳入的小數位數上限來做輸入的限制,再將view中呈現的文字(ex. 1,000.1234)轉換為數值(ex. 1000.1234)來存入model中;最後,別忘了要監控外部傳入的小數位數上限,這樣才可動態切換小數位數並反應相對數值。
Model to View
ctrl.$formatters.push(myFormatter);
存在於controller的$formatters是一個陣列,當Model值被異動時會被觸發依序呼叫$formatters中所有方法,來將Model數值轉換為特定顯示格式呈現於畫面上;因此我們就可以透過這個機制來自行定義數值呈現的格式,在此我們的需求很簡單,就是透過$filter('number')
來將數值加註千分位並限制小數顯示位數。片段示意代碼如下所示。
// [formatter] model -> view
function myFormatter(modelValue) {
var fractionSize = scope.$eval(attrs.fastInput);
var strValue = $filter('number')(modelValue, fractionSize);
// remove trailing zeros & dot
strValue = strValue.replace(/(\.[0-9]*?)0+$/, "$1");
strValue = strValue.replace(/\.$/, "");
return strValue;
}
View to Model
ctrl.$parsers.push(myParser);
存在於controller的$parsers是一個陣列,當View值被異動時會被觸發依序呼叫$parsers中所有方法,來將畫面上輸入的文字轉換為特定數值存入Model中;因此我們除了可以透過這個機制來自行定義數值轉換邏輯外,並可以在輸入當下一併控制畫面上需要呈現的文字,限制使用者輸入小數位數之上限。片段示意代碼如下所示。
// [parser] view -> model
function myParser(viewValue) {
var strViewValue = null;
var numModelValue = null;
if (viewValue) {
try {
// get decimal place
var fractionSize = scope.$eval(attrs.fastInput);
// remove thousand separator
strViewValue = viewValue.toString().replace(/\,/g, '');
// convert k, m to *1000, *1000000
strViewValue = getConvertedStrValue(strViewValue);
// process decimal number
var numParts = strViewValue.split(".");
var integerNum = numParts[0] ? parseInt(numParts[0]).toLocaleString() : '0';
var decimalNum = numParts[1] ? numParts[1].substring(0, fractionSize) : '';
// get view value
strViewValue =
integerNum + (numParts.length > 1 ? '.' : '') + ((decimalNum) ? decimalNum : '');
// get model value
numModelValue =
parseFloat(strViewValue.toString().replace(/\,/g, ''));
} catch (e) {
strViewValue = null;
numModelValue = null;
}
}
// change view value
ctrl.$setViewValue(strViewValue);
ctrl.$render();
// return model value
return numModelValue;
}
Detect Changes
scope.$watch(target, doSomethingWhileChanging);
由於需要動態即時地切換小數位數,因此要監控傳入的小數位數(attrs.fastInput)之異動情況,當有異動時重新調整數值。片段示意代碼如下所示。
// detect outside changes and update our input
scope.$watch(attrs.fastInput, function(value) {
myParser(elem.val());
});
實作說明
首先建立 app model 與 test controller。
angular
.module('app', []);
angular
.module('app')
.controller("TestController", function() {
var vm = this;
vm.MyNum = 100.1234;
vm.MyDecimalPlace = 4;
});
接著實作fastInput directive
angular
.module('app')
.directive('fastInput', ['$filter', function($filter) {
return {
require: '?ngModel',
link: function(scope, elem, attrs, ctrl) {
if (!ctrl) return;
// [formatter] model -> view
function myFormatter(modelValue) {
var fractionSize = scope.$eval(attrs.fastInput);
var strValue = $filter('number')(modelValue, fractionSize);
// remove trailing zeros & dot
strValue = strValue.replace(/(\.[0-9]*?)0+$/, "$1");
strValue = strValue.replace(/\.$/, "");
return strValue;
}
// [parser] view -> model
function myParser(viewValue) {
var strViewValue = null;
var numModelValue = null;
if (viewValue) {
try {
// get decimal place
var fractionSize = scope.$eval(attrs.fastInput);
// remove thousand separator
strViewValue = viewValue.toString().replace(/\,/g, '');
// convert k, m to *1000, *1000000
strViewValue = getConvertedStrValue(strViewValue);
// process decimal number
var numParts = strViewValue.split(".");
var integerNum = numParts[0] ? parseInt(numParts[0]).toLocaleString() : '0';
var decimalNum = numParts[1] ? numParts[1].substring(0, fractionSize) : '';
// get view value
strViewValue =
integerNum + (numParts.length > 1 ? '.' : '') + ((decimalNum) ? decimalNum : '');
// get model value
numModelValue =
parseFloat(strViewValue.toString().replace(/\,/g, ''));
} catch (e) {
strViewValue = null;
numModelValue = null;
}
}
// change view value
ctrl.$setViewValue(strViewValue);
ctrl.$render();
// return model value
return numModelValue;
}
// convert value which inculde k, m multipliers
function getConvertedStrValue(strValue) {
if (isNaN(strValue)) {
var convertedValue = 0;
var multipliers = {k: 1000, m: 1000000};
var lastChar = strValue.charAt(strValue.length-1);
var usedMultiplier = multipliers[lastChar.toLowerCase()];
if (usedMultiplier) {
convertedValue = FloatMul(parseFloat(strValue) , usedMultiplier);
} else {
convertedValue = 0;
}
strValue = convertedValue.toString();
}
return strValue;
}
// float multiplier
function FloatMul(arg1, arg2){
var m = 0, s1 = arg1.toString(), s2 = arg2.toString();
try { m += s1.split(".")[1].length; } catch (e) { }
try { m += s2.split(".")[1].length; } catch (e) { }
return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
}
// model -> view
ctrl.$formatters.push(myFormatter);
// view -> model
ctrl.$parsers.push(myParser);
// detect outside changes and update our input
scope.$watch(attrs.fastInput, function(value) {
myParser(elem.val());
});
}
};
}]);
測試畫面HTML如下,只需要在input元素中加入fast-input attribute (fastInput Directive)即可。
<html ng-app="app">
<head>
<script data-require="angular.js@1.4.7" data-semver="1.4.7" src="https://code.angularjs.org/1.4.7/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div ng-controller="TestController as ctrl">
Please enter number: <br />
<input type="text" ng-model="ctrl.MyNum" fast-input="ctrl.MyDecimalPlace" />
<br />
<br />
Number will be: {{ctrl.MyNum}}
<br />
Decimal Place: {{ctrl.MyDecimalPlace}}
<br />
<br />
<input type="button" value="digit: 3" ng-click="ctrl.MyDecimalPlace=3">
<input type="button" value="digit: 1" ng-click="ctrl.MyDecimalPlace=1">
<input type="button" value="digit: 5" ng-click="ctrl.MyDecimalPlace=5">
</div>
</body>
</html>
結果呈現
首先測試一下輸入 k 及 m 是否可以正確轉換數值。從以下測試可以發現輸入 k 時,數值會自動乘上 1000,當輸入字尾是 m 時,數值會乘上 1000000,而最終數值是可以正確調整顯示的。
再來測試小數位數切換功能。從以下測試可以發現切換位數時,數值會正確進行調整。
參考資訊
http://www.chroder.com/2014/02/01/using-ngmodelcontroller-with-custom-directives/
http://kevintsengtw.blogspot.tw/2011/09/javascript.html
希望此篇文章可以幫助到需要的人
若內容有誤或有其他建議請不吝留言給筆者喔 !