锁定老帖子 主题:在Rails3时代js该怎么写?
该帖已经被评为良好帖
|
|
---|---|
作者 | 正文 |
发表时间:2009-09-24
最后修改:2009-09-25
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)。 [广告:更多Rails3的新特性见:http://www.railsfire.com/article/community-feedback-future-rails] 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代码分离出来,如事件描述: 分离前: <body> <input type="text" name="date" onchange="validateDate(this);" /> </body> 分离后: <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[i]; 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? 用一个比较常见的用户信息修改操作为例,修改用户的信息,一般会用如下代码: <% 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代码: <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[username]" 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代码: <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请求场景,是怎样实现的,如 <%= link_to 'Users', users_path %> 会生成HTML: <a href="/users">Users</a> 用 remote_link_to 替换的话,将会得到: <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 中加入: # 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) end 2. 在js框架方面,我选择用jquery 在layout中加入 <%= 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 中加入: $(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[data-remote=true]').live('click', function() { return app.ajax({ url : this.href }); }); // remote forms handler $('form[data-remote=true]').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(); }); 关键代码其实只是 // remote forms handler $('form[data-remote=true]').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? 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-09-24
我也觉得应该跟jquery更紧密的结合!
|
|
返回顶楼 | |
发表时间:2009-09-24
我早停止使用所有的 rails js view help 方法,
不过我不是加一个data-remote="true"属性,我是加一个class='ajaxlink'属性。。。 然后使用$('.ajaxlink').ajaxRequest();绑定 ajaxRequest方法实现为: $.fn.ajaxRequest = function() { $(this).unbind('click').click(function(){ if ($(this).attr('confirm_messae') ? confirm($(this).attr('confirm_messae')) : ($(this).attr('ajaxmethod') == 'GET' ? true : confirm(_('are your sure')))) { $.cacheAjax({ url: $(this).attr('href'), type: $(this).attr('ajaxmethod') || "PUT", dataType: 'script' }); }; return false; }); }; 关于里面的cacheAjax方法是使用了我的一个插件,目的是对 ajax 的请求进行缓存,很好玩、有用哦 插件地址见:http://github.com/jinzhu/cacheAjax 关于里面_('are your sure')的_方法,也是使用了我的一个javascript localize插件:地址见,http://github.com/jinzhu/javascript_localize |
|
返回顶楼 | |
发表时间:2009-09-24
wosmvp 写道 我早停止使用所有的 rails js view help 方法,
不过我不是加一个data-remote="true"属性,我是加一个class='ajaxlink'属性。。。 然后使用$('.ajaxlink').ajaxRequest();绑定 ajaxRequest方法实现为: $.fn.ajaxRequest = function() { $(this).unbind('click').click(function(){ if ($(this).attr('confirm_messae') ? confirm($(this).attr('confirm_messae')) : ($(this).attr('ajaxmethod') == 'GET' ? true : confirm(_('are your sure')))) { $.cacheAjax({ url: $(this).attr('href'), type: $(this).attr('ajaxmethod') || "PUT", dataType: 'script' }); }; return false; }); }; 关于里面的cacheAjax方法是使用了我的一个插件,目的是对 ajax 的请求进行缓存,很好玩、有用哦 插件地址见:http://github.com/jinzhu/cacheAjax 关于里面_('are your sure')的_方法,也是使用了我的一个javascript localize插件:地址见,http://github.com/jinzhu/javascript_localize 我看到你的cacheAjax还有js testcases,很好很和谐,对js unit test感兴趣,有经验可分享吗 |
|
返回顶楼 | |
发表时间:2009-09-25
使用 sinatra + QUnit搭建的超轻量级组合,前者作为一个server为ajax提供测试数据,QUnit是JQuery的单元测试框架,这套组合作用也就是用在测试js插件等纯js文件而已。
如果测试大型js应用的话,个人还是感觉还是采用眼见为实的方法比较好,可以选择selenium。? (大家有更好的方案吗?) 别外看了部分代码,感觉你的代码有一点点点问题 # <%= 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 : ""}';" %> 一个view中多次调用: respond_to?(:local_request?) && local_request? AUTHENTICITY_TOKEN? 我的记忆中从 Rails 2.3 (?) 开始不验证ajax请求了? |
|
返回顶楼 | |
发表时间:2009-09-25
wosmvp 写道 使用 sinatra + QUnit搭建的超轻量级组合,前者作为一个server为ajax提供测试数据,QUnit是JQuery的单元测试框架,这套组合作用也就是用在测试js插件等纯js文件而已。
如果测试大型js应用的话,个人还是感觉还是采用眼见为实的方法比较好,可以选择selenium。? (大家有更好的方案吗?) 别外看了部分代码,感觉你的代码有一点点点问题 # <%= 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 : ""}';" %> 一个view中多次调用: respond_to?(:local_request?) && local_request? AUTHENTICITY_TOKEN? 我的记忆中从 Rails 2.3 (?) 开始不验证ajax请求了? 我在自己的应用中试直接用local_request? 判读的,因为我在application controller中申明了: helper_method :local_request? 并且要求本地目录中已经下载了对应的jquery代码,否则就直接用google的api。 这里做预判断是为了预防有些同学可能会直接copy代码测试而出错,另外方便生产环境中使用google的js cdn service。 protect_against_forgery? 在Rails 2.3x 默认是开启的(我没记错的话) |
|
返回顶楼 | |
发表时间:2009-09-25
关于第一点,我们的代码:
<% if RAILS_ENV == 'production' %> <script src="http://www.google.com/jsapi"></script> <script type='text/javascript'> google.load("jquery", "1.3.2"); google.load("jqueryui", "1.7.1"); </script> <% else %> <%= javascript_include_merged :jquery %> <% end %> (javascript_include_merged是asset_packager的方法) 关于第二点,ajax请求已不在验证protect_against_forgery?,可自行查看Rails源代码确认,记得当时的github吵的很火的,我加个?号只是忘记Rails2.3分支有没有做这个而已,Rails3肯定是的,当然如果没有revert的话。。。 |
|
返回顶楼 | |
发表时间:2009-09-25
cacheAjax 是个很实用的东东,支持一下
|
|
返回顶楼 | |
发表时间:2009-09-25
我用一开始就没碰过rails那些动态插入js的helper方法⋯⋯其实只要具有前端开发经验,不管哪个版本的rails都能做到“无侵入”,遵循web标准。
|
|
返回顶楼 | |
发表时间:2009-09-25
wosmvp 写道 关于第一点,我们的代码:
<% if RAILS_ENV == 'production' %> <script src="http://www.google.com/jsapi"></script> <script type='text/javascript'> google.load("jquery", "1.3.2"); google.load("jqueryui", "1.7.1"); </script> <% else %> <%= javascript_include_merged :jquery %> <% end %> (javascript_include_merged是asset_packager的方法) 关于第二点,ajax请求已不在验证protect_against_forgery?,可自行查看Rails源代码确认,记得当时的github吵的很火的,我加个?号只是忘记Rails2.3分支有没有做这个而已,Rails3肯定是的,当然如果没有revert的话。。。 不直接判断RAILS_ENV是为了方便本地调试,比如有时你的NB在没有网络的环境,而且也方便在jquery的源码做些logger(有时要跟下jquery源码分析运行流程) 在application_controller.rb 中去掉protect_from_forgery 注释即是开启(今天确认了下,Rails 2.3x 默认是已经去掉注释的了): protect_from_forgery # See ActionController::RequestForgeryProtection for details 忘了提及一点,要支持ajax方式获取无layout的action rendered page时,还应该在ac里加入: # render empty layout for ajax request layout proc { |controller| controller.request.xhr? ? nil : 'application' } |
|
返回顶楼 | |