开发者

jQuery: detect if certain button clicked within 'change' event handler

Forgive me if this is simple - I have been away from computers and JS for a month, so this task is seeming impossible to me, where I know it shouldn't be.

The quickest way I can think of explaining it is to put it in code, using comments to explain how it works. I'll indicate in these comments which is the part that I can't figure out.

I have a text field styled to look non-editable, with an 'edit' button next to it which, when clicked, turns into a 'save' button, and makes the text field look editable.

$(".edit_btn").click(function() {
   // make the associated text field look editable, and change this 'edit'
   // button into a 'save' button. Then place focus on the text in
   // in the field.
});

$(".save_btn").click(function() {
   // if (value in associated text field has changed from when page loaded)
   //   submit the form and save this new text
   // else 
   //   revert to non-editable mode (hide this 'save' button, 
   //   and show 'edit' button)
});

All that works fine. However, if the user leaves the editable text field I want to be able to detect whether they've left the field to click the 'save' button, or just clicked anywhere else on the page. So within the 'blur' event handler for the field, I need to know if I've clicked the 'save button. For the life of me I can't figure out how to do it:

$('input[name^="tgfld_"]').blur(function() {
    // if (save button has been clicked) <- this is the problem
    //   don't do anything since the save function will handle this
    // else if (value in the field hasn't changed)
    //   re开发者_如何学JAVAvert everything back to non-editable mode
    // else if (value in the field HAS changed)
    //   do a window.confirm and prompt the user to click the 'save' 
    //   button to save their changes
});

So it's detecting if the save button was the reason for the 'blur' being triggered - that's what I can't figure out.

Or, if this is completely the wrong way of handling the situation, please let me know.

(I should point out that there could be many of these field/button combinations on the page, but so far that isn't affecting anything.)


Introduction

This is a very interesting (and non-trivial IMHO) problem. To solve it I first created a sample page that had multiple "groups" of text input boxes and buttons that enable them:

jQuery: detect if certain button clicked within 'change' event handler

As per your question, the input box is disabled by default and made "editable" by clicking a button.

The best solution turned out to be a simple state machine. The state machine helps make sense of the (large amount of) events being fired by the browser by only looking at events that apply to the current state and ignoring all others.

Implementation

The following is a diagram of the state machine (text next to each transition arrow specifies the source and name of the event that triggers that transition):

jQuery: detect if certain button clicked within 'change' event handler

Solution in action (JS Fiddle project)

For reference I'm also including the JavaScript code here:

function makeEditable(inputId, btnId) {
    var state = "Locked", timeout = null, $input = $("#" + inputId), $btn = $("#" + btnId);

    function setStateNow(precondition, newState, e) {
        if (!precondition || state === precondition) {
            if (window.console) { window.console.log("State change: " + state + " => " + newState + " (from " + e.target.id + "." + e.type + ")"); }
            if (newState === "Locked") { // changing from any state to Locked
                $input.prop("disabled", true);
                $btn.val("Edit");
            } else if (state === "Locked") { // changing from Locked to any other state
                $input.prop("disabled", false).focus();
                $btn.val("Save");
            }
            if (newState === "LockPending") { // changing from any state to LockPending
                timeout = setTimeout(
                    function () { setStateNow("LockPending", "Locked", { target: { id: e.target.id }, type: "setTimeout" }); },
                    20);
            } else if (state === "LockPending") { // changing from LockPending to any other state
                if (timeout) {
                    clearTimeout(timeout);
                    timeout = null;
                }
            }
            if (newState === "Editable" && state === "LockPendingMouse") {
                $input.focus();
            }

            state = newState;
            return true;
        }
        return false;
    }


    function setState(e) {
        var r;
        if (e.data.rules) {
            for (i in e.data.rules) {
                r = e.data.rules[i];
                if (setStateNow(r.precondition, r.newState, e)) {
                    return;
                }
            }
        } else {
            setStateNow(e.data.precondition, e.data.newState, e);
        }
    }


    $input
        .focus ({ precondition: "LockPending", newState: "Editable" }, setState)
        .blur({ precondition: "Editable", newState: "LockPending" }, setState);

    $btn
        .click({ rules: [{ precondition: "Locked", newState: "Editable" }, { precondition: "LockPendingMouse", newState: "Locked" }, { precondition: "Editable", newState: "Locked" }] }, setState)
        .mousedown({ precondition: "Editable", newState: "LockPendingMouse" }, setState)
        .mouseleave({ precondition: "LockPendingMouse", newState: "Editable" }, setState)
        .focus ({ precondition: "LockPending", newState: "Editable" }, setState)
        .blur({ precondition: "Editable", newState: "LockPending" }, setState);
}

The code defines a single function, makeEditable. This function accepts ID of the input control and ID of the corresponding button that makes it editable. It then creates a closure with "private" state machine. This means that we will have one state machine per each makeEditable call (because different input-button groups may be in different states).

Actual "meat" of the state change handling (making the text box editable or disabled) can be found in setStateNow private function when current or new state is Locked.

Conclusion

I successfully tested the solution in Chrome 14, Opera 10.51, IE 9, FireFox 5.0.1, Safari 5.1, Mobile Safari (iPad2).

One UI behaviour that I would like to point out is an answer to a question What happens if the user presses the mouse button over the save button but while still holding the button leaves area of the button? In this case I decided to return back to Editable state and set focus to the input field again.

If you find bugs or think of a way to improve the implementation, please feel free to leave comments for this answer.


I'm sure there is probably a much better solution, but here is what I came up with:

$(document).ready(function() {
    saved = false;

    $('#button').click(function() {
        saved = true;
    });

    $('#input').blur(function() {
        var t = setTimeout("if(!saved) {alert('not saved!');}",400);
    }); 
});

example here

I introduced a slight delay with setTimeout because it appears that when clicking on the button, the blur event always fires first before the button's click event.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜