【问题标题】:Number input box in Knockout JSKnockout JS中的数字输入框
【发布时间】:2017-01-16 14:14:37
【问题描述】:

我正在尝试创建一个仅接受数字的数字输入框。

我的初始值方法是替换值并将其重新设置为自身。

订阅方式

function vm(){
  var self = this;
  self.num = ko.observable();
  self.num.subscribe(function(newValue){
    var numReg = /^[0-9]$/;
    var nonNumChar = /[^0-9]/g;
    if(!numReg.test(newValue)){
      self.num(newValue.toString().replace(nonNumChar, ''));
    }
  })
}

ko.applyBindings(new vm())
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<input type="text" data-bind="textInput: num" />

现在这种方法有效,但会添加另一个订阅事件循环,所以我尝试使用自定义绑定,以便我只能返回更新的值。新手,我尝试了一些东西,但不知道该怎么做。以下是我的尝试,但它不起作用。它甚至没有更新 observable。

自定义绑定尝试

ko.bindingHandlers.numeric_value = {
  update: function(element, valueAccessor, allBindingsAccessor) {
    console.log(element, valueAccessor, allBindingsAccessor())
    ko.bindingHandlers.value.update(element, function() {
      var value = ko.utils.unwrapObservable(valueAccessor());
      return value.replace(/[^0-9]/g, '')
    });
  },
};

function vm() {
  this.num = ko.observable(0);
  this.num.subscribe(function(n) {
    console.log(n);
  })
}

ko.applyBindings(new vm())
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<div>
  <input type="text" data-bind="number_value: num, valueUpdate:'keyup'">
  <span data-bind="text: num"></span>
</div>

所以我的问题是,我们可以使用自定义绑定来做到这一点吗?它是否比订阅更好?


编辑 1:

根据@user3297291 的回答,ko.extenders 看起来更像是我订阅方法的通用方式。我正在寻找一种方法(如果可能在 Knockout 中),它会在值设置为可观察之前清除值。


我参考了以下文章:

注意:在第一个示例中,他们使用 jQuery 来设置值。我想避免它并且只使用淘汰赛来做到这一点

【问题讨论】:

  • 我认为自定义绑定将是订阅的更好替代方案,因为它是可重用的,因此您可以在需要相同功能的应用程序的其他地方使用它。看看这个 jsfiddle - jsfiddle.net/tdkhypnL/1 不确定这是不是你想要的。
  • @klikas 同意你的观点。我计划实现类似于textInput 但很少捆绑的东西。稍后将添加答案。此外,在您的小提琴中,字符在几分之一秒内可见,并且在某些情况下(如果输入 1 个字符),字符会被保留。
  • 你是对的,我的解决方案并不完美,但我会在喜欢的时候尝试修复它,因为它有点匆忙只是为了说明这个概念:)
  • @klikas 我已经添加了一个答案。不确定它是否是最好的选择,但我想应该改进和使用。希望对您有所帮助。

标签: javascript knockout.js


【解决方案1】:

我赞成使用扩展器作为user3297291 的回答。

扩展器是格式化或验证可观察对象的一种灵活方式,并且可重用性更高。

这是我对数字扩展器的实现

//Extender

ko.extenders.numeric = function(target, options) {
  //create a writable computed observable to intercept writes to our observable
  var result = ko.pureComputed({
    read: target, //always return the original observables value
    write: function(newValue) {
      var newValueAsNum = options.decimals ? parseFloat(newValue) : parseInt(newValue);
      var valueToWrite = isNaN(newValueAsNum) ? options.defaultValue : newValueAsNum;
      target(valueToWrite);
    }
  }).extend({
    notify: 'always'
  });

  //initialize with current value to make sure it is rounded appropriately
  result(target());

  //return the new computed observable
  return result;
};

//View Model

var vm = {
  Product: ko.observable(),
  Price: ko.observable().extend({
    numeric: {
      decimals: 2,
      defaultValue: undefined
    }
  }),
  Quantity: ko.observable().extend({
    numeric: {
      decimals: 0,
      defaultValue: 0
    }
  })
}
&lt;script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"&gt;&lt;/script&gt;

编辑

我明白你的意思,如何使用正则表达式自定义绑定使其更可重用?

类似的东西。

function regExReplace(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

  var observable = valueAccessor();
  var textToReplace = allBindingsAccessor().textToReplace || '';
  var pattern = allBindingsAccessor().pattern || '';
  var flags = allBindingsAccessor().flags;
  var text = ko.utils.unwrapObservable(valueAccessor());
  if (!text) return;
  var textReplaced = text.replace(new RegExp(pattern, flags), textToReplace);

  observable(textReplaced);
}

ko.bindingHandlers.regExReplace = {
  init: regExReplace,
  update: regExReplace
}


ko.applyBindings({
  name: ko.observable(),
  num: ko.observable()
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>


<input type="text" data-bind="textInput : name, regExReplace:name, pattern:'(^[^a-zA-Z]*)|(\\W)',flags:'g'" placeholder="Enter a valid name" />
<span data-bind="text : name"></span>
<br/>
<input class=" form-control " type="text " data-bind="textInput : num, regExReplace:num, pattern: '[^0-9]',flags: 'g' " placeholder="Enter a number " />
<span data-bind="text : num"></span>

【讨论】:

  • 关键是,第一个变量会更新,然后它会触发通知事件,你的扩展器会监听它,然后你会再次解析并设置新值,然后又会发生一个循环。我想消除这个额外的循环。
  • 我明白你的意思,请查看我的回复更新,希望对您有所帮助
  • 这非常接近我的预期。我会努力实现它
【解决方案2】:

我认为您可以将问题分为两部分:

  1. 确保用户只能输入数字,或
  2. 确保您的视图模型值是数字而不是字符串。

如果您只需要第 1 部分,我建议您使用默认的 HTML(5) 功能:

<input type="number" step="1" />
<input type="text" pattern="\d*" />

如果您想确保用户不能输入除数字以外的任何内容,并且还想在您的视图模型中使用该值,我会使用扩展器。通过扩展 observable,您可以在任何订阅被触发之前更改其值。

淘汰赛文档在他们的documentation page 上提供了一个很好的例子:

请注意,当您使用扩展器时,您无需再担心patterntype 属性;敲除会在设置后立即修改值。

ko.extenders.numeric = function(target, precision) {
  //create a writable computed observable to intercept writes to our observable
  var result = ko.pureComputed({
    read: target, //always return the original observables value
    write: function(newValue) {
      var current = target(),
        roundingMultiplier = Math.pow(10, precision),
        newValueAsNum = isNaN(newValue) ? 0 : +newValue,
        valueToWrite = Math.round(newValueAsNum * roundingMultiplier) / roundingMultiplier;

      //only write if it changed
      if (valueToWrite !== current) {
        target(valueToWrite);
      } else {
        //if the rounded value is the same, but a different value was written, force a notification for the current field
        if (newValue !== current) {
          target.notifySubscribers(valueToWrite);
        }
      }
    }
  }).extend({
    notify: 'always'
  });

  //initialize with current value to make sure it is rounded appropriately
  result(target());

  //return the new computed observable
  return result;
};

var vm = {
  changes: ko.observable(0),
  myNumber: ko.observable(0).extend({
    numeric: 1
  })
};

vm.myNumber.subscribe(function() {
  vm.changes(vm.changes() + 1);
});

ko.applyBindings(vm);
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

<input type="text" data-bind="value: myNumber">

<div>2 times <span data-bind="text: myNumber"></span> is <span data-bind="text: myNumber() * 2"></span>.</div>
<div> Changes: <span data-bind="text: changes"></span></div>

【讨论】:

  • target(valueToWrite); 会再次触发扩展器吗? pureComputed 也不期望纯函数。不知道它的影响是什么
  • 另外,使用扩展器看起来像是我的订阅方法的通用版本。我希望有一种方法可以在将值传递给 observable 之前对其进行解析
  • 另外,我的要求是用户不能输入除数字以外的任何字符。如果我将value 更改为textInput 绑定,则可以在您的示例中实现。
  • result 计算出的 observable 是你最终写入的那个。 target 没有额外订阅。通过创建一个只允许有效数据通过的中间层,您可以确保您不会在“真实”可观察对象上触发任何订阅。因此,以它自己的方式,这满足了您“在将值传递给 observable 之前解析值”的要求。
  • 我猜,我理解错了。我会再检查一次。谢谢
【解决方案3】:

使用Knockout Validation | LIVE PEN

<input data-bind="value: Number">

ko.validation.init();

function VM() {
    var self = this;
    self.Number = ko.observable().extend({
        required: true,
        pattern: {
            message: 'Invalid number.',
            params: /\d$/
        }
    });
}

ko.applyBindings(window.v=new VM());

【讨论】:

  • 这将验证数字,但允许用户输入字母。我不想要那个。另外,添加另一个库不是我想要的。
  • 好吧,我不确定 KO.Validate 是否有办法像你想要的那样避免 JQuery,同时仍然得到结果。祝你好运!
【解决方案4】:

以下是knockouttextInput 绑定的模仿,但具有自定义解析。请注意,我知道,这增加了几行额外的重复代码,但我猜它的价值。

我想创建我的自定义代码,但重新发明轮子会有很多问题,因此感谢 Knockout 团队的努力并复制它。

Numeric Only - JSFiddle.

Characters Only - JSFiddle

我在下面更新了代码

  • updateModel:仅从元素中获取解析值。这将防止更新不正确的值。
  • updateView:检查用户是否输入了错误的值。如果是,则替换之前的值,否则将之前的值更新为当前值并继续。

可用性

我试图在这个问题之外增加这个绑定的范围。我添加了一个特殊的数据属性(data-patterndata-flag)来创建正则表达式将相应地解析。

【讨论】:

    猜你喜欢
    • 2021-07-31
    • 1970-01-01
    • 2012-02-25
    • 2022-10-17
    • 2011-11-18
    • 1970-01-01
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多