Javascript 清除焦点上的默认值、显示格式化的数字并将转换后的数据存储为可观察数据的输入?
背景 我正在尝试创建符合以下条件的用户友好输入元素:Javascript 清除焦点上的默认值、显示格式化的数字并将转换后的数据存储为可观察数据的输入?,javascript,jquery,html,knockout.js,Javascript,Jquery,Html,Knockout.js,背景 我正在尝试创建符合以下条件的用户友好输入元素: 解析并验证用户输入 当为空且未聚焦时,显示灰显提示 未聚焦时显示格式化的值 通过在聚焦时删除格式设置,最大限度地减少不必要的击键和鼠标单击 在内部存储数值,用于数学计算 我曾尝试使用自定义的击出绑定处理程序实现上述功能,但在重用此逻辑以在span元素中显示格式化输出时,我似乎陷入了困境 问题 在下面的提琴中,我定义了一个“数字”绑定。例如, 此绑定使用事件处理来实现上述标准,将原始JavaScript数字写入可观察对象,并将格式化字符串写
- 解析并验证用户输入
- 当为空且未聚焦时,显示灰显提示
- 未聚焦时显示格式化的值
- 通过在聚焦时删除格式设置,最大限度地减少不必要的击键和鼠标单击
- 在内部存储数值,用于数学计算
此绑定使用事件处理来实现上述标准,将原始JavaScript数字写入可观察对象,并将格式化字符串写入输入元素的值
不幸的是,这也让我在如何最好地显示输出方面有点困惑:
- 使用
不起作用,因为数字处理程序被硬编码为更新元素的值 - 使用
意味着我将丢失写入数字处理程序的格式规则 - 使用
会中断句子中的文本流,并且由于显示不可用的输入,似乎会导致不直观的用户体验
注意:虽然本例侧重于数字输入,但我最终还是希望对文本和数据输入也进行同样的操作。您需要有一个可观察的对象,该对象位于支持值前面,以处理格式化、取消格式化 最好的方法是通过subscribablefn扩展点创建一个包装器 对fiddle HTML的唯一更改是更改2个文本绑定
<span data-bind="text: waterUsed.formattedValue">
<span data-bind="text: discount.formattedValue">
我想我会坚持你的包装解决方案,我不太喜欢覆盖库函数。。。我忘了提到我需要IE8兼容性,所以我没有占位符。。。但谷歌搜索确实找到了这个链接,似乎也得到了类似的结果:
ko.subscribable.fn['asFormattedNumber'] = function (defaultValue, options) {
var target = this;
var prefix = options.prefix || '';
var postfix = options.postfix || '';
var decimals = options.decimals || 0;
var isFixed = options.isFixed || false;
var roundFactor = Math.pow(10, decimals);
// Very basic - Doesn't assume any number format
var valueExtractor = new RegExp( '^' + (prefix ? '\\' + prefix : '' ) + '([0-9\\.\\,]+)' + (postfix ? '\\' + postfix : '' ) + '$' );
// Extracts the number portion a formatted string
var unformatter = function( value ) {
// If not a match, just return the value
return (value.match(valueExtractor) || ['', value])[1];
};
// Formats the value according to options
var formatter = function(value) {
// If no value, return empty string. Important to tell the difference
// for when the default value is entered into the input box
if ( value === undefined || value === null ) {
return '';
}
return prefix + value.toFixed(decimals) + postfix;
};
// This is the observable the world will see
var wrapperObs = ko.observable();
// If true, formatted value will be blank and placeholder should be shown
var wrapperIsEmpty = true;
// Flag to stop recursion
var wrappedIsBeingSet = false;
// Check if the target observable is writeable. If it isn't then our wrapper can never be set,
// so no point in setting up a subscription on the wrapperObs.
if ( ko.isWriteableObservable(target) ) {
wrapperObs.subscribe(function(newValue) {
wrappedIsBeingSet = true;
if ( newValue === '' ) {
wrapperIsEmpty = true;
target(defaultValue);
return;
}
var unformattedValue = unformatter(newValue);
var parsed = parseFloat(unformattedValue);
if ( isNaN(parsed) && target() === defaultValue ) {
wrapperObs('');
return;
}
if ( isFixed ) {
parsed = Math.round( parsed * roundFactor ) / roundFactor;
}
if ( parsed !== target() ){
target(parsed);
}
wrapperObs( formatter(parsed ));
});
}
target.subscribe(function(newValue) {
// Handles situations where input is empty and resets target to defaultValue;
if ( !wrappedIsBeingSet ) {
var formattedValue = formatter(newValue)
wrapperObs(formattedValue);
}
wrapperIsBeingSet = false;
});
// Initialise initial state
if ( target() === undefined ) {
wrapperObs('');
} else {
target.notifySubscribers(target());
}
// Add stuff to the public observable.
wrapperObs.value = target;
wrapperObs.placeholder = formatter(defaultValue);
wrapperObs.unformattedValue = ko.computed( function() {
return wrapperIsEmpty && target() === defaultValue ? '' : target();
});
wrapperObs.formattedValue = ko.computed( function() {
return formatter(target());
});
return wrapperObs;
}
// custom knockout binding for managing formatted numeric inputs such as dollars, kilolitres & percentages
ko.bindingHandlers.number = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var $el = $(element),
obsValue = valueAccessor();
$el.attr('placeholder', obsValue.placeholder);
// prepare input field for editing by removing unneccessary characters (dollar signs, etc)
$el.focus(function () {
this.value = obsValue.unformattedValue();
$el.attr('placeholder', '');
});
// restore proper input field format (showing dollars signs, etc)
$el.blur(function () {
this.value = obsValue();
$el.attr('placeholder', obsValue.placeholder);
});
return ko.bindingHandlers['value'].init(element, valueAccessor, allBindings, viewModel, bindingContext);
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
ko.bindingHandlers['value'].update(element, valueAccessor, allBindings, viewModel, bindingContext);
}
};
function ViewModel() {
var self = this;
self.waterUsed = ko.observable().asFormattedNumber(0, {
postfix: " KL",
decimals: 3
});
self.price = ko.observable().asFormattedNumber(0, {
prefix: "$",
decimals: 3,
isFixed: true
});
self.discount = ko.observable().asFormattedNumber(0, {
postfix: "%",
decimals: 0
});
self.grossCost = ko.computed(function () {
return self.waterUsed.value() * self.price.value();
}).asFormattedNumber(0, {
prefix: "$",
decimals: 2,
isFixed: true
});
self.netCost = ko.computed(function () {
return self.grossCost.value() - (self.grossCost.value() * (self.discount.value() / 100));
}).asFormattedNumber(0, {
prefix: "$",
decimals: 2,
isFixed: true
});
}
var viewModel = new ViewModel();
ko.applyBindings(viewModel);