使用Masonry的自定义绑定,我将如何适当地调整容器的大小

时间:2013-10-31 03:50:05

标签: knockout.js jquery-masonry bindinghandlers

基于给出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中的外观截图。 Masonry example with Knockout

1 个答案:

答案 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> &gt;</a>
            </div>
        </div>
    </div>
</div>

请注意,在绑定数据之前,您需要确保在砌块拼贴中使用的任何图像都已加载,因为砌体在布局问题时会出现问题。