当foreach具有计算的observable时,动画转换beforeRemove / afterAdd

时间:2015-04-19 13:38:37

标签: javascript jquery knockout.js computed-observable

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

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

我使用计算的observable来过滤我原来的慈善数组。计算是与foreach数据绑定的,我想让整个容器(带有类.tab)在任何更改之前淡出,并在更改之后淡出。

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

实例,包含(失败)动画:http://jsfiddle.net/fy7au6x6/1/

有关如何控制使用动画计算的更改时间的任何建议吗?

这是两个阵列,"所有慈善机构"和"按类别"过滤的慈善机构:

self.allCharities = ko.observableArray([
    new Charity(0, "Amnesty International", "$2,466", "HUMANITARIAN"),
    new Charity(1, "Richard Dawkins Foundation", "$0", "EDUCATION"),
    new Charity(2, "Khaaaan Academy", "13,859", "EDUCATION"),
    new Charity(4, "Wikipedia", "$7,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);

2 个答案:

答案 0 :(得分:2)

与您对问题的评论中提供的解决方案相反,我建议您不要在viewmodel方法中混合使用DOM处理和ViewModel功能。一般来说,我建议避免做任何使视​​图模型依赖于DOM的事情。

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

在某些情况下,您可能不希望为其创建自定义绑定,但只希望您的foreach绑定可以使用动画方法。在这些情况下,将这些方法放在viewmodel上可能是一种更实用的方法。但是,如果你这样做,我建议你根据这些方法避免使用你的viewmodel功能,只保留它们来做DOM动画逻辑。

鉴于这种方法,您的viewmodel可能看起来类似于(复制小提琴中的那个,然后添加动画方法):

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", "$2,466", "HUMANITARIAN"),
        new Charity(1, "Richard Dawkins Foundation", "$0", "EDUCATION"),
        new Charity(2, "Khaaaan Academy", "13,859", "EDUCATION"),
        new Charity(4, "Wikipedia", "$7,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>

我已使用上述代码更新了您的小提琴,您可以在http://jsfiddle.net/LkqTU/23825/找到它。

如果您希望自己创建多个实例,那么将这些方法添加到viewmodel构造函数原型中也是一个好主意(以及更多&#34;正确&#34;)。

答案 1 :(得分:1)

这是使用自定义绑定处理程序的更多 clean 答案。

诀窍是使用一个布尔表示,基本上,“我即将改变”......当我们将其设置为true时,我们会使用简单的绑定处理程序淡出。

一旦过滤器处理完毕并准备就绪,我们将相同的布尔值设置为false,这实际上就是说“我已经完成了......”当发生这种情况时,我们的小处理程序会淡入。

诀窍是使用订阅和第二个可观察数组而不是计算数组。这允许你将boolean设置为true,填充辅助observable,然后将observable设置为false ...这可以驱动淡入淡出行为,而不必担心绑定行为的时间。

<强>小提琴:

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", "$2,466", "HUMANITARIAN"),
        new Charity(1, "Richard Dawkins Foundation", "$0", "EDUCATION"),
        new Charity(2, "Khaaaan Academy", "13,859", "EDUCATION"),
        new Charity(4, "Wikipedia", "$7,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();
相关问题