在Rails3时代js该怎么写?
本帖最后由 中关村村草 于 2010-12-29 11:08 编辑[前注:本篇不是教程,只是一些rails的新技巧、特性的探讨。]
Why?
现在,我们在进行软件、WEB项目开发时都用喜欢用框架,即省时省力,又有规有矩。所谓规矩,最常见的约束就是MVC三层分离,其中V是VIEW(视图),而进行WEB开发时,最常见的VIEW就是HTML页面。HTML到了XHTML(http://en.wikipedia.org/wiki/XHTML)时代,也开始强调了要样式与内容结构分离,“HCJ”三层分离,就是HTML(页面内容)、CSS(页面装饰)、JAVASCRIPT(页面行为)尘归尘土归土,各自归纳到独立的文件中,通过HTML的标签或属性来进行关联,最显而易见的好处是一来方便代码结构化管理、解析,二来方便浏览器缓存。
我们很幸运的,搭上了Rails这俩快车,一路走在流行技术的最前沿,在Rails3时代,Rails秉承“兼容并包”的良好品德和思想,在提供方便默认配置之余,还放开了怀抱,使得更多更方便的框架、类库可以集成进来替换默认配置。在Rails3中,Prototype这个js框架将不会默认绑定(http://rails.uservoice.com/pages/10012-rails/suggestions/99489-unbind-the-framework-from-test-unit-and-prototype)。
What?
在Rails3时代还没到临之前,如果不想用Prototype,可以有jRails(http://ennerchi.com/projects/jrails)代替,然后jRails是完全兼容PrototypeHelper(http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper.html),同样会在HTML中嵌入JS代码片段,从而伤害了HTML的纯洁。为了要维护HTML的纯洁以及JS的主权独立,于是计算机之神说,要Unobtrusive,于是有了Unobtrusive_JavaScript(http://en.wikipedia.org/wiki/Unobtrusive_JavaScript)。
Unobtrusive_JavaScript简单来说,就是把有指定元素行为的JS代码分离出来,如事件描述:
分离前:
Html代码<body>
<input type="text" name="date" onchange="validateDate(this);" />
</body>
分离后:
Html代码<head>
<script>
window.onload = function(){ //Wait for the page to load.
var inputs = document.getElementsByTagName('input');
for(var i=0,l=inputs.length;i<l;i++){
input = inputs;
if(input.name && input.name=='date'){
input.onchange = function(){
validateDate(this);
}
}
}
};
function validateDate(){
//Do something when the content of the 'input' element with the name 'date' is changed.
}
</script>
</head>
<body>
<input type="text" name="date" />
</body>
把所有js绑定操作都统一放到页面加载完再进行。
How?
用一个比较常见的用户信息修改操作为例,修改用户的信息,一般会用如下代码:
Ruby代码<% form_for @user do |f| %>
<%= f.error_messages %>
<p>
<%= f.label :username %><br />
<%= f.text_field :username %>
</p>
<p><%= f.submit "Submit" %></p>
<% end %>会生成如下HTML代码:
Html代码<form action="/users/2" class="edit_user" id="edit_user_2" method="post"><div style="margin:0;padding:0"><input name="_method" type="hidden" value="put" /><input name="authenticity_token" type="hidden" value="ooDWeKPVumeI0r+O4E20g9TjfnxFKHp3ZsnCPCCrSFg=" /></div>
<p>
<label for="user_username">Username</label><br />
<input id="user_username" name="user" size="30" type="text" value="rainchen" />
</p>
<p><input id="user_submit" name="commit" type="submit" value="Submit" /></p>
</form>如果要改为AJAX提交操作时,可以用remote_form_for helper(http://api.rubyonrails.org/classes/ActionView/Helpers/PrototypeHelper.html#M001649),但这个其实是PrototypeHelper提供的一个辅助方法,把 form_for替换为remote_ form_for 后会在页面中生成如下HTML代码:
Html代码<form action="/users/2" class="edit_user" id="edit_user_2" method="post" onsubmit="new Ajax.Request('/users/2', {asynchronous:true, evalScripts:true, parameters:Form.serialize(this)}); return false;">
经过之前的一番论述,现在的结论就是<form>标签不纯洁了,被插了一段Prototype的AJAX代码。
在Rails3时代,这种代码是不和谐的,需要批判。
在解决这个问题前,先看一下在Rails3时代,类似AJAX请求场景,是怎样实现的,如
Ruby代码<%= link_to 'Users', users_path %>
会生成HTML:
Html代码<a href="/users">Users</a>
用 remote_link_to 替换的话,将会得到:
Html代码<a href="/users" data-remote="true">Users</a>
被加进了一个data-remote属性,data-xxx 形式的属性,在HTML5中是合理又合法的:http://ejohn.org/blog/html-5-data-attributes/
remote_link_to 其实是一个Rails3新的AjaxHelper的方法,实现代码见:
http://github.com/rails/rails/blob/master/actionpack/lib/action_view/helpers/ajax_helper.rb
浏览代码后,不难发现到今天为止,AjaxHelper 中还没发现remote_form_for 的身影,也就是remote_form_for 还只是个传说。
今天我们就是要尝试实现这个传说,让我们就来见证奇迹。
在Rails3时代,没有意外的话,<form>标签也会被插入 data-remote=“true" 这个标记,因此思路很简单,覆盖掉remote_form_for 方法,加入这个标记,然后在页面加载后,用js查找带有这个标记的form,绑上AJAX操作即可。
1. 在application_helper.rb 中加入:
Ruby代码# unobtrusive javascript helpers
def remote_form_for(*args, &proc)
options = args.extract_options!
options[:html] ||= {}
options[:html]["data-remote"] = "true"
options[:html]["data-update"] = options[:update] unless options[:update].blank? # enable update the resposne data to *update* dom
args << options
form_for(*args, &proc)
end2. 在js框架方面,我选择用jquery
在layout中加入
Ruby代码<%= javascript_include_tag respond_to?(:local_request?) && local_request? ? 'jquery.min' : 'http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js' %>
<%= javascript_include_tag respond_to?(:local_request?) && local_request? ? 'jquery-ui.min' : 'http://ajax.googleapis.com/ajax/libs/jqueryui/1.7.2/jquery-ui.min.js' %>
<%= javascript_tag "AUTHENTICITY_TOKEN = '#{protect_against_forgery? ? form_authenticity_token : ""}';" %>
<%= javascript_include_tag 'application' %>
在application.js 中加入:
Js代码$(function(){
// set authenticity koen for Rails
if(typeof AUTHENTICITY_TOKEN != 'undefined' && AUTHENTICITY_TOKEN != ''){
$.ajaxSetup( {data: {authenticity_token: AUTHENTICITY_TOKEN}} );
}
// setup app namespace
var app = {};
// show the ajax result
app.ajaxShowResult = function(options){
options = $.extend({title: '', body: '', width: 200, height: 200}, options);
if(!$("#app_ajax_result").get(0)){
$(document.body).append('<div id="app_ajax_result"></div>');
$("#app_ajax_result").dialog({
title: '',
bgiframe: true,
width: options.width,
height: options.height,
modal: true
});
}
$("#app_ajax_result").html(options.body);
$("#app_ajax_result").dialog('option', 'title', options.title);
return $("#app_ajax_result").dialog('open');
};
// default error handler for ajax request
app.ajaxError = function(XMLHttpRequest, textStatus, errorThrown){
return app.ajaxShowResult({title: XMLHttpRequest.statusText, body: XMLHttpRequest.responseText, width: 600, height: 400});
};
// default success handler for ajax request
app.ajaxSuccess = function(data, textStatus){
if(this.update){
$("#"+this.update).html(data);
}else if(this.dataType == 'html'){
return app.ajaxShowResult({title:textStatus, body: data});
}
};
app.ajax = function(options) {
$.ajax($.extend({ url : options.url, type : 'get', dataType: 'html', error: app.ajaxError, success: app.ajaxSuccess }, options));
return false;
};
// find all all data-remote tags
app.setupAjaxHelpers = function(){
// remote links handler
$('a').live('click', function() {
return app.ajax({ url : this.href });
});
// remote forms handler
$('form').live('submit', function() {
return app.ajax({ url : this.action, type : this.method, data : $(this).serialize(), update: $(this).attr('data-update') });
});
};
// init
app.init = function(){
app.setupAjaxHelpers();
};
app.init();
});
关键代码其实只是
Js代码// remote forms handler
$('form').live('submit', function() {
return app.ajax({ url : this.action, type : this.method, data : $(this).serialize(), update: $(this).attr('data-update') });
});默认用jquery-ui来做结果显示。
3.要支持ajax方式获取无layout的action rendered page时,还应该在application_controller.rb里加入:#render empty layout for ajax request
layout proc { |controller| controller.request.xhr? ? nil : 'application' }
后注:
1. 其中jquery的live事件还不是实现得很完整,对IE支持不好:
引用
jQuery is going to be adding support for .live("submit") in the next
release, and it's possible to emulate in rails.jquery.js in the interim.
-- Yehuda
On Tue, May 26, 2009 at 1:07 PM, meefs...@googlemail.com <
- Show quoted text -
--
Yehuda Katz
Developer | Engine Yard
(ph) 718.877.1325
2. 在HTML标签中是约定用css做标记还是用属性做标记,筛选时会性能问题的差异,对我来说不是关注重点。
3. 在现在的Rails中使用Unobtrusive_JavaScript 版本的 remote_link_to 可参考:http://blog.solnic.eu/2009/09/08/unobtrusive-javascript-helpers-in-rails-3
参考:
http://blog.solnic.eu/2009/09/08/unobtrusive-javascript-helpers-in-rails-3
http://groups.google.com/group/rubyonrails-core/browse_thread/thread/3fa1cc2b1979a858
http://nutrun.com/weblog/unobtrusive-ajax-with-jquery-and-rails/
http://docs.jquery.com/Events/live
End?
页:
[1]