【问题标题】:Using Knockout Observable Array to Add dynamic UI element使用 Knockout Observable Array 添加动态 UI 元素
【发布时间】:2016-10-05 20:55:06
【问题描述】:

这是我正在努力解决的问题。

1- 我正在使用映射插件来创建 Knockout ViewModel。从服务器端,我有一个复杂的对象,它有几个属性(与这个问题无关)。除了其他属性之外,我还有一个属性 TotalofExpenses 并且还有一个名为 Expenses 的列表(费用项目是一个具有金额和描述的对象)。映射插件将此列表转换为 ObservableArray 并将 TotalofExpenses 转换为 observalbe

2- 在 UI 我想实现以下

  • 最初我想为费用项目提供一个输入。两个输入框(一个用于金额,一个用于描述)
  • 一旦用户添加描述和费用金额(两者),我想显示另一个费用项目的输入框,并在某处显示当前费用总额(比如在表格底部)。随着用户不断添加更多费用,我不断呈现新的输入框并继续显示总费用
  • 如果用户返回之前添加的费用项目并进行更改,请说更改金额,我想更新我的总金额。

以下是我试图实现上述功能的代码。它没有按预期工作。 注意:我试图只放置与此问题相关的代码

      var data = @Html.Raw(Model.ToJson());
       //Add total expenses  TotalShiftExpenses
    var mappingOptionsForTotalShiftExpenses = {
        create: function(options) {
            return new ComputeTotalShiftExpenses(options.data);
        }
    };

    var Expense = function(data) {
        var self = this;
        self.ShiftId = ko.observable(@Model.Shift.Id),
            self.Id = ko.observable(0),
            self.Amount = ko.observable(0),
            self.Description = ko.observable("");
    }        

    function ComputeTotalShiftExpenses(data) {
        var self = this;
        var model = ko.mapping.fromJS(data, {}, self);
        model.TotalShiftExpenses = ko.computed(function() {
            var sum = 0;

            for (var i = 0; i < model.Shift.ShiftExpenses().length; i++) {
                //                 if ((parseFloat(model.Shift.ShiftExpenses()[i].Amount)) > 0) {

                sum += parseFloat(model.Shift.ShiftExpenses()[i].Amount());

            }
           //Add empty Expense to Observable array
           if (parseFloat(model.Shift.ShiftExpenses()[ (model.Shift.ShiftExpenses().length - 1)].Amount()) > 0) {

                model.Shift.ShiftExpenses
                    .push( new Expense({ "ShiftId": 0, "Id": 0, "Amount": 0, "Description": "" }));

          }

            return sum.toFixed(2);
        });
        return model;
    }

        //Apply MappingOptions
      var AppViewModel = ko.mapping.fromJS(data, mappingOptionsForTotalShiftExpenses);

        $(document)
        .ready(function() {

            ko.applyBindings(AppViewModel);


        });

通过上面的代码,我可以得到 - 第一个费用项目的第一个输入框 - 一旦我添加费用项目,它将显示为下一个项目设置的另一个输入框,并显示总计。 - 问题是当我尝试添加第二笔费用时,它不会触发任何事情(似乎第二组输入框是不可观察的)。但是,如果我在第一个费用项目中更改金额,它将触发可观察行为,并将正确地为这两个费用添加项目......并为第三个费用添加 UI 输入框。由于某种原因,最后一个输入框始终不会触发可观察的行为,但最后一个输入框之前的任何框都会触发。 注意:我发布的代码只是试图增加费用。

迫不及待地想听听一些淘汰赛专家的消息....提前非常感谢。

【问题讨论】:

    标签: knockout.js


    【解决方案1】:

    在我看来,你的事情有点过于复杂了。映射到您的数据并创建一个跟踪总量的计算 observable 应该非常简单:

    编辑: 更新为包含动态输入框功能。您可以通过向计算的 observable 添加几行代码来完成此操作,以保证数组底部始终存在一个空白项:

    // Data pulled in from wherever
    var data = [{
      ShiftId: 1,
      Id: 1,
      Amount: 25.00,
      Description: 'Supplies'
    }, {
      ShiftId: 2,
      Id: 2,
      Amount: 20.00,
      Description: 'Lunch'
    }];
    
    // Expense model
    var Expense = function(shiftId, id, amount, desc) {
      var self = this;
      self.ShiftId = ko.observable(shiftId);
      self.Id = ko.observable(id);
      self.Amount = ko.observable(amount);
      self.Description = ko.observable(desc);
    };
    
    //  Main ViewModel
    var AppViewModel = function() {
      var self = this;
    
      // Expenses array
      self.Expenses = ko.mapping.fromJS(data);
    
      // Compute the grand total of all expenses
      self.TotalofExpenses = ko.computed(function() {
        // First, ensure that a blank item remains at the bottom of array
        self.Expenses.remove(function(item) {
            return !item.Amount() && !item.Description();
        });
        self.Expenses.push(new Expense());
    
        // Now calculate the sum of all expenses
        var total = 0;
        self.Expenses().forEach(function(item, index) {
          total += +item.Amount() || 0;
        });
    
        return total;
      });
    };
    
    ko.applyBindings(new AppViewModel());
    

    编辑 2: 更新了 JSFiddle,以便在用户为 Description 输入提供值之前禁用 Amount 输入框(请参阅 JSFiddle 链接中的 HTML)。我通过简单地将“启用:描述”添加到金额输入框的绑定来做到这一点。

    https://jsfiddle.net/dw1284/aaysdxmb/9/

    【讨论】:

    • 非常感谢您的帮助。但是,您发布的内容已经对我有用。我的问题是添加动态输入框并获得它们的总数。请您在您的解决方案中添加该功能。非常感谢。
    • Answer 已更新,包括在用户输入数据时动态添加和删除表单元素。
    • 谢谢一百万。其实我不能说谢谢。我已将其标记为答案。如果您可以添加一些更新,我将不胜感激。如果可以的话,必须先输入描述,然后才能输入金额。
    • 当然,检查 JSFiddle 链接。我已对其进行了更新,使其包含一个新的绑定,除非提供了描述,否则它将禁用金额输入框。
    • 非常感谢丹尼斯,它正在按照我的意愿工作。上帝保佑你,伙计。
    【解决方案2】:

    因此,根据您所说的,我创建了一个简单的小提琴,其中输入字段的数量由 ko.observableArrayforeach 绑定控制。当您从数组中添加或删除(不在小提琴中删除)项目时,视图会自动更新。在您的情况下,您似乎需要进行更多功能和其他特殊检查,但我相信这应该可以帮助您完成您想要完成的工作。

    JSFiddle

    function TestVM() {
      this.inputFields = ko.observableArray([
        { type: 'type1', displayName: 'name1' },
        { type: 'type2', displayName: 'name2' },
        { type: 'type3', displayName: 'name3' }
      ]);
    }
    
    var dynamic = new TestVM();
    ko.applyBindings(dynamic);
    
    $('#addInput').click(function(){
    dynamic.inputFields.push({ type: 'newtype', displayName: 'newField' });
      console.log(dynamic.inputFields());
    });
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    
    <div>
      <div data-bind="foreach: inputFields">
        <label data-bind="text: $data.displayName"></label><br>
        <input data-bind="name: $data.type"><br>
      </div>
    </div>
    
    <button id="addInput">Add</button>

    【讨论】:

    • 非常感谢我的朋友。我看到了你的小提琴。我想在不点击按钮的情况下添加一个新的输入框。此外,我还希望对输入框中输入的所有金额进行累计。
    • 对,这就是我的意思,您在幕后所要做的就是添加到该数组,而不是现在如何将其挂接到点击事件中。在此示例中,按钮单击只是添加新按钮的发起者。至于绑定中的运行总量,只需将文本绑定添加到视图中的foreach(knockoutjs.com/documentation/text-binding.html)。
    猜你喜欢
    • 2015-10-25
    • 2017-12-13
    • 2013-11-10
    • 1970-01-01
    • 2015-08-15
    • 2013-04-22
    • 1970-01-01
    • 2012-06-14
    • 2015-05-04
    相关资源
    最近更新 更多