Miika L.
Miika L.

Reputation: 3353

Prevent change event on a textarea when clicking an external cancel button

I have the following scenario: A textarea has an autosave currently bound to the change event. When the textarea receives focus, it will also display Save and Cancel buttons hanging off the bottom, so that the user has something to click in case they are uncomfortable just moving off the control or clicking outside it into empty space.

The save button is basically just a dummy button. When the user click it, focus is naturally lost from the textarea, and the change event is triggered. Everything also works fine for clicking outside the textarea to make the save happen.

The problem is with the Cancel button. Clicking the Cancel button causes the textarea to lose focus, and thus fire the change event, which is handled before the click event of the button. With a save and a cancel event being sent to the server nearly at the same time, its not possible to be certain in which order they are processed.

The example can be seen here: http://jsfiddle.net/vwu79wd1/1/

HTML

<div class="largerEventContainer">
    <div class="inlineControls" style="width:200px;">
    <textarea name="SomeText" data-prevvalue="Original Text" style="width:100%">Original Text</textarea>
    </div>
</div>
<div id="log"></div>

CSS

.inlineControls {
    position:relative;
    margin-bottom:1em;
}

.inlineControls textarea, .inlineControls input {
    margin-bottom: 0px;
}

.inlineControls textarea:focus ~ .saveOptions {
    display: block;
}

.saveOptions:hover {
    display:block;
}

.saveOptions {
    display:none;
    position:absolute; 
    right:0px; 
    top:100%;
    background-color: #f0f0f0;
    border: 1px solid #ccc;
    padding: 3px;
    outline: none;
    border-top:none;
    border-radius: 0 0 3px 3px;
}

Javascript

$('.largerEventContainer').on('change', 'input, textarea', function () {
    $('#log').append('<div>saving</div>');
});

$(document).on('focus', '.inlineControls textarea', function () {
    var $control = $(this)

    if ($(this).closest('.inlineControls').find('.saveOptions').length == 0) {
        $(this).closest('.inlineControls')
            .append($(document.createElement('div'))
                .addClass('saveOptions')
                .append($(document.createElement('span'))
                    .css('padding', '4px')
                    .html('Save')
                    .click(function () {
                        $(this).closest('.saveOptions').remove();
                    }))
                    .append($(document.createElement('span'))
                        .css('padding', '4px')
                        .html('Cancel'))
                        .click(function () {
                            var $control =     $(this).closest('.inlineControls').find('textarea')
                            $control.val($control.data('prevvalue'));
                            $('#log').append('<div>reset</div>');
                            $(this).closest('.saveOptions').remove();
            }));
    }
});

What I have tried or considered but dismissed as either not working or too clunky

Is there any way I can elegantly detect that the cancel button is (will be) clicked before the change event is allowed to proceed?

Upvotes: 3

Views: 2109

Answers (3)

Miika L.
Miika L.

Reputation: 3353

From my own testing and Jav Rok's answer, mousedown seems to be the way to go. The browsers do behave a little differently depending on exactly what is done though. If the value is restored to the original value (rather than the previous one) then Firefox will trigger a change event, but chrome, IE and iPad Safari won't. Additionally, IE and iPad Safari require a manual call to .blur() or it won't actually leave the control (especially annoying on the iPad because of the on-screen keyboard).

So with some minor adjustments the acceptable behaviour can be reached with the following fiddle: http://jsfiddle.net/Lcyte5h5/14/

The cancel button handler is as follows:

$('.largerEventContainer').on('change', 'input, textarea', function () {
    $('#log').append('<div>change</div>');
    if ($(this).val() != $(this).data('prevvalue')) {
    	$('#log').append('<div>saving</div>');
        $(this).data('prevvalue', $(this).val())
    }
});


$(document).on('focus', '.inlineControls textarea', function () {
    var $control = $(this)

    if ($(this).closest('.inlineControls').find('.saveOptions').length == 0) {
        $(this).closest('.inlineControls')
            .append($(document.createElement('div'))
                    .addClass('saveOptions')
                    .append($(document.createElement('span'))
                        .css('padding', '4px')
                        .html('Save')
                        .click(function () {
                            $(this).closest('.saveOptions').remove();
        }))
            .append($(document.createElement('span'))
                .css('padding', '4px')
                .html('Cancel')
                .on('mousedown', function () {
                    var $control = $(this).closest('.inlineControls').find('textarea')
                    $control.val($control.data('prevvalue'));
    		        $('#log').append('<div>reset</div>');
                    $control.blur();
                    $(this).closest('.saveOptions').remove();
        })));
    }
});

Upvotes: 0

user4257136
user4257136

Reputation:

I suggest you to save content of text area into a variable temporary,While at mouseOut event.

And save value to database only after 'save' button is clicked

Upvotes: 0

Jav Rok
Jav Rok

Reputation: 331

I think the solution should be a different approach, like actually saving the changes only on 'Save' button click, or maybe using local storage to save the changes before commiting. Obviously you should put an alert before user tries to move out from the page.

But anyway, testing the order of events, I discovered a surprising thing: 'mousedown' event on the CANCEL button fires first, before 'change' event on the textarea. Try it, maybe that works, even if doesn't make sense...

Upvotes: 1

Related Questions