Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

KnockoutJS: fade in after fading out something else

Say I have a set of 3 radio buttons:

<div>
    <label>
        <input type="radio" name="Who" value="Myself" 
            checked="@isMyselfChecked" data-bind="checked: who" />
        Mine
    </label>
    <label>
        <input type="radio" name="Who" value="MemberId" 
            checked="@isMemberIdChecked" data-bind="checked: who" />
        I know the member's ID
    </label>
    <label>
        <input type="radio" name="Who" value="MemberUrl" 
            checked="@isMemberUrlChecked" data-bind="checked: who" />
        I know the member's URL
    </label>
</div>

When the user selects the first radio button (Mine/Myself), no additional input is required. However, when selecting the second or third, additional input is required:

<div>
    <input type="text" name="MemberId" placeholder="Enter Member ID" 
        data-bind="toggleWho: who()" style="display: none" />
    <input type="text" name="MemberUrl" placeholder="Enter Member URL" 
        data-bind="toggleWho: who()" style="display: none; width: 450px;" />
</div>

It is easy enough to just have data-bind="visible: who() === '[MemberId|MemberUrl]'" on the dependent text boxes. However, what if I want to add fade in/out transitions?

I tried out the example custom fadeVisible bindingHandler from the knockout site, and I understand how it works. However this will fade out and fade in the text boxes at the same time. If radio 'MemberId' is selected, and user selects 'MemberUrl' radio, I want the MemberId text box to fade out before the MemberUrl text box fades in.

Below is what I have now, and it works, but I don't think it's optimal. How else can knockout be told to not perform the fade in until a previous element has been faded out? Do I need another ko.observale, or possibly a ko.computed?

var viewModel = {
    fadeSpeed: 150,
    who: ko.observable($('input[type=radio][name=Who]:checked').val())
};

ko.bindingHandlers.toggleWho = {
    init: function (element, valueAccessor) {
        var value = valueAccessor();
        var unwrapped = ko.utils.unwrapObservable(value);
        if (unwrapped === element.name)
            $(element).show();
    },
    update: function (element, valueAccessor) {
        var value = valueAccessor();
        var unwrapped = ko.utils.unwrapObservable(value);

        // when selected value is myself, fade out the visible one, if any
        if (unwrapped === 'Myself') {
            $('input[type=text][name=MemberId]:visible')
                .fadeOut(viewModel.fadeSpeed);
            $('input[type=text][name=MemberUrl]:visible')
                .fadeOut(viewModel.fadeSpeed);
        }

            // when selected value is memberid, may need to fade out url first
        else if (unwrapped === 'MemberId') {
            if ($('input[type=text][name=MemberUrl]:visible').length > 0) {
                $('input[type=text][name=MemberUrl]:visible')
                    .fadeOut(viewModel.fadeSpeed, function () {
                        $('input[type=text][name=MemberId]')
                            .fadeIn(viewModel.fadeSpeed);
                    });
            } else {
                $('input[type=text][name=MemberId]')
                    .fadeIn(viewModel.fadeSpeed);
            }
        }

            // when selected value is memberurl, may need to fade out id first
        else if (unwrapped === 'MemberUrl') {
            if ($('input[type=text][name=MemberId]:visible').length > 0) {
                $('input[type=text][name=MemberId]:visible')
                    .fadeOut(viewModel.fadeSpeed, function () {
                        $('input[type=text][name=MemberUrl]')
                            .fadeIn(viewModel.fadeSpeed);
                });
            } else {
                $('input[type=text][name=MemberUrl]')
                    .fadeIn(viewModel.fadeSpeed);
            }
        }
    }
};

ko.applyBindings(viewModel);
like image 934
danludwig Avatar asked Jul 11 '12 13:07

danludwig


2 Answers

You will have to adapt this a little to fit your example, but I needed to simplify it to test in this fiddle.

Here is the binding:

var previousElement = null;
ko.bindingHandlers.fadeSwitcher = {
    init: function(element, valueAccessor) {
        var value = valueAccessor();
        $(element).toggle(ko.utils.unwrapObservable(value));
    },
    update: function(element, valueAccessor) {

        var value = ko.utils.unwrapObservable(valueAccessor());
        if (value) {
            if (previousElement == null) { // initial fade
                $(element).fadeIn();
            } else {
                $(previousElement).fadeOut('fast', function() {
                    $(element).fadeIn();
                });
            }
            previousElement = element;
        }        
    }
};
like image 91
Kyeotic Avatar answered Sep 30 '22 19:09

Kyeotic


A bit late to the party, but maybe it is of use to someone else.

I've taken Tyrsius' answer and changed it to fix my own needs; this version deals with an observable property and will fadeOut/fadeIn the old/new value whenever it changes.

Usage example: <span data-bind="fadeSwitcher: myObservable"></span>

ko.bindingHandlers.fadeSwitcher = {
    init: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        element.setAttribute('previousValue', value);
        ko.bindingHandlers.text.update(element, ko.observable(value));
    },
    update: function (element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor());
        var previousValue = element.getAttribute('previousValue');
        if (value !== previousValue) {
            $(element).fadeOut('fast', function () {
                ko.bindingHandlers.text.update(element, ko.observable(value));
                $(element).fadeIn();
            });
            element.setAttribute('previousValue', value);
        }
    }
};
like image 26
MartijnK Avatar answered Sep 30 '22 20:09

MartijnK