KnockoutJS 映射插件 (observableArray)

Posted

技术标签:

【中文标题】KnockoutJS 映射插件 (observableArray)【英文标题】:KnockoutJS Mapping Plugin (observableArray) 【发布时间】:2015-07-04 00:06:56 【问题描述】:

我是淘汰赛新手,使用映射插件时遇到问题,因为我不明白它如何映射我的 JSON 数据。 这是一个类似于我的程序中的示例 json 数据:

contact: 
        name : 'John',
        email : 'address@domain.com',
        phones : [
            phoneType : 'Home Phone',
            phoneNumber: '999-888-777',
            
            phoneType : 'Business Phone',
            phoneNumber: '444-888-777',
            ]
        

如您所见,这个 json 数据包含一个电话数组。 我使用了敲除映射插件,我可以轻松地绑定“姓名”、“电子邮件”并在“foreach:电话”中循环电话号码,直到我尝试在 phoneNumber 上创建一个 ko.compute,这是一个对象阵列电话。

@section scripts

    <script src="~/ViewModels/ContactModel.js"></script>
    <script type="text/javascript">
        var viewModel = new ContactModel(@html.Raw(Model.ToJson()));
        $(document).ready(function () 
            ko.applyBindings(viewModel);
        );
</script>

<label>Name</label><input data-bind="value: name" />
<label>Email</label><input data-bind="value: email" />
<label>Phones</label>
<table>
  <tbody data-bind="foreach: phones">
     <tr>
      <td><strong data-bind='text: phoneType'></strong></td>
      <td><input data-bind='value: phoneNumber' /></td>
     </tr>
   /tbody>
 </table>

这是 ContactModel.js

    var ContactModel = function (data) 
    var self = this;
    ko.mapping.fromJS(data, , self);

    self.reformatPhoneNumber = ko.computed(function()
    var newnumber;
    newnumber = '+(1)' + self.phones().phoneNumber;
    return newnumber;
    );

    ;

对于视觉表示,这就是现在的样子:

Name: John
Email: address@domain.com
Phones:
<--foreach: phones -->
Home Phone: 999-888-777
Business Phone: 444-888-777

我想要做的是重新格式化 phoneNumber 以这样显示它:

Name: John
Email: address@domain.com
Phones:
<--foreach: phones -->
Home Phone: (+1)999-888-777
Business Phone: (+1)444-888-777

我尝试通过在绑定中使用重新格式化电话号码代替电话号码来做到这一点,如下所示:

<table>
      <tbody data-bind="foreach: phones">
         <tr>
          <td><strong data-bind='text: phoneType'></strong></td>
          <td><input data-bind='value: $root.reformatPhoneNumber' /></td>
         </tr>
       /tbody>
     </table>

但是当我这样做时,reformatPhoneNumber 的值不会出现。 我在这里的某个地方读到,我必须使 observableArray 中的对象也可观察,因为 ko.mapping 默认情况下不会这样做。但是我无法想象如何做到这一点,因为我期望 ko.mapping 插件自动为我完成所有工作,因为我是这个 jslibrary 的新手。 任何帮助将不胜感激。非常感谢!!

【问题讨论】:

测试题:self.phones().phoneNumber 到底指的是什么值? 另外,在你的情况下self 是什么?请发布minimal but complete example。 *** 有工具可以在您的问题中包含一个可运行的示例。 @Tomalak 嗨 tomalak,感谢您的回复,我编辑了我的帖子以按照您的要求显示我的整个代码。谢谢!请告诉我如何敲掉我的程序。 :( 【参考方案1】:

您对命名(一个名为reformatPhoneNumber 的计算)的使用表明您将计算视为函数。虽然从技术上讲,它们是函数,但它们代表值。将它们视为值,就像您对待 observables 一样。在您的情况下,这意味着它应该被称为更像formattedPhoneNumber,并且应该作为电话号码的属性而不是联系人的属性。

将您的模型分成可以从原始数据引导自己的独立单元。

模型层次结构中最小的信息单位是电话号码:

function PhoneNumber(data) 
    var self = this;

    self.phoneType = ko.observable();
    self.phoneNumber = ko.observable();
    self.formattedPhoneNumber = ko.pureComputed(function () 
        return '+(1) ' + ko.unwrap(self.phoneNumber);
    );

    ko.mapping.fromJS(data, PhoneNumber.mapping, self);

PhoneNumber.mapping = ;

层次结构中的下一个是联系人。它包含电话号码。

function Contact(data) 
    var self = this;

    self.name = ko.observable();
    self.email = ko.observable();
    self.phones = ko.observableArray();

    ko.mapping.fromJS(data, Contact.mapping, self);

Contact.mapping = 
    phones: 
        create: function (options) 
            return new PhoneNumber(options.data);
        
    
;

下一个是联系人列表(或电话簿),它包含联系人:

function PhoneBook(data) 
    var self = this;

    self.contacts = ko.observableArray();

    ko.mapping.fromJS(data, PhoneBook.mapping, self);

PhoneBook.mapping = 
    contacts: 
        create: function (options) 
            return new Contact(options.data);
        
    
;

现在您可以通过实例化一个电话簿对象来创建整个对象图:

var phoneBookData = 
    contacts: [
        name: 'John',
        email: 'address@domain.com',
        phones: [
            phoneType: 'Home Phone',
            phoneNumber: '999-888-777'
        , 
            phoneType: 'Business Phone',
            phoneNumber: '444-888-777'
        ]
    ]
;
var phoneBook = new PhoneBook(phoneBookData);

通读documentation of the mapping plugin。

展开下面的代码 sn-p 看看它的工作原理。

function PhoneBook(data) 
    var self = this;

    self.contacts = ko.observableArray();
    
    ko.mapping.fromJS(data, PhoneBook.mapping, self);

PhoneBook.mapping = 
    contacts: 
        create: function (options) 
            return new Contact(options.data);
        
    
;
// ------------------------------------------------------------------

function Contact(data) 
    var self = this;
    
    self.name = ko.observable();
    self.email = ko.observable();
    self.phones = ko.observableArray();
    
    ko.mapping.fromJS(data, Contact.mapping, self);

Contact.mapping = 
    phones: 
        create: function (options) 
            return new PhoneNumber(options.data);
        
    
;
// ------------------------------------------------------------------

function PhoneNumber(data) 
    var self = this;
    
    self.phoneType = ko.observable();
    self.phoneNumber = ko.observable();
    self.formattedPhoneNumber = ko.pureComputed(function () 
        return '+(1) ' + ko.unwrap(self.phoneNumber);
    );
    
    ko.mapping.fromJS(data, PhoneNumber.mapping, self);

PhoneNumber.mapping = ;
// ------------------------------------------------------------------

var phoneBook = new PhoneBook(
    contacts: [
        name: 'John',
        email: 'address@domain.com',
        phones: [
            phoneType: 'Home Phone',
            phoneNumber: '999-888-777'
        , 
            phoneType: 'Business Phone',
            phoneNumber: '444-888-777'
        ]
    ]
);

ko.applyBindings(phoneBook);
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout/3.3.0/knockout-min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>

<ul data-bind="foreach: contacts">
    <li>
        <div data-bind="text: name"></div>
        <div data-bind="text: email"></div>
        <ul data-bind="foreach: phones">
            <li>
                <span data-bind="text: phoneType"></span>:
                <span data-bind="text: formattedPhoneNumber"></span>
            </li>
        </ul>
    </li>
</ul>

<hr />
Model data:
<pre data-bind="text: ko.toJSON(ko.mapping.toJS($root), null, 2)"></pre>

Viewmodel data:
<pre data-bind="text: ko.toJSON($root, null, 2)"></pre>

【讨论】:

哇。您立即发现我缺乏关于 Knockout 的知识,您对此进行了详细说明和解释。非常感谢你,你是个野兽。老实说,我只是直接将示例复制粘贴到我的程序中,而无需阅读文档。这对我帮助很大,Tomalak 爵士,比我迄今为止看过的任何快速入门视频都要好。再次感谢您! 您好,先生,我有一个后续问题,如果出于某种原因,我需要从层次结构中的更高位置访问对象怎么办。例如,我想从 Contact viewModel 中获取名称并将其附加到 PhoneNumber viewModel 中的 formattedPhoneNumber。 self.formattedPhoneNumber = ko.pureComputed(function () return Contact().Name + ko.unwrap(self.phoneNumber); ); 我认为它应该是这样的,但它不起作用 我会在视图中添加它。这适用于foreach 绑定:&lt;span data-bind="text: phoneNumber"&gt;&lt;/span&gt; (&lt;span data-bind="text: $parent.name"&gt;&lt;/span&gt;)。视图知道绑定是如何相互关联的(它知道binding context)并且是解决这类显示问题的自然场所。

以上是关于KnockoutJS 映射插件 (observableArray)的主要内容,如果未能解决你的问题,请参考以下文章

Knockoutjs 映射不会将 observables 添加到字符串数组中

KnockoutJS:将 Observable 属性和函数添加到映射生成的 ObservableArray 中的对象

KnockoutJS - 点击事件后的数据绑定

Knockout JS映射插件没有初始数据/空表单

使用可观察数组进行 Knockoutjs 映射和验证

KnockoutJs - 为啥初始化绑定处理程序只被调用一次?