要求:输入的是xxx.xx元
,后台保存的是xxxxx分
。并添加表单验证。
基本思路
在上面的描述中,包含了两个价格,一个是输入
的价格,也就是我们能看到的价格;另一个就是实际
传给后台的价格。当输入一个价格的时候,指令监听到价格发生变化,然后对输入的价格乘100
,将元
转换成分
。
指令实现
确定绑定的数据
根据要求,第一个必须绑定的数据就是ng-model
中的值,也就是最后传给后台的数据。
另外,需要实现表单验证,所以required
属性是必须的,然后有时我们还要给用户一些提示信息,提示这是一个必填项,所以还需要name
。
scope: {
ngModel: '=', // 绑定的价格(以`分`为单位)
name: '@?', // 输入框name属性,使用‘@’,获取name属性的值
required: '=?' // 表单验证,默认为true;如果不想进行验证,将其设置为false
}
由于name
和required
都不是必须设置的属性,所以使用了?
。
主功能实现
首先我们要初始化一个显示在input框
的价格:
scope.price = undefined;
但是考虑到编辑的时候,会从后台获取一个已经存在的价格,这个时候我们还要将其显示出来,所以修改一下:
scope.price = scope.ngModel / 100
接下来就是对我们刚刚定义的价格进行监听了,当其发生变化的时候,对其进行单位转换处理。
// 监听价格
scope.$watch('price', function(newValue) {
if (newValue) {
scope.ngModel = newValue * 100; // 将‘元’转换成‘分’
}
});
由于在监听的时候,当删除所有输入后,price
的值就是undefined
了,也就是newValue
是undefined
,所以这时ngModel
中还是上一次的值。所以要做一下清空处理。
scope.$watch('price', function(newValue) {
if (newValue) {
scope.ngModel = newValue * 100; // 将‘元’转换成‘分’
}
// 防止删除所有的输入后,ngModel还有值
if (typeof(scope.price) === 'undefined') {
scope.ngModel = 0;
}
});
这个时候我们的效果就是这样的:
再测试一下编辑时的效果:
出问题了,实际传给后台的价格有,但是并没有显示成元,也就是说我们设置的初始值没有生效。
初始化之前打印一下ngModel
:
造成这个的原因是因为angular
在进行渲染的时候很快,当我们定义price
的时候,ngModel
的值还么有绑定过来,所以这里就是undefined
了。
解决办法就是让他延迟一会再进行初始化。
$timeout(function() {
scope.price = scope.ngModel / 100; // 实际显示的价格(以’元‘为单位)
}, 100);
再来看下编辑的效果:
验证的实现
最开始我们在绑定的时候选择了两个属性:name
和required
然后我们将他绑定在输入框上:
在指令中用一下:
然后看看验证的效果:
功能补充
从上面的效果我们可以看到,还没有真正的满足我们对价格输入的期待:
1.控制两位小数
2.不能出现输入多个小数点的情况
所以,为了使用户的体验更加好,这里还需要做一点改进,对价格做一下格式化。
显示价格的格式化
首先我们要控制价格显示两位小数,大概要按照下面的思路来处理:
1.获取小数点的位置
2.获取小数点后面的部分
- 如果没有后面的部分,添加
00
- 如果后面的部分只有一位,添加一个
0
- 如果后面的部分有两位及以上,截取到两位的部分
所以按照上面的思路,我们就能实现普遍的金钱显示的效果。然后再考虑小数点的问题。
因为为了使用户输入的时候能够显示xx.00
的样子,所以我们在前台显示的时候,就必须将price
的类型换成是字符串,而一旦我们的类型是字符串,就意味着用户可以输入不止一个小数点,我们要做的就是禁止用户输入多个小数点。
接着上面的思路继续:
3.从第一个小数点后面的部分,获取第二个小数点的位置。
- 如果位置下标为负数,说明
第一个小数点
后面的部分是一个整数
,不存在第二个小数点
- 如果下标不为负数,说明
第一个小数点
后面的部分存在第二个小数点
,我们只保留到第二个小数点
前面的那部分
按照这个思路,我们可以整理出下面的代码:
self.format = function(price) {
// 获取小数点后的数字,并计算长度
var firstPoint = price.indexOf("."); // 获取小数点的位置
if (firstPoint >= 0) {
var mantissa = price.slice(firstPoint + 1); // 获取小数点后面的部分
var secondPoint = mantissa.indexOf("."); // 获取第二个小数的位置,防出现输入两个及以上的小数点的情况
if (secondPoint < 0) {
// 如果小数点后超过两位,去掉后面的
if (mantissa.length >= 2) {
return price.slice(0, firstPoint + 3);
} else if (mantissa.length === 1) {
return price + '0';
}
} else {
// 去除第二个小数点
return price.slice(0, firstPoint + secondPoint + 1);
}
} else {
return price + '.00';
}
};
这里小数点的格式化完成了,但是又出现了另一个问题,就是我们什么时候调用的问题。最开始,我是在监听ngModel
变化的时候去调用这个函数。大部分的功能都是没问题的,就是控制小数点不行。
上面已经说了,输入的价格price
是一个字符串,这时候如果我们连着输入两个小数点。比如12..
,当我们输入12
的时候,ngModel
的值是12
,;当我们输入12.
的时候,ngModel
还是12
;当输入两个点12..
的时候,还是12
,ngModel
的值没变,所以不会触发格式化的函数,也就控制不了小数点的个数。同样的,当我们小数点后面连续输入多个0
也是这个道理。
所以就在监听价格的时候,去触发格式化的函数。
// 监听价格
self.watchPrice = function(newValue) {
if (newValue) {
var point = newValue.indexOf("."); // 获取小数点的位置
// 防止在输入的时候,当出现输入一个整数的时候,会自动补全小数点
// 所以这里只有在小数点后的位数超过两位的时候
// 或者出现两个小数点的时候才触发格式化函数
if (point >= 0) {
var mantissa = newValue.slice(point + 1);
var secondPoint = mantissa.indexOf(".");
if (mantissa.length >= 2 || secondPoint >= 0) {
scope.price = self.format(newValue);
}
} else {
scope.price = newValue;
}
scope.ngModel = parseFloat(scope.price) * 100; // 将‘元’转换成‘分’
}
...
}
效果图:
完整代码:
angular.module('webappApp')
.directive('yunzhiPrice', function($timeout) {
return {
// 独立scope
scope: {
ngModel: '=', // 绑定的价格(以`分`为单位)
name: '@?', // 输入框name属性,使用‘@’,获取name属性的值
required: '=?' // 表单验证,默认为true;如果不想进行验证,将其设置为false
},
templateUrl: '/views/directive/yunzhiPrice.html',
restrict: 'E',
link: function postLink(scope) {
var self = this;
// 初始化
self.init = function() {
// 这里由于最开始渲染的时候,ngModel还没有值,所以延迟一会再进行赋初值
$timeout(function() {
scope.price = self.format((scope.ngModel / 100).toString()); // 实际显示的价格(以’元‘为单位)
}, 100);
scope.$watch('price', self.watchPrice);
scope.$watch('ngModel', self.watchModel);
};
// 默认进行验证
if (typeof(scope.required) === 'undefined') {
scope.required = true;
}
// 监听价格
self.watchPrice = function(newValue) {
if (newValue) {
var point = newValue.indexOf("."); // 获取小数点的位置
// 防止在输入的时候,当出现输入一个整数的时候,会自动补全小数点
// 所以这里只有在小数点后的位数超过两位的时候
// 或者出现两个小数点的时候才触发格式化函数
if (point >= 0) {
var mantissa = newValue.slice(point + 1);
var secondPoint = mantissa.indexOf(".");
if (mantissa.length >= 2 || secondPoint >= 0) {
scope.price = self.format(newValue);
}
} else {
scope.price = newValue;
}
scope.ngModel = parseFloat(scope.price) * 100; // 将‘元’转换成‘分’
}
// 防止删除所有的输入后,ngModel还有值
if (typeof(scope.price) === 'undefined') {
scope.ngModel = 0;
}
};
// 监听ngModel
self.watchModel = function(newValue) {
if (newValue) {
scope.price = (newValue / 100).toString();
}
};
// 价格格式化函数
// 整数:在后面添加’.00‘
// 一位小数:添加一个0
// 两位小数: 不做改变
// 两位以上:只截取两位的部分
self.format = function(price) {
// 获取小数点后的数字,并计算长度
var firstPoint = price.indexOf("."); // 获取小数点的位置
if (firstPoint >= 0) {
var mantissa = price.slice(firstPoint + 1);
var secondPoint = mantissa.indexOf("."); // 获取第二个小数的位置,防出现输入两个及以上的小数点的情况
if (secondPoint < 0) {
// 如果小数点后超过两位,去掉后面的
if (mantissa.length >= 2) {
return price.slice(0, firstPoint + 3);
} else if (mantissa.length === 1) {
return price + '0';
}
} else {
// 去除第二个小数点
return price.slice(0, firstPoint + secondPoint + 1);
}
} else {
return price + '.00';
}
};
self.init();
}
};
});
总结
每一个复杂的问题,只要将其拆分成简单的小问题,都会变得简单。