动态健康栏 - 动态可观察性和依赖性

时间:2016-12-27 01:45:04

标签: javascript json knockout.js

这个问题有点具体,所以请随时回答更广泛的问题,或指出我正确的方向。

我几天前选择了Knockout.js,因为它解决了我在我的应用程序中预先遇到的问题,尽管如此,对图书馆来说这么新,往往会产生更多问题......

我试图为GM(游戏大师)应用程序制作一个简单的PC(可玩角色)管理器。我从json对象数组中加载所有PC信息(稍后来自db),并将它们放在Bootstrap选项卡面板中以便于维护。

我想要创建的第一件事是每个玩家的健康栏,其侧面有一个控件,用于添加和减去hp,以及一个X / Y简单文本视图。这些组件中的每一个都应该独立于另一个播放器的组件,并且应该根据提交的输入框中的值动态更新。

我遇到的问题是考虑淘汰而不是简单的javascript。我稍后会解释。

这是我的代码:

〜带数据的JSON数组:

    var initialData = [
    {
        id: 0,
        pcName: "Player 1",
        hp: 12,
        curHp: 12,
        name: "Dudebro One",
        playerClass: "Ranger",
        level: 1,
        background: "",
        race: "Elf - Wood",
        Alignment: "",
        exp: 700,
        inspiration: 0,
        proficiencyBonus: 0
    },
    {
        id: 1,
        pcName: "Player 2",
        hp: 10,
        curHp: 10,
        name: "Brodude Two",
        playerClass: "Fighter",
        level: 1,
        background: "Soldier",
        race: "Gnome",
        Alignment: "",
        exp: 700,
        inspiration: 0,
        proficiencyBonus: 0
    }
];

(注意:出于可读性目的,数据不完整。还假设n个玩家数量,而不是2)

〜淘汰赛代码(来自完整的FAR):

var PCModel = function (pcs) {
    var self = this;

    var currentHp = ko.observable(10);
    var maximumHp = ko.observable(10);

    self.pcsList = ko.observableArray(ko.utils.arrayMap(pcs, function (pc) {
        return {
            id: pc.id, pcName: pc.pcName, hp: pc.hp, curHp: pc.curHp, name: pc.name, playerClass: pc.playerClass, level: pc.level, background: pc.background, race: pc.race, Alignment: pc.Alignment, exp: pc.exp,
            inspiration: pc.inspiration, proficiencyBonus: pc.proficiencyBonus
        };
    }));

    //TODO: REMOVE (Note: Here just for testing).
    self.myFunction= function(pc){
        currentHp--;
    };

    self.getHpPercentage = function (pc) {
        var hpRound = Math.round((pc.curHp / pc.hp) * 100);
        return hpRound + "%"
    }

    self.hpClass = function (pc) {
        var hp = currentHp() / maximumHp() * 100;
        if (hp >= 70) {
            return 'progress-bar-success';
        } else if (hp < 70 && hp >= 30) {
            return 'progress-bar-warning';
        } else if (hp < 30) {
            return 'progress-bar-danger';
        }
    };
};
ko.applyBindings(new PCModel(initialData));

现在让我解释一下自己。我知道,对于动态更新的东西,我要么需要定义observable,要么手动订阅东西。我决定走可观察的路线。我的问题是var maximumHp = ko.observable(10);,例如,在我的情况下没有意义,因为我在json数组中拥有所需的所有数据。此外,对于正在查看/处理的CURRENT对象,maximumHp必须如此。我不知道如何定义......

〜我的带有绑定的html代码(再次,无处接近完成):

<div style="float:right; margin-top:25px; width: 65vw;">
<div class="container">
    <ul class="nav nav-tabs" id="sortable" data-bind="foreach: pcsList">

        <li data-bind="css: {active: $index() == 0 }" >
            <a data-bind="attr: {href: '#tab' + id}, text: pcName" data-toggle="tab"></a>
        </li>
    </ul>
    <div class="container-border-cup">
        <div class="tab-content " data-bind="foreach: pcsList">

            <div class="tab-pane tabbed-content-style" data-bind="attr: {id: 'tab' + id}, css: {active: $index() == 0 }">
                <h3 data-bind="text: name"></h3>

                <div style="float:left; font-size:15pt; font-weight:500; line-height:35px; padding-right:20px; min-width:100px;">
                    <span data-bind="text: curHp"></span>/<span data-bind="text: hp"></span>
                </div>

                <button data-bind="click: $root.myFunction">
                    Click me
                </button>

                <div class="progress" style="width: 50%; height: 35px; float:left">
                    <div class="progress-bar" style="float:left;" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" data-bind="text: $root.getHpPercentage($data), attr:{class: 'progress-bar ' + $root.hpClass($data)}, style:{width: $root.getHpPercentage($data)}"></div>
                </div>


                <div class="input-group" style="padding-left:20px">
                    <span class="input-group-btn">
                        <input type="button" class="btn btn-danger" id="btnToggleHP" value="-" style="width: 35px; font-weight:900;" />
                    </span>
                    <input id="inModHP" style="width:70px" type="text" class="form-control" value="0">
                    <span class="input-group-btn" style="float:left">
                        <input type="button" class="btn btn-default" id="btnGoHP" value="Go" />
                    </span>
                </div>
            </div>
        </div>
    </div>
</div>
</div>

以上所有代码都给出了以下效果: enter image description here

(不要担心ulgy按钮,这是那个仅用于测试的按钮)。理想情况下,最终,用户可以单击红色减号按钮,并在该绿色加号和绿色加号之间切换,以识别从健康池中添加或减去。单击“Go”按钮应该级联更新所有内容。

虽然我可能会把最终有效的东西混在一起,但我还是想学习一些好的做法。

感谢您阅读我的小说中的一个问题,如果您愿意放弃答案,请多谢一下!

1 个答案:

答案 0 :(得分:1)

如果您实施的行为与每个玩家相关,则应将逻辑放在PlayerViewModel中并为每个玩家设置一个模型。您可以在另一个视图模型中跟踪模型集合。我们称之为App

App中,我们只执行一个事物:

  1. 获取json数组,并为其中的每个对象创建 new PlayerViewModel
  2. 当我们创建new Player时,我们会做几件事:

    1. 首先,我们将所有静态json属性复制到新的播放器实例。您可以像在代码中一样手动执行此操作,也可以自动执行此操作,就像我使用Object.assign一样。

    2. 然后,我们覆盖一些属性以使用ko.observable s。 您希望能够在UI中更改的每个属性都必须是可观察的。因此,如果您希望能够更改名称,则必须将其添加为可观察的名称。

    3. 现在我们也可以添加自己的可观察值和计算值。例如:如果玩家可以更改curHp,我们可以自动计算HP百分比。

    4. 最后,每个Player都会通过Player.prototype获取一组函数。例如:有一个函数可以获取input字段中的值并将其添加到curHp

    5. 现在,因为所有信息和逻辑都在Player模型中,所以您可以轻松地进行数据绑定,而无需传递对玩家的引用,而无需使用$parent或{{1} }。

      $root
      var Player = function(playerData) {
        // Make a copy of all properties to the new player
        // These are static properties, suitably for one-way
        // data-binding.
        Object.assign(this, playerData);
        
        // Create observables for two-way properties
        // (properties you want to change via the UI)
        this.curHp = ko.observable(playerData.curHp);
        this.subtractValue = ko.observable(0);
        
        // Create computed values for the UI
        this.hpPercentage = ko.pureComputed(function() {
          return (this.curHp() / playerData.hp * 100).toFixed(1);
        }, this);
        
        this.healthWarning = ko.pureComputed(function() {
          var hpPerc = this.hpPercentage();
          if (hpPerc === 0) return "Dead";
          if (hpPerc < 30) return "Watch out";
          if (hpPerc < 70) return "Steady";
          return "OK";
        }, this);
      };
      
      // Add functions
      Player.prototype.addHp = function() {
        var val = parseFloat(this.subtractValue());
        var newHP = Math.min(
          Math.max(this.curHp() + val, 0), this.hp);
        
        this.curHp(newHP);
      };
      
      // A helper constructor
      Player.create = function(playerJson) {
        return new Player(playerJson);
      };
      
      var App = function(jsonData) {
        this.players = jsonData.map(Player.create);
      };
      
      
      var initialData=[{id:0,pcName:"Player 1",hp:12,curHp:4,name:"Dudebro One",playerClass:"Ranger",level:1,background:"",race:"Elf - Wood",Alignment:"",exp:700,inspiration:0,proficiencyBonus:0},{id:1,pcName:"Player 2",hp:10,curHp:7,name:"Brodude Two",playerClass:"Fighter",level:1,background:"Soldier",race:"Gnome",Alignment:"",exp:700,inspiration:0,proficiencyBonus:0}];
      
      ko.applyBindings(new App(initialData));