我正在使用bootstrap-tokenfield和typeahead的淘汰赛来展示广告代码。以前我需要一种方式以一种很好的方式显示我的标签,因此我创建了一个自定义绑定。当标签列表没有变化且只有选定的标签发生变化时,它工作得非常好。
这是一个非常简化的例子looks like this。如您所见,您可以输入各种代码(tag1
,tag2
,...,tag5
),并且可观察的内容正在发生变化。所以我的自定义绑定适用于这种情况。
这是:
ko.bindingHandlers.tags = {
init: function(element, valueAccessor, allBindings) {
var initializeTags = function(listOfTags, inputID, max){
var tags = new Bloodhound({
local: listOfTags,
datumTokenizer: function(d) {return Bloodhound.tokenizers.whitespace(d.value);},
queryTokenizer: Bloodhound.tokenizers.whitespace
});
tags.initialize();
inputID.tokenfield({
limit : max,
typeahead: {source: tags.ttAdapter()}
}).on('tokenfield:preparetoken', function (e) {
var str = e.token.value,
flag = false,
i, l;
for(i = 0, l = listOfTags.length; i < l; i++){
if (listOfTags[i]['value'] === str){
flag = true;
break;
}
}
if (!flag){
e.token = false;
}
});
}
var options = allBindings().tagsOptions,
currentTagsList = Helper.tags1List,
currentTagsInverted = Helper.tags1Inverted;
initializeTags(currentTagsList, $(element), 4);
ko.utils.registerEventHandler(element, "change", function () {
var tags = $(element).tokenfield('getTokens'),
tagsID = [],
observable = valueAccessor(), i, l, tagID;
for (i = 0, l = tags.length, tagID; i < l; i++){
tagID = currentTagsInverted[tags[i].value];
if (typeof tagID !== 'undefined'){
tagsID.push(parseInt(tagID));
}
}
observable( tagsID );
});
},
update: function(element, valueAccessor, allBindings) {
var arr = ko.utils.unwrapObservable(valueAccessor()),
options = allBindings().tagsOptions,
currentTags = Helper.tags1, tagsName = [], i, l, tagName;
if ( !(arr instanceof Array) ){
arr = [];
}
for (i = 0, l = arr.length, tagName; i < l; i++){
tagName = currentTags[arr[i]];
if (typeof tagName !== 'undefined'){
tagsName.push(tagName);
}
}
$(element).tokenfield('setTokens', tagsName);
}
};
但问题是我需要添加额外的标签:tag6
,如果我只是做
Helper.getAllTags({
"1":{"value":"tag1"}, ..., "6":{"value":"tag6"}
})
它不起作用(这对我来说并不意外,我知道它为什么不起作用)。这样做的正确方法是什么。
P.S。
如果您认为我的约束力很差,我同意您的意见,并乐于听取如何改进它。
如果您需要澄清有约束力的工作方式,我很乐意提供。
拥有tags1, tags1List, tags1Inverted
的想法是能够通过id或名称快速找到合适的标签(我有500个)。
如果你想改变许多你喜欢的东西
答案 0 :(得分:2)
我为bootstrap-tokenfield创建了一个KnockoutJS绑定。
https://github.com/mryellow/knockoutjs-tokenfield
首先,让我们看一下来自update
的{{1}}。
valueAccessor()
这里我们为模型中的任何传入数据创建标记。所有标记,update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var observable = valueAccessor() || { };
var peeked = ko.unwrap(observable.peek());
ko.tokenfield[element.id].handlerEnabled = false;
$(element).tokenfield('setTokens',peeked);
ko.tokenfield[element.id].handlerEnabled = true;
}
为我们提供了完整的对象。但是,这会触发valueAccessor()
绑定tokenfield:createdtoken
侧的init
。因此,为了避免将这些令牌重新保存到模型中,我们设置变量handlerEnabled
来控制事件流。
现在,对于任何用户互动,HTML value
属性或模型更改,都会触发此事件:
ko.utils.registerEventHandler(element, 'tokenfield:createdtoken', function (e) {
// Detect private token created.
if (e.attrs[ko.tokenfield[element.id].bindings['KeyDisplay']].indexOf("_") === 0) {
console.log('tt-private');
$(e.relatedTarget).addClass('tt-private');
}
// Allow `update` to temporarily disable pushing back when this event fires.
if (ko.tokenfield[element.id].handlerEnabled == true) observable.push(e.attrs);
});
请注意handlerEnabled
全局阻止重新添加到valueAccessor()
。
删除令牌时,令牌字段(patched)中缺少来自我们的AJAX自动完成的额外元数据。因此,我们必须根据确实存在的属性进行查找:
ko.utils.registerEventHandler(element, 'tokenfield:removedtoken', function (e) {
var peeked = observable.peek();
var item;
// Find item using tokenfield default values, other values are not in tokenfield meta-data.
ko.utils.arrayForEach(peeked, function(x) {
if (ko.unwrap(x.label) === e.attrs.label && ko.unwrap(x.value) === e.attrs.value) item = x;
});
observable.remove(item); // Validation of `item` likely needed
});
这就是关于粘合剂内部的内容。现在我们将所有内容直接保存到KnockoutJS所期望的绑定模型中,而不会出现重复数据或同步问题。让我们回到那个CSV字段,使用observableArray.fn
返回一个计算好的并且可以重复使用。
用法:self.tags_csv = self.tags.computeCsv();
。
ko.observableArray['fn'].computeCsv = function() {
console.log('observableArray.computeCsv');
var self = this;
return ko.computed({
read: function () {
console.log('computed.read');
var csv = '';
ko.utils.arrayForEach(ko.unwrap(self), function(item) {
console.log('item:'+JSON.stringify(item));
if (csv != '') csv += ',';
// Our ID from AJAX response.
if (item.id !== undefined) {
csv += item.id;
// Tokenfield's ID form `value` attrs.
} else if (item.value !== undefined) {
csv += item.value;
// The label, no ID available.
} else {
csv += item.label;
}
});
return csv;
},
write: function (value) {
console.log('computed.write');
ko.utils.arrayForEach(value.split(','), function(item) {
self.push({
label: item,
value: item
});
});
}
});
};
现在我们的模型中有一个对象数组和一个CSV表示形式,可以在发送到服务器之前进行映射或操作。
"tags": [
{
"label": "tag1",
"value": "tag1"
},
{
"id": "id from AJAX",
"field": "field from AJAX",
"label": "tag2",
"value": "tag2"
}
],
"tags_csv": "tag1,id from AJAX"
答案 1 :(得分:0)
请参阅其他版本。
直接将addItem()
/ removeItem()
添加到模型有助于使事情更容易管理。下面是我的模型,其中包含与每个字段关联的项目。
var tokenFieldModel = function tokenFieldModel() {
var self = this;
this.items = ko.observableArray([]);
this.addItem = function(attrs) {
console.log('addItem');
self.items.push(new tokenItemModel(attrs));
};
this.removeItem = function(attrs) {
console.log('removeItem');
var item;
if (attrs.id != null) {
ko.utils.arrayForEach(this.items(), function(x) {
if(x.id === attrs.id && ko.unwrap(x.value) == attrs.value) item = x;
});
} else {
ko.utils.arrayForEach(this.items(), function(x) {
// TODO: Use allBindingsAccessor().tokenFieldDisplay
if(ko.unwrap(x.value) === attrs.value) item = x;
});
}
//console.log(ko.unwrap(this.items()));
self.items.remove(item);
};
};
removeItem()
看起来很麻烦,不得不循环使用,但对我的情况有点特别,我想添加尚未与自动完成匹配的令牌,并且没有id
field_id = "id:111, id:222, a new tag, id:333, another new tag"
1}}或任何其他对象键。他们只有创建令牌的文本/标签。
然后,我可以将其发送到服务器,如下所示:
field_id = [
{
id: 111,
value: 'tag1',
label: 'tag1'
},
{
id: 222,
value: 'tag2',
label: 'tag2'
},
{
value: 'a new tag'
},
]
或
removeItem()
这允许我创建没有前缀的令牌。我使用的是Couchbase NoSQL,因此这与数据/文档的存储方式非常吻合。
因此,id
必须搜索数组,尝试匹配value
或后退仅查找allBindingsAccessor()
。可以改进此部分以接受来自init
的绑定变量以控制匹配哪个字段。
现在在活页夹ko.utils.registerEventHandler(element, 'tokenfield:removedtoken', function (e) {
console.log('tokenfield:removedtoken');
console.log(e);
tokenBaseModel.fields[element.id].removeItem(e.attrs);
});
中,我们可以定义将响应令牌字段的eventHandlers。
tokenBaseModel.fields()
请注意,我在页面上的每个令牌字段都在其elementId
(不是obserableArray()
索引的数组var tokenBaseModel = {
fields: []
};
内,只是一个普通数组,用于存储每个令牌字段的单独项目列表页面)。
update
然后在活页夹data-bind
部分,我们可以将令牌字段中的值传递回update: function(element, valueAccessor, allBindingsAccessor, bindingContext) {
console.log('update');
var observable = valueAccessor() || {};
// Does validation on allBindingsAccessor and sets defaults.
var bindings = new tokenFieldUtils().processBindings(allBindingsAccessor);
// An `fn` util function extending both `observableArray` and `observable` to accept whichever datatype they're expecting and sort it out.
observable.refreshAll(ko.unwrap(tokenBaseModel.fields[element.id].items),bindings['Delimiter'],bindings['FieldKey']);
}
属性本身定义的其他模型。
refreshAll()
然后我的valueAccessor()().refreshAll()
(实际valueAccessor()
)函数最终将数据传递回 ko.observableArray['fn'].refreshAll = function(valuesToPush, delimiter, key) {
var underlyingArray = this();
this.valueWillMutate();
this.removeAll();
ko.utils.arrayPushAll(underlyingArray, valuesToPush);
this.valueHasMutated();
return this;
};
ko.observable['fn'].refreshAll() = function(valuesToPush, delimiter, key) {
this.valueWillMutate();
var csv = '';
ko.utils.arrayForEach(valuesToPush, function(item) {
if (csv != '') csv += delimiter;
if (item[key] === undefined) {
csv += item['value'];
} else {
csv += item[key];
}
});
this(csv);
this.valueHasMutated();
return this;
};
。
data-bind="tokenfield: fooModel.bar"
将绑定定义为valueAccessor()
意味着fooModel.bar
将被评估到我的tokenfield模型范围之外的外部字段valueAccessor()
。
(valueHasMutated()
实际上是一个获取/设置的函数,而不是直接指向该值的链接。
然后,最后点击fooModel.bar
会触发更改以更新{{1}}绑定的其他元素。