項目分享七:客戶端防止表單重復提交


防止表單數據重復提交,是 APP 常見而又必須具備的功能。客戶端最常見的做法是,當用戶點擊按鈕的時候,首先把按鈕給禁用,待數據完全提交到服務端后,再讓按鈕處於啟用的狀態。如下圖中的“結算”按鈕。

道理很簡單,實現起來也不難。但是如果全部代碼都這樣子去寫,未免太煩瑣。我們看一下 ChiTu Store 是如何封裝的。(注:客戶防止重復提交,不意味着服務端不需要防止重復提交。)

一、結算代碼

打開 App/Module/Shopping/ShoppingCart.html 頁面,我們找到結算按鈕,結算按鈕綁定到 buy 方法的。

<button class="btn btn-primary" type="button" data-bind="click:buy, disable:productsCount()<=0">結算(<span data-bind="text:productsCount"></span></button>

我們再來看一下 buy 方法(注:代碼有刪減),值得注意的時候,buy 方法是返回一個 Deferred 對象,也就是 jquery 中的類型為 JQueryPromise 的對象。關於 Promise 對象,這里不展開講,不了解自行 Google。在后面,我會告訴大家,為什么要返回一個 JQueryPromise 對象。

    buy = () => {
        var deferred = $.Deferred();
        shopping.createOrder(productIds, quantities)
            .done((order) => {
                app.redirect('Shopping_OrderProducts_' + order.Id());
                deferred.resolve(order);
            })
            .fail((data) => {
                deferred.reject(data);
            });

        return deferred;
    }

二、Click事件的封裝代碼

打開 App/Core/ko.ext.ts 文件,找到關於 click 綁定的代碼。我們可以看得到,上面的實現,又是通過重寫 knockout js 的 click 綁定來實現的。它主要的實現過程,封裝在了 translateClickAccessor 函數中。

var _click = ko.bindingHandlers.click;
ko.bindingHandlers.click = {
    init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
        valueAccessor = translateClickAccessor(element, valueAccessor, allBindings, viewModel, bindingContext);
        return _click.init(element, valueAccessor, allBindings, viewModel, bindingContext);
    }
};

關於 translateClickAccessor 函數(代碼有刪減),是這樣子:

function translateClickAccessor(element, valueAccessor, allBindings, viewModel, bindingContext) {
    var value = ko.unwrap(valueAccessor());
    if (value == null) {
        return valueAccessor;
    }

    return $.proxy(function () {
        var element = this._element;
        var valueAccessor = this._valueAccessor;
        var allBindings = this._allBindings;
        var viewModel = this._viewModel;
        var bindingContext = this._bindingContext;
        var value = this._value;

        return function (viewModel) {

            var deferred: JQueryPromise<any> = $.Deferred<any>().resolve();

            deferred = deferred.pipe(function () {
                var result = $.isFunction(value) ? value(viewModel, event) : value;
                if (result && $.isFunction(result.always)) {
                    $(element).attr('disabled', 'disabled');
                    $(element).addClass('disabled');
                    result.element = element;

                    result.always(function () {
                        $(element).removeAttr('disabled');
                        $(element).removeClass('disabled');
                    });

                    //===============================================
                    // 超時去掉按鈕禁用,防止 always 不起作用。 
                    setTimeout($.proxy(function () {
                        $(this._element).removeAttr('disabled');
                        $(this._element).removeClass('disabled');
                    }, { _element: element }), 1000 * 20);
                    //===============================================

                  

                    });
                }
                return result;
            });



            return deferred;
        };
    },
        { _element: element, _valueAccessor: valueAccessor, _allBindings: allBindings, _viewModel: viewModel, _bindingContext: bindingContext, _value: value });
}

我們這來看第一句:var value = ko.unwrap(valueAccessor())  這句話是獲取綁定到 click 方法的函數的返回值,在我們這個例子里,是 JQuery.Deferred 對象。

為什么我們要求是 JQuery.Deferred(JQueryPromise) 對象?因為 JQueryPromise 有 done,fail,always 等方法,通過這些方法,我們可以知道任務是否已經完成

下面這幾行代碼,判斷 click 所綁定方法返回的結果是否為 JQueryPromise 對象,當然,這種判斷不是百分百准確,但是對於絕大多數情況來說應該是沒有問題的。只有返回的對象是 JQueryPromise對象,我們才進行處理(if 邏輯塊代碼)。

 var result = $.isFunction(value) ? value(viewModel, event) : value;
 if (result && $.isFunction(result.always)) {
      //..........
}

我們來看一下相關的代碼,下面這段代碼,還是挺好理解,首先要做的就是在 element 元素(在我們的例子中,是結算按鈕),加上 disabled 的屬性,然后加上一個 disabled 的 class,當執行完成后,如論是成功還是失敗,都取消禁用。當然,我們還要作一個超時的處理,這時的超時設置為 2 秒。

$(element).attr('disabled', 'disabled');
$(element).addClass('disabled');
result.element = element;

result.always(function () {
    $(element).removeAttr('disabled');
    $(element).removeClass('disabled');
});

//===============================================
// 超時去掉按鈕禁用,防止 always 不起作用。 
setTimeout($.proxy(function () {
    $(this._element).removeAttr('disabled');
    $(this._element).removeClass('disabled');
}, { _element: element }), 1000 * 20);
//===============================================

相關的代碼,在 github 的 ChiTuStore 項目中可以找到。

 

 

項目分享六:圖片的延遲加載

項目分享五:H5圖片壓縮與上傳 

項目分享四:購物車頁面的更新 

項目分享三:頁面之間的傳值

項目分享二:APP 小紅點中數字的處理

項目分享一:在項目中使用 IScroll 所碰到的那些坑

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM