惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

NISL@THU
NISL@THU
Latest news
Latest news
Scott Helme
Scott Helme
T
Tenable Blog
Simon Willison's Weblog
Simon Willison's Weblog
T
The Exploit Database - CXSecurity.com
C
CERT Recently Published Vulnerability Notes
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 司徒正美
K
Kaspersky official blog
The Hacker News
The Hacker News
Jina AI
Jina AI
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
Cisco Blogs
S
Secure Thoughts
雷峰网
雷峰网
Project Zero
Project Zero
T
Troy Hunt's Blog
IT之家
IT之家
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - Franky
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Know Your Adversary
Know Your Adversary
爱范儿
爱范儿
博客园 - 聂微东
N
News and Events Feed by Topic
The Cloudflare Blog
博客园_首页
阮一峰的网络日志
阮一峰的网络日志
D
Darknet – Hacking Tools, Hacker News & Cyber Security
AI
AI
Schneier on Security
Schneier on Security
Recent Announcements
Recent Announcements
博客园 - 三生石上(FineUI控件)
大猫的无限游戏
大猫的无限游戏
The Last Watchdog
The Last Watchdog
L
LINUX DO - 热门话题
Vercel News
Vercel News
C
Check Point Blog
Cisco Talos Blog
Cisco Talos Blog
Apple Machine Learning Research
Apple Machine Learning Research
量子位
C
Cyber Attacks, Cyber Crime and Cyber Security
TaoSecurity Blog
TaoSecurity Blog
B
Blog RSS Feed
MongoDB | Blog
MongoDB | Blog
P
Proofpoint News Feed
P
Palo Alto Networks Blog
云风的 BLOG
云风的 BLOG

博客园 - 麦舒

网站访问速度的优化 ERP 订单打印的优化 页面 SEO 信息的优化 一个网站首页重构小记 广告播放软件的开发 项目分享九:客户端的异常处理 项目分享八:基于按钮点击事件的弹窗 项目分享六:图片的延迟加载 项目分享五:H5图片压缩与上传 项目分享四:购物车页面的更新 项目分享三:页面之间的传值 项目分享二:APP 小红点中数字的处理 项目分享一:在项目中使用 IScroll 所碰到的那些坑 微信开发——通过授权获取用户的基本信息 千呼万唤岂出来,写款软件不容易——Visual Entity 2.0 发布 实现虽易,写好不易——小玩意也能体现编码功力,微信消息处理框架发布 代码重构之 —— 一堆if、esle 逻辑的处理 ALinq Dynamic 使用指南——慨述(上) ALinq Dynamic 使用指南——代码的获取与编译
项目分享七:客户端防止表单重复提交
麦舒 · 2015-12-22 · via 博客园 - 麦舒

项目分享七:客户端防止表单重复提交

2015-12-22 10:24  麦舒  阅读(5045)  评论()    收藏  举报

防止表单数据重复提交,是 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 所碰到的那些坑