我在开发网络应用时遇到了一些问题。需要访问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;
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;
答案 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>