在ajax打开的页面中加载tinymce,ajax重新绑定事件响应秘诀





在一些界面的开发中,我们使用ajax的方法保证页面无刷新的操作,这样可以让操作更流畅。但是有一个问题,通过ajax的方法请求一个页面,把抓取到的节点加载到当前页面的DOM中,这时新加载的dom节点并没有在当前页面的js初始化过程中绑定事件响应,因此,新加载进来的这些元素不会完成当前页面的原始操作。


那么怎么解决这个问题呢?方法很简单,即新加载DOM结束之后,再次执行绑定动作,让新加载的节点元素也可以响应事件。


在get到这个点之后,我们再来看下tinymce的具体实践情况。


首先,我们有一个页面a.html,在b.html中有一个textarea需要加载tinymce作为富文本编辑器。我们希望在a.html中,通过一个弹出层,把b.html加载进来,在a.html中操作这个富文本区域,实现表单提交。就像:


2016-03-04 14-08-36屏幕截图


那么我们要怎么来实现呢?


我们在a.html中,使用下面的js来实现ajax重新绑定事件:


$('#open').click(function(e){    e.preventDefault();


$.get('b.html',function(result){    var html = $(result).find('#form');    $('#dialog').html(html).show(); // 将抓取到的html元素加入到a.html的#dialog节点中,并且把#dialog显示出来    var options = {        // tinymce的配置项    }    $('#editor').tinymce(options); // 注意,editor的b.html中textarea的id值});


});


这样,每次在点击#open这个按钮的时候,就会先请求b.html,并将抓取到的html元素作为内容加载到弹出层中显示出来,并且重新绑定新加载的dom的编辑器事件,展示富文本编辑器。


可是有一个严重的问题,估计这个问题很多情况下都会出现,不单单是tinymce。那就是dom中是否已经存在有对应的tinymce.editors?


我们要从内存的角度去分析这个问题。当tinymce被初始化之后,富文本编辑器的渲染结果已经以dom节点的形式存在与内存中,每一个富文本编辑器对应的dom节点由产生该编辑器的textarea的id值(或其他选择器值)决定。这也就让tinymce开发者决定,在第二次打开tinymce时,只需要从内存中取出对应的操作,将它再绑定到渲染出来的dom节点上去。


而这个操作,直接影响了我们上述代码的最终执行效果。因为我们第一次执行$('#editor').tinymce(options);时是ok的,会自然看到富文本编辑器。但是,当我们关闭弹出层,第二次点击#open时,会发现,编辑器处一片空白,编辑器没有被加载。这是什么原因呢?


原来,在第二次打开弹出层时,我们通过ajax去抓取到html元素加载进来之后,tinymce不会做任何操作。为什么呢?因为在内存中tinymce已经实例化了#editor这个textarea,tinymce认为不需要再实例化一次。可是实际上,当tinymce实例化一个编辑器时,会通过加载一些新的<div>节点,来实现编辑器的皮肤效果,比如编辑器的工具按钮等;而如果我们通过ajax再次加载得到的html中,并没有这些<div>节点,而tinymce又不再次实例化#editor,所以实际上,现在的dom中,是不存在编辑器皮肤相关的<div>节点的,所以,我们当然看不到我们熟悉的编辑器界面。


那么怎么解决这个问题呢?第一种方法是,我们仅第一次点击#open的时候把b.html的内容加载进来,第二次点击#open的时候,不要再去抓取,而是直接打开原来加载好的,这个方法可以称为“缓存法”。但是,缓存法会导致当b.html得到更新时,a.html打开的内容不即时。比如,原本textarea中没有任何内容,可是在第二次打开的时候textarea实际上又被增加了内容,这样的话,在a.html的缓存中,并不存在这些内容,导致提交表单的时候会覆盖原来添加的内容。


第二种方法则是我们本文要讲的重新绑定的方法,可以称为“重载法”。什么意思呢?就是取消tinymce对内存中是否存在相关节点的判定,每次执行$('#editor').tinymce(options);时,都往内存中增加新节点(同时销毁原有节点)。当然,这种方法是比较消耗资源的,会反复的消耗内存,因此只在必要的时候这样子去操作。


$('#open').click(function(e){    e.preventDefault();


$.get('b.html',function(result){    var html = $(result).find('#form');    $('#dialog').html(html).show(); // 将抓取到的html元素加入到a.html的#dialog节点中,并且把#dialog显示出来    var options = {        // tinymce的配置项    }    tinymce.execCommand('mceRemoveEditor',true,'editor'); // tinymce 4.0+    $('#editor').tinymce(options); // 注意,editor的b.html中textarea的id值});


});


和第一段代码相比,多了上面红色的部分。这一句表示,将内存中原有的通过#editor创建的tinymce资源销毁,一旦销毁,那么下面的tinymce就会再执行init动作了。注意一个细节,上面的'editor'不是'#editor',该句操作不依赖jquery的语法,而execCommand的第三个参数是editor_id的意思,也就是textarea的id属性值。另外,4.0+版本才使用'mceRemoveEditor'参数,3.0+版本则使用'mceRemoveControl'参数,注意区别。


除了tinymce,很多插件都是这样,比如幻灯、瀑布流、评论框等等,当你使用ajax去请求新内容时,一定要记住,新加载的节点,需要重新绑定原有的事件响应。

有话要说