Ajax就是前端页面通过js向后端服务器请求资源,服务器返回资源给前端页面,前端页面得到js资源并自动执行呈现在浏览器页面。这种方式下,页面获取服务器资源并呈现新增加的内容,然而页面你看不到刷新的痕迹,而且也不会像刷新页面那样又返回页面顶部。
咱们还是来看这个评论框,现在如果提交评论,那页面会整个刷新,于是会跳到页顶,用户体验不好。
点击"提交评论"之后整个页面刷新(页面会闪一下)并来到页面顶部:
步骤:
(1)前端部分,将需要触发ajax的提交按钮对应的form_for标签添加 remote: true;如上是博客的show页面,而触发ajax的提交按钮对应的form_for标签是该show页面视图里面渲染的一个局部视图模板_comment_box.html.erb
原来代码:
修改后的代码:
然后我们刷新页面查看源码,发现跟原来的区别就是在原来的基础上添加了一个data-remote="true"
这样转换成的 html 其实变化不大,就是多了 data-remote="true" 这几个字,但是注意在 application.js 文件中 有 require jquery_ujs,rails默认就加载了 jquery_ujs 这个 js 文件,里面的代码会把remote: true的表单提交自动变成一次异步的 ajax 提交。具体细节不用管,真正要关心的就是后台 log 中的变化。如果出现Processing by IssuesController#show as JS (非 ajax 请求是请求 html) 证明前端要做的修改就弄好了。
然后我们重新在页面提交评论:
在控制台日志中可以看到以js方式提交了表单(ajax就是用javascript向服务器提交数据),也就是实现了ajax:
(2)前端暂时修改就这么一点点就可以了,接下来就是去后端修改ruby代码作为响应了
因为提交给后端的create动作,用respond_to方法设置JS格式的响应,并且在与该控制器create动作对应views目录下位置创建js代码--这样rails收到请求才知道去哪里查找js文件,修改如下代码:
然后创建 app/views/comments/create.js (先新建comments目录)注意这样位置不能敲错,不然 rails 就找不着这个文件了(因为提交给后端的comment控制器的create动作,所以对应的ajax的javascript代码位置在views/comments目录下,文件名与动作名一致)。
先在里面写个 alert("hello"); 这样在前端提交一下评论,就可以看到后端给发送过来 create.js 的内容,并在浏览器里执行了。好,这就是基本流程,挺简单吧。
点击"提交评论",这样前端就会向后端服务器请求javascript的资源,后端响应请求就把javascript的资源(也就是我们定义的create.js文件里的代码)传递给前端浏览器并在浏览器中执行,效果如下:
如上就是是最简单的ajax,体现了ajax的完整工作流程。
当然我们要做的不是打印hello,而是要在评论列表的最底部追加一条评论,而这里要添加的内容就是一些比较复杂的html了,直接都写在create.js文件不太好,我这么做:
将文件后缀改成.js.erb,然后让它里面渲染一个局部模板就可以了:
但是直接render模板,因为视图模板里面的引号和换行符直接render会导致javascript的语法错误,可以使用rails的接口escape_javascript 把 视图模板中的引号和回车进行转义;该接口函数可以简写为一个字母j。如下:
现在还没有每条评论对应的comment这个局部模板
所以我们把显示全部评论的_commit_list.html.erb重命名为_commit.html.erb就可以了(博客的show页面里面使用_commit_list.html.erb局部模板,使用ajax之后我们后面会把渲染_commit_list.html.erb得到全部评论改成使用each遍历渲染每个_commit.html.erb即每个评论视图模板达到一样的效果),如下:
补充:上面那样变成渲染局部模板_commit.html.erb会让人感觉这跟前面学习的视图部分一样了,跟ajax没有关系了吧。其实不是,这里渲染局部模板的代码放在create.js.erb文件并且用escape_javascript 把 视图模板中的引号和回车进行转义成js资源了,所以每当提交新的评论是想服务器请求这个js资源,所以这就是ajax,不是以前的渲染视图。点击提交按钮,create.js.erb文件中的js资源都会被返回,也就是返回一个弹出框显示hello,并且返回渲染的评论局部模板。
因为每点击一次提交按钮就得到create.js.erb里的全部js资源,我们希望每次提交得到一条新评论的局部模板,所以这个create.js.erb里面是渲染单条评论的局部模板;如果还是_comment_list.erb那么结果将是每次提交一条评论,show页面视图多出一遍全部评论,虽然页面也是没刷新的ajax方式,但是页面已经显示全部评论了,我们只想页面添加新提交的评论而已不是添加全部评论。
所以_commit.html.erb视图代码里面改成只渲染一条评论:
在来到博客的show页面(也就是上面我们的那个页面),我们找到包裹评论的div,发现评论框也在里面:
更改为:
把评论框的局部视图移动到评论列表的div下面,防止新添加的评论显示在评论框下面。
因为现在没有_comment_list.html.erb了,所以show页面只能遍历每个_comment.html.erb来显示全部评论
注意我们在上面create.js.erb中渲染js资源为每条评论的局部视图那时就已经完成ajax的全部代码了,如下就是跟ajax无关了,就是最普通的渲染局部视图而已。当我们点击提交按钮,跟show页面的渲染局部视图没有任何关系也就是跟下图代码没有关系:
页面新呈现的代码不是上面each多渲染一个评论视图得到的,而是跟最开始获取hello弹出框一样从create.js.erb的js资源中得到的
(1)
普通的渲染方式---show.html.erb中each方法渲染每个评论的局部视图,我们传递给它一个局部变量c(c用each得到的实例变量赋值),使得该局部视图无对应的动作实例变量也可使用该变量。如下的局部变量c是@comments.each do |c|中的c得到的
(2)
Ajax的渲染方式---show页面中局部视图里_comment_box.html.erb中form_for标签添加 remote: true(因为提交给动作是create动作所以找到对应的create.js.erb的js资源文件,该文件中的局部视图需要使用评论变量所以也要传递进来这个局部变量)
原来:
修改为:
因为_comment,html.erb中使用了局部变量c,而_comment.html.erb是局部视图无对应动作实例变量可用。
方式一:
我们就想到comments控制器的其他视图可以使用实例变量,我们可以在comments控制器对应动作的视图中将该实例变量传递给改局部模板(该动作视图也是类似,就是该动作视图也需要加载该局部模板),不过comments控制器只有create动作,所以这个方式行不通。
方式二:
这里create.js.erb对应的就是comments的create动作,可以在create.js.erb使用实例变量。我们无法通过其他动作视图将实例变量赋值给局部变量,然后再将局部变量传递给局部模板使用,那么此时可以通过create.js.erb将实例变量赋值给局部变量,然后再将局部变量传递给局部模板使用来完成。
create.js.erb要使用实例变量,所以控制器动作改成实例变量
修改为:
然后我们查看效果,输入222点击提交之后,页面没有刷新并且在上方显示提交的评论
但是我们希望提交之后输入框的222清空,并且不希望保留原来弹出的提示框显示hello,于是修改create.js.erb为如下:
再来试一下,输入eee点击提交评论,页面不刷新且在上面显示提交的评论,并且输入框内容提交后自动清空: