动画转换 beforeRemove/afterAdd when foreach with computed observable

Animated transition beforeRemove/afterAdd when foreach with computed observable

我在尝试使用 jquery 动画时面临一些挑战,例如 fadeIn() fadeOut() with knockout。

实例,无动画:http://jsfiddle.net/LkqTU/23801/

我使用一个计算的可观察对象来过滤我原来的慈善机构数组。计算的数据与 foreach 绑定,我想让整个容器(使用 class .tab)在任何更改之前淡出,在更改之后淡入。

我已经尝试使用内置的 beforeRemove 和 afterAdd 属性,但是在计算我的数组时这似乎不起作用。正如下面的实例所示,容器被一些慈善机构的多个实例填满,即使底层计算数组只包含正确的数组。

现场示例,带有(失败的)动画:http://jsfiddle.net/fy7au6x6/1/

关于如何控制动画计算的更改时间的任何建议?

这是两个数组,"All charities"和"Charities filtered by category":

self.allCharities = ko.observableArray([
    new Charity(0, "Amnesty International", ",466", "HUMANITARIAN"),
    new Charity(1, "Richard Dawkins Foundation", "[=11=]", "EDUCATION"),
    new Charity(2, "Khaaaan Academy", "13,859", "EDUCATION"),
    new Charity(4, "Wikipedia", ",239",  "EDUCATION")
]);

self.filteredCharities = ko.computed(function () {

    // If no category is selected, return all charities
    if (!self.selectedCategory())
        return self.allCharities();

    // Return charities in the selected category
    return ko.utils.arrayFilter(self.allCharities(), function (c) {
        return (c.Category() == self.selectedCategory());
    });

}, this);

与您问题的评论中给出的解决方案相反,我建议您不要在 viewmodel 方法中混合使用 DOM-handling 和 ViewModel 功能。通常,我建议避免做任何使视​​图模型依赖于 DOM.

的事情

当谈到 foreach 绑定的动画时,我首先建议创建 一个自定义绑定处理程序,它实际上会使用 foreach 绑定并添加您想要的动画。这样,您可以将与 DOM 相关的代码保留在视图或 bindingHandlers 中,它们应该在的位置。

在某些情况下,您可能不想为其创建自定义绑定,而只是希望动画方法可用于您的 foreach 绑定。在这些情况下,将这些方法放在视图模型上可能是一种更实用的方法。但是,如果您这样做,我建议您完全避免让视图模型功能依赖于这些方法,只保留它们以执行 DOM 动画逻辑。

鉴于这种方法,您的视图模型可能看起来类似于(复制 fiddle 中的视图模型,然后添加动画方法):

function ViewModel() {
    var self = this;
    self.selectedCategory = ko.observable("");

    self.setCategory = function (newCat) {
        self.selectedCategory(newCat);
    };

    self.allCharities = ko.observableArray([
        new Charity(0, "Amnesty International", ",466", "HUMANITARIAN"),
        new Charity(1, "Richard Dawkins Foundation", "[=10=]", "EDUCATION"),
        new Charity(2, "Khaaaan Academy", "13,859", "EDUCATION"),
        new Charity(4, "Wikipedia", ",239",  "EDUCATION")
    ]);

    self.filteredCharities = ko.computed(function () {

        // If no category is selected, return all charities
        if (!self.selectedCategory())
            return self.allCharities();

        // Return charities in the selected category
        return ko.utils.arrayFilter(self.allCharities(), function (c) {
            return (c.Category() == self.selectedCategory());
        });

    }, this);

    var fadeAnimationDuration = 500;
    self.animationAfterAddingCharityElementsToDom = function(element){
        //Since this method will be depending on the DOM, avoid having 
        //the viewmodel functionality depending on this method

        //First hide the new element
        var $categoryDomElement = $(element);
        $categoryDomElement.hide();
        var $tabDomElement = $categoryDomElement.parent();
        $tabDomElement.fadeOut(fadeAnimationDuration, function(){
            //When the tab has faded out, show the new element and then fade the tab back in
            $categoryDomElement.show();
            $tabDomElement.fadeIn(fadeAnimationDuration);
        });
    };
    self.animationBeforeRemovingCharityElementsFromDom = function(element){
        //Since this method will be depending on the DOM, avoid having 
        //the viewmodel functionality depending on this method

        var $categoryDomElement = $(element);
        var $tabDomElement = $categoryDomElement.parent();
        $tabDomElement.fadeOut(fadeAnimationDuration, function(){
            //When the tab has faded out, remove the element and then fade the tab back in
            $categoryDomElement.remove();
            $tabDomElement.fadeIn(fadeAnimationDuration);
        });
    };
};

然后您的绑定将是:

<div class="tab" data-bind="foreach: { data: filteredCharities, afterAdd: animationAfterAddingCharityElementsToDom, beforeRemove: animationBeforeRemovingCharityElementsFromDom }">
    <div class="tab-tile mb21" data-bind="css:{'mr21':$index()%3 < 2}">
        <a href="#" class="amount" data-bind="text: Amount"></a>
        <a href="#" class="title" data-bind="text: Name"></a>
        <a href="#" class="category" data-bind="text: Category"></a>
    </div>
</div>

我已经用上面的代码更新了你的fiddle,你可以在http://jsfiddle.net/LkqTU/23825/找到它。

如果您希望创建多个实例,那么将这些方法添加到视图模型构造函数原型中也是一个好主意(以及更多 "correct")。

这里有一个使用自定义绑定处理程序的更 干净 的答案。

诀窍是使用一个布尔值,本质上说,"I am about to change"...当我们将其设置为 true 时,我们使用简单的绑定处理程序淡出。

过滤器处理完毕并准备就绪后,我们将相同的布尔值设置为 false,本质上说,"I am done"...我们的小处理程序会在发生这种情况时淡出。

诀窍是使用订阅和第二个可观察数组而不是计算数组。这允许您将布尔值设置为真,填充辅助可观察对象,然后将该可观察对象设置为假...这可以驱动淡入淡出行为,而不必担心绑定行为的时间。

Fiddle:

http://jsfiddle.net/brettwgreen/h9m5wb8k/

HTML:

<div class="side-bar">
    <a href="#" class="category" data-bind="click: function(){ setCategory('')}">All</a>
    <a href="#" class="category" data-bind="click: function(){ setCategory('EDUCATION')}">Education</a>
    <a href="#" class="category" data-bind="click: function(){ setCategory('HUMANITARIAN')}">Humanitarian</a>
</div>

<div class="tab" data-bind="fader: filtering, foreach: filteredCharities">
    <div class="tab-tile mb21" data-bind="css:{'mr21':$index()%3 < 2}">
        <a href="#" class="amount" data-bind="text: Amount"></a>
        <a href="#" class="title" data-bind="text: Name"></a>
        <a href="#" class="category" data-bind="text: Category"></a>
    </div>
</div>

JS:

ko.bindingHandlers.fader = {
    update: function(element, valueAccessor) {
        var obs = valueAccessor();
        var val = ko.unwrap(obs);
        if (val) {
            $(element).fadeOut(500);
        }
        else
        {
            $(element).fadeIn(500);
        }
    }
};

function Charity(id, name, amount, category) {
    var self = this;
    self.Id = ko.observable(id);
    self.Name = ko.observable(name);
    self.Amount = ko.observable(amount);
    self.Category = ko.observable(category);
}

// ----------------------------------------------------------
// VIEWMODEL ------------------------------------------------
// ----------------------------------------------------------
function ViewModel() {
    var self = this;
    self.selectedCategory = ko.observable("");

    self.filtering = ko.observable(false);
    self.setCategory = function (newCat) {
        self.filtering(true);
        window.setTimeout(function() {self.selectedCategory(newCat);}, 500);
    };

    self.allCharities = ko.observableArray([
        new Charity(0, "Amnesty International", ",466", "HUMANITARIAN"),
        new Charity(1, "Richard Dawkins Foundation", "[=11=]", "EDUCATION"),
        new Charity(2, "Khaaaan Academy", "13,859", "EDUCATION"),
        new Charity(4, "Wikipedia", ",239",  "EDUCATION")
    ]);
    self.filteredCharities = ko.observableArray(self.allCharities());

    self.selectedCategory.subscribe(function(newValue) {
        self.filtering(true);
        console.log(newValue);
        if (!newValue)
            self.filteredCharities(self.allCharities());
        else {
            var fChars = ko.utils.arrayFilter(self.allCharities(), function (c) {
                return (c.Category() === newValue);
            });
            self.filteredCharities(fChars);
        };
        self.filtering(false);
    });

};

// ----------------------------------------------------------
// DOCUMENT READY FUNCTION ----------------------------------
// ----------------------------------------------------------

$(document).ready(function () {

    ko.applyBindings(vm);
});

var vm = new ViewModel();