基于给出here的建议,以及给出here关于如何为forEach创建自定义bindingHandler的信息,我决定尝试为forEach和Masonry编写自己的自定义绑定。
因为元素是动态添加的,所以不会重新绘制和移动元素以填充空间。因此,在添加每个项目之后渲染或调用元素之后,需要移动此功能。
这是我的bindingHandler
ko.bindingHandlers.masonry = {
init: function (element, valueAccessor, allBindingsAccessor) {
var $element = $(element),
originalContent = $element.html();
$element.data("original-content", originalContent);
//var msnry = new Masonry($element);
return { controlsDescendantBindings: true }
},
update: function (element, valueAccessor, allBindingsAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()),
//get the list of items
items = value.items(),
//get a jQuery reference to the element
$element = $(element),
//get the current content of the element
elementContent = $element.data("original-content");
$element.html("");
var container = $element[0];
var msnry = new Masonry(container);
for (var index = 0; index < items.length; index++) {
(function () {
//get the list of items
var item = ko.utils.unwrapObservable(items[index]),
$childElement = $(elementContent);
ko.applyBindings(item, $childElement[0]);
//add the child to the parent
$element.append($childElement);
msnry.appended($childElement[0]);
})();
msnry.layout();
msnry.bindResize();
}
}
};
和实现处理程序的HTML。
<div id="criteriaContainer" data-bind="masonry: { items: SearchItems.Items }">
<div class="searchCriterion control-group">
<label class="control-label" data-bind="text: Description"></label>
<div class="controls">
<input type="hidden" data-bind="value: Value, select2: { minimumInputLength: 3, queryUri: SearchUri(), placeholder: Placeholder(), allowClear: true }" style="width: 450px">
</div>
<p data-bind="text: Value"></p>
</div>
</div>
当它显示在页面上时如果通过append方法呈现的元素彼此重叠,则它会堆叠所有元素。
你可以在我的bindingHandler中看到我调用bindResize以及layout(),它们似乎都没有任何效果。
以下是UI中的外观截图。
答案 0 :(得分:2)
我制作的自定义活页夹基于其他人对同位素的自定义绑定:https://github.com/aknuds1/knockout-isotope/blob/master/lib/knockout-isotope.js
注意:自定义同位素绑定的作者正在使用修改的版本的knockout。下面的绑定使用标准淘汰库(我使用的是v3.3.0)。
使自定义绑定工作的技巧是使用afterAdd回调来跟踪添加的元素,以便将它们附加到砌体对象。
"use strict";
(function () {
var $container, haveInitialized, newNodes = [], itemClass, masonryOptions;
function afterAdd(node, index, item) {
if (node.nodeType !== 1) {
return; // This isn't an element node, nevermind
}
newNodes.push(node);
}
ko.bindingHandlers.masonry = {
defaultItemClass: 'grid-item',
// Wrap value accessor with options to the template binding,
// which implements the foreach logic
makeTemplateValueAccessor: function (valueAccessor) {
return function () {
var modelValue = valueAccessor(),
options,
unwrappedValue = ko.utils.peekObservable(modelValue); // Unwrap without setting a dependency here
options = {
afterAdd: afterAdd
};
// If unwrappedValue.data is the array, preserve all relevant
// options and unwrap value so we get updates
ko.utils.unwrapObservable(modelValue);
ko.utils.extend(options, {
'foreach': unwrappedValue.data,
'as': unwrappedValue.as,
'includeDestroyed': unwrappedValue.includeDestroyed,
'templateEngine': ko.nativeTemplateEngine.instance
});
return options;
};
},
'init': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
console.log({ msg: 'Initializing binding' });
itemClass = ko.bindingHandlers.masonry.defaultItemClass;
masonryOptions = {};
haveInitialized = false;
$container = $(element);
var parameters = ko.utils.unwrapObservable(valueAccessor());
if (parameters && typeof parameters == 'object' && !('length' in parameters)) {
if (parameters.masonryOptions) {
var clientOptions;
if (typeof parameters.masonryOptions === 'function') {
clientOptions = parameters.masonryOptions();
if (typeof clientOptions !== 'object') {
throw new Error('masonryOptions callback must return object');
}
} else if (typeof parameters.masonryOptions !== 'object') {
throw new Error('masonryOptions must be an object or function');
} else {
clientOptions = parameters.masonryOptions;
}
ko.utils.extend(masonryOptions, clientOptions);
}
if (parameters.itemClass) {
itemClass = parameters.itemClass;
}
}
// Initialize template engine, moving child template element to an
// "anonymous template" associated with the element
ko.bindingHandlers.template.init(
element,
ko.bindingHandlers.masonry.makeTemplateValueAccessor(valueAccessor)
);
return { controlsDescendantBindings: true };
},
'update': function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.bindingHandlers.template.update(element,
ko.bindingHandlers.masonry.makeTemplateValueAccessor(valueAccessor),
allBindingsAccessor, viewModel, bindingContext);
// Make this function depend on the view model, so it gets called for updates
var data = ko.bindingHandlers.masonry.makeTemplateValueAccessor(
valueAccessor)().foreach;
ko.utils.unwrapObservable(data);
if (!haveInitialized) {
masonryOptions.itemSelector = '.' + itemClass;
console.log({msg: 'Binding update called for 1st time, initializing Masonry', options: masonryOptions});
$container.masonry(masonryOptions);
}
else {
console.log({ msg: 'Binding update called again, appending to Masonry', elements: newNodes });
var newElements = $(newNodes);
$container.masonry('appended', newElements);
$container.masonry('layout');
newNodes.splice(0, newNodes.length); // reset back to empty
}
// Update gets called upon initial rendering as well
haveInitialized = true;
return { controlsDescendantBindings: true };
}
};
})();
以下是使用中绑定的示例:
<div class="grid" data-bind="masonry: {data: blogEntries, masonryOptions: { itemClass: 'grid-item', columnWidth: 320, gutter: 10}}">
<div class="grid-item">
<div data-bind="css: {'idea-blog': isIdea }">
<img data-bind="attr: { src: imageUrl }">
<h2 data-bind="text: title"></h2>
<p data-bind="text: description"></p>
<div class="button-keep-reading">
<a data-bind="attr: { src: articleUrl }"><span data-bind="text: linkText"></span> ></a>
</div>
</div>
</div>
</div>
请注意,在绑定数据之前,您需要确保在砌块拼贴中使用的任何图像都已加载,因为砌体在布局问题时会出现问题。