Rails ujs
UJS是Rails 3引入的JavaScript框架与Rails的抽象层。我们知道Rails一些Helper是依赖于JavaScript框架的,比如Ajax Form,Ajax Link等,并且在Rails 3之前默认集成的JavaScript框架是Prototype,再这之后才换成了社区呼声很高的jQuery。
如前面所说UJS是个抽象层,它需要在每个框架上实现对应的接口,比如官方实现了jquery-ujs和prototype-ujs。本篇主要以jquery-ujs为例来讲解UJS。jquery-ujs代码很短,只有500行不到,想先浏览一下整个代码可访问Github的jquery-ujs repo。
data-confirm #
先从最简单confirm例子入手。比如,在某些链接上让用户在点击链接后再次确认一次,我们一般会这么写
link_to "Visit Other Site", "http://www.rubyonrails.org/", data: { confirm: "Are you sure?" }
这里和普通的链接不同的地方只是在于多加了一个data-confirm
属性,然后UJS帮你实现了弹出确认框。这其中的实现方法很简单,写过jQuery的同学都会,就是监听所有链接的click
事件,当这个被点击链接上有data-confirm
属性时,取出data-confirm
中的文本,弹出确认框,并根据用户的操作选择是否中断这个点击事件的处理。
data-method #
然后我们来看看,另外一种比较常见的链接用法,让链接点击时使用非GET方法请求对应的URL,代码如下
link_to("Sign Out", sign_out_path, method: :delete)
这里传入的method
参数,生成HTML时会被转换为data-method="delete"
。与前面一个例子一样,UJS在这个链接的click事件上监听,当这个链接有data-method
属性时,创建一个隐藏的form
标签,并附带上名为_method
参数,值为data-method
属性值的input
标签,最后将这个构造的表单提交。
通过这样的小技巧,Rails开发者就能通过<a>
标签以任何想要的HTTP Method请求对应的链接。
Ajax Form #
在Rails 3之后的form_tag
和form_for
上传入remote: true
就能实现表单的Ajax提交,同样这个事情,UJS也是通过监听所有的Form标签的submit
事件,然后检测标签上的data-remote
属性来实现的。
对于开发者,在传入了remote: true
之后要怎样去插入对应的Ajax处理器呢?UJS在对应Ajax提交阶段上触发了Rails自定义的ajax:xxx
事件。
beforeSend: function(xhr, settings) {
if (settings.dataType === undefined) {
xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
}
return rails.fire(element, 'ajax:beforeSend', [xhr, settings]);
},
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]);
}
所以基于UJS我们可以这样直接在form
元素上绑定上对应的Ajax提交处理代码
$("#myform").on("ajax:success", function () {
alert("Post successfully:)")
}).on("ajax:error", function () {
alert("Post fail:(")
})
UJS在Ajax表单提交了之后,还会将该表单中的button
和input[type='submit']
都加上disable
属性,防止用户多次点击引发多次提交。
此外,UJS实现的Ajax Form上还有两个特殊的事件
ajax:aborted:required
当表单提交的时候,有未填的input
标签时会触发这个事件,你可以选择去处理ajax:aborted:file
我们知道通过正常的方式是无法通过AJAX来提交文件的,当表单里包含了文件字段的时候,这个事件会被触发,在这里可以去实现自己的文件提交逻辑。比如remotipart通过这个事件实现了Ajax Form的文件提交。
选择器 #
UJS的所有功能都通过预设的选择器,绑定事件处理逻辑到对应元素上,以下是选择器的定义
$.rails = {
// Link elements bound by jquery-ujs
linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote], a[data-disable-with]',
// Button elements boud jquery-ujs
buttonClickSelector: 'button[data-remote]',
// Select elements bound by jquery-ujs
inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
// Form elements bound by jquery-ujs
formSubmitSelector: 'form',
// Form input elements bound by jquery-ujs
formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type])',
// Form input elements disabled during form submission
disableSelector: 'input[data-disable-with], button[data-disable-with], textarea[data-disable-with]',
// Form input elements re-enabled after form submission
enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled',
// Form required input elements
requiredInputSelector: 'input[name][required]:not([disabled]),textarea[name][required]:not([disabled])',
// Form file input elements
fileInputSelector: 'input[type=file]',
// Link onClick disable selector with possible reenable after remote submission
linkDisableSelector: 'a[data-disable-with]'
}
这提供可配置对应选择器的机会,比方说使用某个jQuery插件,它通过data-remote
去标记别的事情。那么在不修改这个插件的情况下想让UJS继续工作,我们可以重新配置UJS的选择器
$.rails.formSubmitSelector = 'form([data-ajax-form])';
CSRF Token #
UJS还会在每次表单提交上自动附带上CSRF Token。Rails 3之后强制所有的非幂等HTTP请求需要带上CSRF token作安全校验,用来防止XSS攻击。这就要求开发者在每次写Ajax请求的时候,都需要手动把这部分的token带上,UJS也通过jQuery的ajaxPrefilter接口,让每次的Ajax请求都自动附带上CSRF token。
另外,每次UJS初始化时,会为页面上所有表单都加上带有CSRF Token的隐藏input
标签,让表单在提交时都能自动带上CSRF Token。
从UJS学到了什么 #
- jquery-ujs的所有事件绑定都是绑定在document,等到事件触发后再分发到对应的事件处理逻辑里,不需要在初始化时查找对应的元素并绑定事件
- 充分利用HTML5的
data
属性来解耦事件处理逻辑,将各种参数序列化到data
属性 - 利用jQuery的自定义事件更好地定制自己的事件处理流程
参考 #
- http://www.alfajango.com/blog/rails-3-remote-links-and-forms/
- http://www.alfajango.com/blog/rails-3-remote-links-and-forms-data-type-with-jquery/
- http://railscasts.com/episodes/205-unobtrusive-javascript
- http://net.tutsplus.com/tutorials/javascript-ajax/using-unobtrusive-javascript-and-ajax-with-rails-3/
- Previous: ActionView基本架构
- Next: ActionView Safe Buffer