最近在做一个博客网站,文章编辑器使用的是markdown语法解析,解析方法使用到一个helper方法markdown @article.content
,所以内部的编辑器实际上就是一个textarea
,没有其他工具,看上去光秃秃的,所以我想给它添加点小工具,方便使用。而第一个要添加的工具就是预览功能。
我的想法是这样的:首先预先创建一个<div id="preview-text"></div>
,然后将其设置为display:none
。剩下要做的就是当用户点击预览按钮时,将动态获取textarea
的内容,并将其传递给rails的markdown
方法进行渲染,将渲染结果输出到div#preview-text
。使用jquery的slideDown()
和slideUp()
函数可以很好的控制div#preview-text
的显示和隐藏。而获取textarea
的内容的解决方案,目前也只能靠js完成。于是剩下的问题就很明了了,即如何将js获取的textarea
的内容传递给rails的markdown
函数。一个突出的问题是,js不能使用rails的函数;rails在erb模板中可以获得js变量,但其在不刷新页面的情况下,只能解析一次(erb只能保存静态变量,无法保存动态变量,即erb在编译后其所定义的变量就不变了)。于是最好的办法就只能是ajax了(js获取内容传递给服务器,服务器(rails)解析后,js获取解析后的内容,异步加载到div#preview-text
)。
但rails中如何使用ajax呢?
rails对ajax做了很好的封装,我们可以更加方便的在rails中使用ajax。
首先要构建一个请求,链接,表单等都可以充当一个发送请求的入口。以链接为例:
<%= link_to 'ajax',deal_ajax_path(text:"text"),remote:true,method: :post %>
<%# 路由需自己设计 %>
注意一定要有remote:true
这代表你将向那个url发送一个远程请求而不是转到那个目标url去。
此时在对应的controller中就可以获取params[:text]
然后返回json信息。
def preview
@preview_text = markdown(params[:text])
respond_to do |format|
if @preview_text
format.js {}
format.json { render json: @preview_text , status: :success, location: @preview_text }
else
format.json { render json: @preview_text, status: :unprocessable_entity }
end
end
end
由于之前的访问是remote:true
所以会返回json格式的数据,在rails中会自动寻找对应action名的模板进行渲染,在这里是preview.js.erb
(自行创建)
在preview.js.erb
中可以使用js:
$("#preview_text").html("<%= escape_javascript(@preview_text) %>");
至此,ajax所需的所有工作都已做完了。
对于一般的需求,到这就为止了,但有个问题,即传递的text
该在哪设置。上文作为演示,在url后加上了一个固定的文本,但我要做的是实时获取textarea
域的文本。我第一次想到的方案是,当用户点击预览按钮,使用js动态设置url的参数。这有个问题,即用户点击一次后,在html中就会保留上次动态在url后添加的文本参数,该参数将非常长,很不美观。因此我使用了第二种方法:丢弃rails提供的remote方式,自行用js构造请求:
$(document).on("click","#tool-preview",function(event){
preview_text = $("#preview_text").val();
$.post("/admin/mdtools/preview",
{
text:preview_text
},function(data,status){
if(...){
$("#preview_text").slideDown("fast");
}else{
$("#preview_text").slideUp("fast");
}
})
})
至此终于达到我的预期功能。
预览前:
预览后: