0

背景

我有一个带有网格的 asp.net webform,当用户更新该网格中的文本框时,onchange 事件会启动一个 WebMethod 调用并更新其余更改的行。那时没有保存任何内容——我们只是在更新 UI。

要提交更改,请单击保存按钮。

这实际上在几乎所有情况下都可靠地工作。然而,有一个非常顽固的问题感觉我应该能够解决,但现在是时候请专家了。

问题场景

我正在使用 jQuery 来捕获回车键,不幸的是该事件首先触发,导致页面在回调完成之前提交。该行未正确更新。保存陈旧和令人眼花缭乱的数据。

更新

我不认为您可以使输入行为取决于回调,因为您可以在不更改行的情况下保存。在这种情况下,如果您不更改行,它将永远不会保存。

现在,如果有某种方法可以检查 javascript 的内部待办事项列表,或者创建我自己的然后以某种方式管理它,那将起作用。但这对于应该很容易的事情来说是一些繁重的工作。因此,除非专家告诉我其他情况,否则我必须假设这是错误的。

尝试

现在我正在使用内置的 jQuery 事件,并且我有这个精心设计的 setTimeout 坚持尝试保存的事实,暂停足够长的时间以至少调用 WebMethod,并依靠回调进行提交. 但事实证明 javascript ansychrony 并没有按我希望的方式工作,并且 onchange 事件甚至不会触发,直到该代码块完成。这很令人惊讶。

我在想我可以使用我自己的小对象以正确的顺序排列这些事件,并找到一种聪明的方法来触发它,等等。

这一切似乎都是错误的方向。这肯定是疯狂的矫枉过正,这是一个常见问题,我忽略了一个简单的解决方案,因为我不在 javascript 24/7 中工作。

正确的?

代码

这就是我这一分钟做对的事情。这显然是行不通的——我试图利用 jquery 的异步特性,但所有这一切显然必须在行的 onchange 事件触发之前结束:

$(document).bind("keypress", function (e) {
    if (e.keyCode == 13) {
        handleEnter();
        return false; //apparently I should be using e.preventDefault() here. 
    }
});


function handleEnter() {
    setTimeout(function () {
        if (recalculatingRow) { //recalculatingRow is a bit managed by the onchange code.
            alert('recalculating...');
            return true; //recur
        }

        //$('input[id$="ButtonSave"]').click();
        alert('no longer recalculating. click!');
        return false;
    }, 1000);
}

然后一个典型的行看起来像这样。请注意,我没有使用 jquery 来绑定它:

 <input name="ctl00$MainContent$GridOrderItems$ctl02$TextOrderItemDose" type="text" value="200.00" maxlength="7" id="ctl00_MainContent_GridOrderItems_ctl02_TextOrderItemDose" onchange="recalculateOrderItemRow(this);" style="width:50px;" />

我可以发布 recalculateOrderItemRow 的代码,但它真的很长,现在的问题是它不会触发,直到 after keypress 事件结束。

更新 Dos 根据Nick Fitzgerald的说法(这是一篇很酷的文章),使用 setTimeout 应该会导致它变为异步。进一步挖掘 setTimeout 和 jQuery 之间的交互,以及普通 javascript 事件和 jQuery 事件之间的交互。

4

5 回答 5

2

Preventing ENTER shouldn't be causing you so much trouble! Make sure you have something like this on your code:

$(document).on('keydown', 'input', function(e) {
    if(e.keyCode == 13) {
       e.preventDefault();
    }
});

UPDATE

It looks like you do want to save on ENTER, but only after the UI is updated on change. That is possible. You could use a flag a Matthew Blancarte suggested above, trigger save from the change callback, and get rid of the setTimeout.

But I wouldn't recommend that. You are better off relying solely on the save button for saving. If you don't, your users will have to wait for two async operations to complete before saving is finished. So you'd have to block the UI, or keep track of all async operations, aborting some as needed. I think it's not worthy, ENTER becomes less intuitive for the users if saving takes too long.

于 2012-04-12T03:42:39.500 回答
2

下面这些可怕的变通办法,实际上花了我今天一整天和昨天一半的时间来写,似乎解决了每一个排列。

有趣的是,如果您调用 e.preventDefault(),enter 本身不会触发 onchange。为什么会呢?在单击保存按钮的默认行为发生之前,实际上不会发生更改。

关于这一点的其他内容很少有趣。

//Used in handleEnter and GridOrderItems.js to handle a deferred an attempt to save by hitting enter (see handleEnter).
var isSaving = false; 
var saveOnID = '';

//When one of the fields that trigger WebMethods get focus, we put the value in here
//so we can determine whether the field is dirty in handleEnter.
var originalVal = 0;

//These fields trigger callbacks. On focus, we need to save their state so we can
//determine if they're dirty in handleEnter().
$('[id$=TextOrderItemDose], [id$=TextOrderItemUnits]').live("focus", function() {
    originalVal = this.value;
});

$(document).bind("keypress", function (e) {
    if (e.keyCode == 13) { //enter pressed.
        e.preventDefault();
        handleEnter();
    }
});

//Problem:
//In the products grid, TextOrderItemDose and TextOrderItemUnits both have js in their onchange events
//that trigger webmethod calls and use the results to update the row. Prsssing enter is supposed to 
//save the form, but if you do it right after changing one of those text fields, the row doesn't always
//get updated due to the async nature of js's events. That leads to stale data being saved.  
//Solution:
//First we capture Enter and prevent its default behaviors. From there, we check to see if one of our
//special boxes has focus. If so, we do some contortions to figure out if it's dirty, and use isSaving
//and saveOnID to defer the save operation until the callback returns. 
//Otherwise, we save as normal.
function handleEnter() {
    var focusedElement = $("[id$=TextOrderItemDose]:focus, [id$=TextOrderItemUnits]:focus")

    //did we press enter with a field that triggers a callback selected?
    if (isCallbackElement(focusedElement) && isElementDirty(focusedElement)) { 
        //Set details so that the callback can know that we're saving.
        isSaving = true;
        saveOnID = focusedElement.attr('id');

        //Trigger blur to cause the callback, if there was a change. Then bring the focus right back.
        focusedElement.trigger("change");
        focusedElement.focus();
    } else {
        forceSave();
    }
}

function isCallbackElement(element) {
    return (element.length == 1);
}

function isElementDirty(element) {
    if (element.length != 1) 
        return false;

    return (element.val() != originalVal);
}

function forceSave() {
    isSaving = false;
    saveOnID = '';

    $('input[id$="ButtonSave"]').click();
}

这在文本框的更改事件中被调用:

function recalculateOrderItemRow(textbox) {
    //I'm hiding a lot of code that gathers and validates form data. There is a ton and it's not interesting.

    //Call the WebMethod on the server to calculate the row. This will trigger a callback when complete.
    PageMethods.RecalculateOrderItemRow($(textbox).attr('id'),
                                   orderItemDose,
                                   ProductItemSize,
                                   orderItemUnits,
                                   orderItemUnitPrice,
                                   onRecalculateOrderItemRowComplete);

}

然后,在 WebMethod 回调代码的末尾,我们将更新的表单值拉出,使用 jquery.caret 将插入符号放在需要的位置,并检查是否需要强制保存:

function onRecalculateOrderItemRowComplete(result) {
    var sender, row;

    sender = $('input[id="' + result.Sender + '"]');
    row = $(sender).closest('tr');

    row.find('input[id$="TextOrderItemDose"]').val(result.Dose);
    row.find('input[id$="TextOrderItemUnits"]').val(result.Units);
    row.find('span[id$="SpanTotalPrice"]').html(formatCurrency(result.TotalPrice));

    calculateGrandTotalPrice();
    $(document.activeElement).select();

    if (isSaving && saveOnID == result.Sender) {
        forceSave();
    }
}

result.Sender 是调用控件的ID,我塞进了WebMethod调用,然后返回。saveOnID 可能并不完美,实际上维护一个活动/未回调 WebMethod 调用的计数器可能会更好,以完全确保在保存之前一切都结束了。唷。

于 2012-04-12T17:36:29.773 回答
1

You could unbind the Enter key capture while you are in the onChange event, then rebind it at the end of the callback function. If you post some code, I could give a more specific answer.

于 2012-04-12T03:27:01.240 回答
1

你可以发布你的javascript吗?听起来你在正确的轨道上。在进行 AJAX 调用之前,我会更改我的 OnChange 事件以增加一个变量。我将调用变量 inProcess 并将其初始化为零。当 AJAX 调用返回时,我会将 inProcess 更新为当前值减一。在 Enter 键事件中,我会检查 inProcess 是否为零。如果没有,您可以警告用户或设置超时以稍后重试。

于 2012-04-12T02:48:03.003 回答
0

听起来您不应该异步调用 WebMethod 。同步调用它,成功后保存数据。

于 2012-04-12T03:20:04.850 回答