使用 rails/jquery-ujs 来编写非侵入式的 js 模板代码

sf 上前端大牛太多,本人前端菜鸟,总结归纳,觉得内容不错就总结分享之。

惯例安利一波我的后端 php 直播课。
很多工程师在工作1~3年的时候最容易遇到瓶颈,不知道自己应该学习什么,面试总是吃闭门羹。那么 PHP 后面应该怎么学呢?《PHP 进阶之路

原文地址 https://mengkang.net/1145.html
项目地址 https://github.com/rails/jque...

在线 demo

  1. 演示 jquery-ujs https://mengkang.net/demo/rails/
  2. 原理解析 - 代理表单提交 https://mengkang.net/demo/rai...
  3. 原理解析 - 实现 ajax 提交 https://mengkang.net/demo/rai...
  4. 扩展开发 - 行内回调 https://mengkang.net/demo/rai...

项目中经常看到类似于下面这样的 a 链接的请求,而点击的时候,实际发送的是 ajax 请求,而没有发生跳转。

<a href="/comments/destroy/908/" class="js-comment-destroy"
data-remote="true" 
data-method="post" 
data-confirm="确定要删除吗?">删除</a>
演示 https://mengkang.net/demo/rails/

不是说它有多么复杂,而是我真真切切感受到了它的高效,非常舒服的抽象了模板嵌套中的一些常用的交互。避免了重复编写类似于下面这样的代码:

$("#del_btn").bind('click', function () {
  if (confirm("确定要删除?")) {
    var method = '/comments/destroy/908/';
    Ajax.sendData({
      type: 'POST',
      url: method,
      async: false,
      dataType: "json",
      data: {},
      success: function (data, status, xhr) {
        if (data.state == 'success') {
          location.href = '/articles/';
        } else {
          alert(data.msg);
        }
      },
      error: function (xhr, type, error) {
        console.log(error.toString());
      }
    });
  }
});

我们有可以在rails/jquery-ujs基础上做更多的交互扩展,文章末尾我们将把callback写在行内的方式来扩展该工具。

原理分析

A 链接实现表单的提交

虽然点击还是跳转,但是通过控制台可以看到,该请求是 post 的方式,我们自定义的 rails 插件给代理了 click 为一个 form 的 post 请求。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://static.mengkang.net/view/js/jquery-1.11.3.min.js"></script>
    <script>
        (function ($) {
            'use strict';
            var rails;
            $.rails = rails = {
                linkClickSelector: 'a[data-remote]',
                href: function (element) {
                    return element[0].href;
                },
                handleMethod: function (link) {
                    var href = rails.href(link),
                        method = link.data('method'),
                        target = link.attr('target'),
                        form = $('<form method="post" action="' + href + '"></form>'),
                        metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';

                    if (target) {
                        form.attr('target', target);
                    }

                    form.hide().append(metadataInput).appendTo('body');
                    form.submit();
                },
            };

            $(document).on('click.rails', rails.linkClickSelector, function (e) {
                var link = $(this);
                rails.handleMethod(link);
                return false;
            });
        })(jQuery);
    </script>
</head>
<body>
    <a href="/comments/destroy/908/" data-remote="false" data-method="post">删除</a>
</body>
</html>
代码演示 https://mengkang.net/demo/rai...

例子中

$(document).on('click.rails', 'a[data-remote]', function (e) {
  //...
});

click.railsrails是命名空间。方便解除该事件。

http://api.jquery.com/on/
One or more space-separated event types and optional namespaces, such as "click" or "keydown.myPlugin".

Any event names can be used for the events argument. jQuery will pass through the browser's standard JavaScript event types, calling the handler function when the browser generates events due to user actions such as click. In addition, the .trigger() method can trigger both standard browser event names and custom event names to call attached handlers. Event names should only contain alphanumerics, underscore, and colon characters.

An event name can be qualified by event namespaces that simplify removing or triggering the event. For example, click.myPlugin.simple defines both the myPlugin and simple namespaces for this particular click event. A click event handler attached via that string could be removed with .off("click.myPlugin") or .off("click.simple") without disturbing other click handlers attached to the elements. Namespaces are similar to CSS classes in that they are not hierarchical; only one name needs to match. Namespaces beginning with an underscore are reserved for jQuery's use.

实现 A 链接的 ajax 请求

在上面代码的基础上我们把data-remote参数利用上,如果data-remote设置为true则表示使用 ajax 请求。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://static.mengkang.net/view/js/jquery-1.11.3.min.js"></script>
    <script>
        (function ($) {
            'use strict';
            var rails;
            $.rails = rails = {
                linkClickSelector: ' a[data-remote]',
                href: function (element) {
                    return element[0].href;
                },
                isRemote: function(element) {
                    return element.data('remote') !== undefined && element.data('remote') !== false;
                },
                handleMethod: function (link) {
                    var href = rails.href(link),
                        method = link.data('method'),
                        target = link.attr('target'),
                        form = $('<form method="post" action="' + href + '"></form>'),
                        metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';

                    if (target) {
                        form.attr('target', target);
                    }

                    form.hide().append(metadataInput).appendTo('body');
                    form.submit();
                },
                handleRemote: function(element) {
                    var options = {};

                    options.url = rails.href(element);
                    options.method = element.data('method');
                    options.data = element.data('params') || null;

                    return $.ajax(options);
                }
            };

            $(document).on('click.rails', rails.linkClickSelector, function (e) {
                var link = $(this);

                if (rails.isRemote(link)) {
                    rails.handleRemote(link);
                }else{
                    rails.handleMethod(link);
                }
                return false;
            });
        })(jQuery);
    </script>
</head>
<body>
  <a href="/comments/destroy/908/" data-remote="true" data-method="post">删除</a>
</body>
</html>
代码演示 https://mengkang.net/demo/rai...

绑定到 ajax:success 做回调

$document.on('ajax:success', '.js-comment-destroy', function() {
    return $(this).closest('.media').remove();
});

这个事件是如何触发的呢?实际是在发送 ajax 里面使用trigger来触发。

options = {
    success: function(data, status, xhr) {
        element.trigger('ajax:success', [data, status, xhr]);
    },
    complete: function(xhr, status) {
        element.trigger('ajax:complete', [xhr, status]);
    },
    error: function(xhr, status, error) {
        element.trigger('ajax:error', [xhr, status, error]);
    }
}

options.url = rails.href(element);
options.method = element.data('method');
options.data = element.data('params') || null;

$ajax.(options);

扩展开发

以自定义 callback 为例,我们直接把 callback 定义在 data-done属性中。为了完全非侵入式的 JavaScript 服务端开发,才有了这个需求(反正作为一个服务端渲染的模板,不想又去专门弄前端项目,更愿意直接在模板里修改)

准备知识之 trigger + on

我们知道在 jquery 的on方法中回调函数的参数是支持无限多参数的,第一个参数event事件本身,后面都是自定义的任意内容

handler Type: Function( Event eventObject [, Anything extraParameter ] [, ... ] )
http://api.jquery.com/on/#on-...

配合trigger来使用,两个参数演示

$( "div" ).on( "click", function( event, person ) {
  alert( "Hello, " + person.name );
});
$( "div" ).trigger( "click", { name: "Jim" } );

trigger传递数组

$( "div" ).on( "click", function( event, salutation, name ) {
  alert( salutation + ", " + name );
});
$( "div" ).trigger( "click", [ "Goodbye", "Jim" ] );

到这里我们就可以看到 jquery-ujs 在发送 ajax 的 options 里面设置了

options = {
    success: function(data, status, xhr) {
        element.trigger('ajax:success', [data, status, xhr]);
    },
    //...
}
//...
$ajax.(options);

那我们绑定到data-done上就是

$(document).on('ajax:success', '[data-done]', function(event, data, status, xhr) {
  //...
});

准备知识之 new Function() + call

https://developer.mozilla.org...
https://developer.mozilla.org...
var obj = {
  person: 'Douglas Crockford'
};

function funA() {
  console.log(this.person);
}

funA.call(obj);

function funB(arg1,arg2) {
  console.log(this.person,arg1,arg2);
}

funB.call(obj,1,2);

// new Function (arg1, arg2, ... argN, functionBody)
new Function('arg1','arg2','console.log(this.person,arg1,arg2)').call(obj,3,4);

实现 data-done 回调

代码演示:https://mengkang.net/demo/rai...
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://static.mengkang.net/view/js/jquery-1.11.3.min.js"></script>
    <script>
        (function ($) {
            'use strict';
            var rails;
            $.rails = rails = {
                linkClickSelector: ' a[data-remote]',
                href: function (element) {
                    return element[0].href;
                },
                handleRemote: function (element) {
                    var options = {
                        success: function (data, status, xhr) {
                            element.trigger('ajax:success', [data, status, xhr]);
                        }
                    };

                    options.url = rails.href(element);
                    options.method = element.data('method');
                    options.data = element.data('params') || null;
                    options.dataType = 'json';

                    return $.ajax(options);
                }
            };

            $(document).on('click.rails', rails.linkClickSelector, function (e) {
                rails.handleRemote($(this));
                return false;
            });

            $(document).on('ajax:success', '[data-done]', function (event, data, status, xhr) {
                console.log(data, "\n", status, "\n", xhr);

                var done = $(this).data('done');
                if (done) {
                    (new Function('data', done)).call(this, data);
                }
            });
        })(jQuery);
    </script>
</head>
<body>
<div class="media">
    <a href="del.php"
       data-remote="true"
       data-method="post"
       data-done="$(this).closest('.media').remove()"
    >删除 - 删掉整个 div</a>

    <a href="del.php"
       data-remote="true"
       data-method="post"
       data-done="alert(data.error_code)"
    >输出接口里返回的error_code</a>
</div>

</body>
</html>

周梦康
金三银四 还不上车补课?学了就是赚了 [链接]

退隐江湖

8.9k 声望
6.7k 粉丝
0 条评论
推荐阅读
什么?JVM 老年代内存不断上涨竟是因为获取 ServletContext 姿势不对
前几日一直在筹备一个比较大的项目,发现一个问题,还好流量不是非常非常大,不然又得提桶跑路了。在线上运行的时候发现当并发过高的情况,会出现老年代内存上涨的情况。

周梦康2阅读 2.7k

从零搭建 Node.js 企业级 Web 服务器(零):静态服务
过去 5 年,我前后在菜鸟网络和蚂蚁金服做开发工作,一方面支撑业务团队开发各类业务系统,另一方面在自己的技术团队做基础技术建设。期间借着 Node.js 的锋芒做了不少 Web 系统,有的至今生气蓬勃、有的早已夭折...

乌柏木150阅读 12.3k评论 10

正则表达式实例
收集在业务中经常使用的正则表达式实例,方便以后进行查找,减少工作量。常用正则表达式实例1. 校验基本日期格式 {代码...} {代码...} 2. 校验密码强度密码的强度必须是包含大小写字母和数字的组合,不能使用特殊...

寒青56阅读 7.9k评论 11

JavaScript有用的代码片段和trick
平时工作过程中可以用到的实用代码集棉。判断对象否为空 {代码...} 浮点数取整 {代码...} 注意:前三种方法只适用于32个位整数,对于负数的处理上和Math.floor是不同的。 {代码...} 生成6位数字验证码 {代码...} ...

jenemy46阅读 6k评论 12

从零搭建 Node.js 企业级 Web 服务器(十五):总结与展望
总结截止到本章 “从零搭建 Node.js 企业级 Web 服务器” 主题共计 16 章内容就更新完毕了,回顾第零章曾写道:搭建一个 Node.js 企业级 Web 服务器并非难事,只是必须做好几个关键事项这几件必须做好的关键事项就...

乌柏木66阅读 6.2k评论 16

再也不学AJAX了!(二)使用AJAX ① XMLHttpRequest
「再也不学 AJAX 了」是一个以 AJAX 为主题的系列文章,希望读者通过阅读本系列文章,能够对 AJAX 技术有更加深入的认识和理解,从此能够再也不用专门学习 AJAX。本篇文章为该系列的第二篇,最近更新于 2023 年 1...

libinfs39阅读 6.3k评论 12

封面图
从零搭建 Node.js 企业级 Web 服务器(一):接口与分层
分层规范从本章起,正式进入企业级 Web 服务器核心内容。通常,一块完整的业务逻辑是由视图层、控制层、服务层、模型层共同定义与实现的,如下图:从上至下,抽象层次逐渐加深。从下至上,业务细节逐渐清晰。视图...

乌柏木44阅读 7.4k评论 6

退隐江湖

8.9k 声望
6.7k 粉丝
宣传栏