从数据绑定外部引用knockout数组,没有路径知识

时间:2018-01-23 11:22:04

标签: javascript arrays knockout.js data-binding

我在开发网络应用时遇到了一些问题。需要访问knockout外部的foreach绑定中使用的knockout.js数组,或者更确切地说是.js文件中的数组。现在ko.dataFor()ko.contextFor()允许我访问数组的特定元素的上下文和数据,但不能访问数组本身。

我基本上需要的是能够扫描整个数组(所有元素)以获取其中一个属性的值,并在发出请求时通过AJAX将所有当前属性发送到服务器以便过滤出'那些已经存在的,使它成为多个数组条目的唯一选择器,如果这是有道理的。

我很确定答案就在上述功能的某处,我似乎无法找到它。

编辑:

外部脚本需要访问包含用于绑定的元素的可观察数组,或者从视图模型的根开始获知该数组的路径。我无法使用subscribe获取有关数据的信息,因为我不知道要订阅什么。模型是动态的,在这种情况下example array嵌套3级深,如果它在那里。它也在循环中,因此它的路径根据访问的html元素而变化,它可能在父循环中被不同地索引。我拥有的唯一入口点是绑定的元素。

检查示例以便更好地理解。



var VM = {};
var data = {
  exampleArray: [{
      key: 1,
      value: 'foo1'
    },
    {
      key: 2,
      value: 'foo2'
    },
    {
      key: 3,
      value: 'foo3'
    }
  ],
  lookup: [{
      key: 1,
      value: 'foo1'
    },
    {
      key: 2,
      value: 'foo2'
    },
    {
      key: 3,
      value: 'foo3'
    },
    {
      key: 4,
      value: 'foo4'
    },
    {
      key: 5,
      value: 'foo5'
    }
  ]
}

ko.mapping.fromJS(data, {}, VM);
ko.applyBindings(VM);

function addRow() {
  var select = document.getElementById('selector');
  var value = select.options[select.selectedIndex].value;
  var text = select.options[select.selectedIndex].text;
  VM.exampleArray.push({
    key: ko.observable(value),
    value: ko.observable(text),
  })
}

#container {
  width: 300px;
  margin-bottom: 10px;
}

#container>div {
  border: solid 1px green;
  min-height: 15px;
  padding: 3px;
  margin: 3px;
}
select {
width: 200px;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.js"></script>
<div id="container" data-bind="foreach: exampleArray">
  <div>
    <span data-bind="text: key() + ' - '"></span>
    <span data-bind="text: value"></span>
  </div>
</div>
<select id="selector" data-bind="options: lookup,
                       optionsText: 'value',
                       optionsValue: 'key'
                       "></select>
<button onclick="addRow();">Add a row</button>

<p> Now imagine that the lookup data set is updated from the server via AJAX call every time a row is added, where it would need to send only the rows that are not already present in this data set, making this a unique selection. I would need a way to send keys already selected to the server, so that SQL can filter out what data to send back and what data to skip. <br><br>

<b>How would i get this information?</b><br><br>

P.S. I need a way to send an array of keys, not the whole model or data-set or whatever inventive solution might be, because this is an oversimplifed example, the real model is deep nested and much more complex, and pretty much inaccessible/untouchable in the heirarchy of the app's classes that make it. If you need clerification as to why, post a comment and I will try to answer as briefly as possible.</p>
&#13;
&#13;
&#13;

SPECIAL EDIT:

&#13;
&#13;
var VM = {};
var data = {
  some: {
    super: [
      {
        deep: {
          nested: {
            shit: {
              articles: [
                {key: 1, value: "fish"},
                {key: 2, value: "fruit"},
                {key: 3, value: "meat"},
              ],    
            }
          }
        }
      }
    ],
    lookup: [
      {key: 1, value: "fish"},
      {key: 2, value: "fruit"},
      {key: 3, value: "meat"},
      {key: 4, value: "eggs"},
      {key: 5, value: "bread"},
      {key: 6, value: "milk"},
      {key: 7, value: "water"},
    ]
  }
}

ko.mapping.fromJS(data, {}, VM);
ko.applyBindings(VM);

document.getElementById('sayt').addEventListener('focus', function(){
  document.getElementById('saytResults').style.display = "block";
})
document.getElementById('sayt').addEventListener('blur', function(){
  document.getElementById('saytResults').style.display = "none";
})

function printListItemContextData(btn){
  console.log(ko.contextFor(btn.parentNode.getElementsByClassName('selectedArticles')[0].children[0]));
}
&#13;
#sayt, #saytResults{
  width: 200px;
}
#saytResults > div {
  border: solid 1px black;
  padding: 2px;
  width: 100%;
}
&#13;
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>

<div data-bind="foreach: some.super">
  <div class="selectedArticles" data-bind="foreach: deep.nested.shit.articles">
    <div data-bind="text: key() + ' - ' + value()"></div>
  </div>
  <p>Imagine that the input control below is a "full text search" of the articles on the database. As you type, a request is sent to the server with your typed text as a parameter for the query, results get delivered into the lookup dataset.</p>
  
  <input id="sayt" type="text" value="Click here to simulate request">
  <div id="saytResults" style="display: none;" data-bind="foreach: $root.some.lookup">
    <div data-bind="text: key() + ' - ' + value()"></div>
  </div>
  <br>
  <p>
  The goal however is to NOT display elements that are already present in the dataset. But I cannot get to that data set, I have no idea how to "target" the array. The button below can 'print' the info (console.log) of any of the elements already in the list, but not the list itself.
  </p>
  <button onclick="printListItemContextData(this)"> PRINT </button>
  <p>The issue is, parent context is not created as you go deep down the tree, and $parent of an element of a <b>foreach</b> is not the array it is in, but rather an object, 3 levels up.</p>
</div>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

如果您知道您的数据是一个数组,并且每个元素的元素都有一个子DOM节点,您可以通过迭代子节点并从每个节点获取dataFor来重建数组。附件代码就是这样做的。

更新:调整代码,因此每个元素有多少个子节点并不重要。

var VM = {};
var data = {
  some: {
    super: [
      {
        deep: {
          nested: {
            shit: {
              articles: [
                {key: 1, value: "fish"},
                {key: 2, value: "fruit"},
                {key: 3, value: "meat"},
              ],    
            }
          }
        }
      }
    ],
    lookup: [
      {key: 1, value: "fish"},
      {key: 2, value: "fruit"},
      {key: 3, value: "meat"},
      {key: 4, value: "eggs"},
      {key: 5, value: "bread"},
      {key: 6, value: "milk"},
      {key: 7, value: "water"},
    ]
  }
}

ko.mapping.fromJS(data, {}, VM);
ko.applyBindings(VM);

document.getElementById('sayt').addEventListener('focus', function(){
  document.getElementById('saytResults').style.display = "block";
})
document.getElementById('sayt').addEventListener('blur', function(){
  document.getElementById('saytResults').style.display = "none";
})

function printListItemContextData(btn){
  const el = btn.parentNode.getElementsByClassName('selectedArticles')[0];
  const data = [];
  
  for (const c of el.children) {
    data[ko.contextFor(c).$index()] = ko.dataFor(c);
  }
  
  console.log("Data:", ko.toJS(data));
}
#sayt, #saytResults{
  width: 200px;
}
#saytResults > div {
  border: solid 1px black;
  padding: 2px;
  width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>

<div data-bind="foreach: some.super">
  <div class="selectedArticles" data-bind="foreach: deep.nested.shit.articles">
    <div data-bind="text: key() + ' - ' + value()"></div>
  </div>
  <p>Imagine that the input control below is a "full text search" of the articles on the database. As you type, a request is sent to the server with your typed text as a parameter for the query, results get delivered into the lookup dataset.</p>
  
  <input id="sayt" type="text" value="Click here to simulate request">
  <div id="saytResults" style="display: none;" data-bind="foreach: $root.some.lookup">
    <div data-bind="text: key() + ' - ' + value()"></div>
  </div>
  <br>
  <p>
  The goal however is to NOT display elements that are already present in the dataset. But I cannot get to that data set, I have no idea how to "target" the array. The button below can 'print' the info (console.log) of any of the elements already in the list, but not the list itself.
  </p>
  <button onclick="printListItemContextData(this)"> PRINT </button>
  <p>The issue is, parent context is not created as you go deep down the tree, and $parent of an element of a <b>foreach</b> is not the array it is in, but rather an object, 3 levels up.</p>
</div>