kindEditor官网:http://kindeditor.net/demo.php
个人实践:
为了在自己的项目中引入一个类似用户写博客的功能,在网上找到了kindeditor,真心又好又易用。
一、准备工作
1、下载kindeditor,在官网上点击右上方的下载链接,我下载的是4.1.11版。由于我的项目前端采用jsp,所以将解压以后的asp,asp.net和php文件夹删掉。
在jsp文件夹里的就是在jsp下使用kindeditor的demo。需要注意的是,demo.jsp文件中有笔误,需要首先将<script charset="utf-8" src="../kindeditor.js"></script>
的src指向kindeditor-all-min.js才可以。
2、阅读demo.jsp:kindeditor的js使用K.create方法,于页面加载完毕以后,在name是content1的textarea前面自动创建博客编辑功能toolbar,最关键的uploadJson参数指定
了用户上传图片、动画、文件等媒体文件的后台处理controller。在demo.jsp中,表单提交给了demo.jsp自己,可以在jsp代码的开头部分发现,程序从request中将content1的
内容取了出来,在body中,将内容展示在form表单之前。也就是说demo.jsp中的博客编辑器提交后的内容会在编辑器上方展示出来。需要注意的是htmlspecialchars方法,它
将html标签中的相关字符进行转义,目的是将用户提交的内容在博客编辑器中显示,方便用户继续编辑已经存在的内容。这个转义的过程在项目中我们其实是放在后台处理的,
做好以后直接传给前台展示。fileManagerJson参数指定了上传后文件的编辑界面,由于我的项目不需要这个功能,所以将这个参数舍去,将allowFileManager参数置false。
afterCreate参数指定了创建博客编辑功能toolbar以后做的事,就是当用户使用ctrl+enter的时候提交表单。我不需要这功能所以也可以舍去afterCreate。
3、阅读完了demo.jsp以后,如何展示编辑工具栏,如何提交表单,如何在编辑框中显示已经存在的内容,这几个关键的基本问题就都找到答案了,下面就是实践和解决细节问题。
主要是两个后台问题,一是如何处理uploadJson参数对应的文件上传及给kindeditor返回准确的值,二是表单提交以后如何在后台取到提交的内容,由于我使用了springmvc的form
标签库,如何结合标签库也是个小问题。
二、编码和实现
1、jsp文件片段
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>${pageTitle }</title> <link href="/Public/media/css/new/page.css" rel="stylesheet" type="text/css" /> <link rel="stylesheet" href="/Public/kindeditor/themes/default/default.css" /> <link rel="stylesheet" href="/Public/kindeditor/plugins/code/prettify.css" /> <script charset="utf-8" src="/Public/kindeditor/kindeditor-all-min.js"></script> <script charset="utf-8" src="/Public/kindeditor/lang/zh-CN.js"></script> <script charset="utf-8" src="/Public/kindeditor/plugins/code/prettify.js"></script> <script> KindEditor.ready(function(K) { var editor1 = K.create('textarea[name="content"]', { cssPath : '/Public/kindeditor/plugins/code/prettify.css', uploadJson : '${pageContext.request.contextPath}/workConfig/updateContent', allowFileManager : false }); prettyPrint(); }); </script> </head> <body> <form:form action="${formActionURL}" method="post" commandName="work"> <div class="formDiv"> <table width="1020" border="0"> <tr> <td align="right">展示页面:</td> <td align="left"> <textarea name="content" rows="20" cols="100" style=" 700px; height: 200px; visibility: hidden;"> ${work.content } </textarea> </td> </tr> </table> </div> </form:form> </body> </html>
1.1:曾尝试过使用springmvc的textarea标签库<form:textarea,虽然只要将kindeditor的K.create里textarea的name改为<form:textarea的name即可(假设form:textarea的path值为"content",那么这个标签库在被翻译成html时,textarea的name和id会被自动赋值为"content"),但由于textarea的内容会被springmvc自动赋值给表单代表的Model类的相应值,所以htmlspecialchars的转义过程就没法进行了,当然你也可以修改JavaBean的
getContent方法中加入转义过程,但这么做稍显繁琐,不如就不用form:textarea标签库,只要将其name值设置为表单Model类的属性值,内容在提交后一样会被自动赋值给对应的属性(经试验证实)。
2、文件上传处理程序片段
@RequestMapping(value="/updateContent",produces = "application/json; charset=utf-8") @ResponseBody public String updateContent(MultipartHttpServletRequest request) { MultipartFile realMediaFile = null; Iterator<String> iter = request.getFileNames(); JSONObject obj = new JSONObject(); while (iter.hasNext()) { // 取得上传文件 MultipartFile file = request.getFile(iter.next()); if (file != null) { realMediaFile = file; } if (realMediaFile != null) { // 定义允许上传的文件扩展名 String extStr = "gif,jpg,jpeg,png,bmp,swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb,doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2"; List<String> extList = Arrays.asList(extStr.split(",")); // 最大文件大小 long maxSize = 1000000; // 准备好路径参数 String uploadMediaSavePath = pathUtil.getYgbhUploadMediaDiskFolderPath(request); String urlRootPath = pathUtil.getYgbhUploadMediaUrlFolderPath(); List<MultipartFile> files = new ArrayList<MultipartFile>(); files.add(realMediaFile); // 保存 String message = ""; if (realMediaFile.getSize() > maxSize) { message = "上传文件大小超过限制。"; } String originalfileName = realMediaFile.getOriginalFilename(); String suffixName = originalfileName.substring(originalfileName.lastIndexOf(".") + 1).toLowerCase(); if (!extList.contains(suffixName)) { message = "上传文件扩展名是不允许的扩展名。 只允许[" + extStr + "]格式。"; } String newFileName = ""; try { newFileName = MultipartFileUtil.saveMediaFiles(files, uploadMediaSavePath, urlRootPath).get(0); } catch (Exception e) { message = "上传文件失败。"; } try { if (!message.isEmpty()) { obj.put("error", 1); obj.put("message", message); } else { obj.put("error", 0); obj.put("url", urlRootPath + newFileName); } } catch (Exception e) { log.error("上传文件[" + originalfileName + "]失败。"); e.printStackTrace(); } } else { try { obj.put("error", 1); obj.put("message", "找不到文件。"); } catch (JSONException e) { log.error("上传文件失败。"); e.printStackTrace(); } } return obj.toString(); } return obj.toString(); }
2.1:使用@ResponseBody的返回值为String类型的方法,在试验后被证明是可以使用的。如果不标识@ResponseBody,返回String类型的Controller方法会将返回值视为jsp文件的路径去寻找jsp文件。而如果你打算
将返回值类型用某JavaBean来代替,心想反正使用了@ResponseBody,springmvc会将对象转换成正确的JSON格式,结果是kindeditor拿不到返回值。
2.2:由于kindeditor生成的博客编辑工具栏有批量上传按钮,所以在取得用户提交内容时,需要用iterator的方式。
2.3:看到urlRootPath变量了吗,它指向我的系统的静态资源工程,是一个只能在本系统内可以访问的不跨域url。这也牵扯到后面的一个问题。需求是:用户自己编写的博客内容,会以html的方式保存在系统的数据库中,
该段html代码必须在它被从数据库里取出并放置在一个html页面的body片段中时能够完整地展示所有内容,包括图片、动画等媒体资源内容。那么问题来了,无论我怎么设置,就算是我直接在urlRootPath前拼接“http://
XXX.XXX.XX.XX:8080”,等到内容显示在textarea编辑框中时,图片等媒体资源的src依然被kindeditor自动改为不跨域的url,也就是说,只能在系统内展示时才能看见这些非文本的内容。如何优雅地解决该问题我至今
还没找到很好的办法,只是暂时在保存博客内容html时,将所有包含不跨域url的内容前面自动加上类似“http://XXX.XXX.XX.XX:8080”的前缀,以保证该资源在任何地方都能被访问。
补充,上面划线部分的问题已经得到解决(参考资料:http://www.111cn.net/wy/255/64719.htm),给KindEditor加一个与uploadJson同级别的运行参数,urlType:'domain',
urlTypeY有四种值,""表示不修改URL,"relative"表示相对路径,"absolute"表示绝对路径,"domain表示带域名的绝对路径"。
由于该问题已解决,所以3中的replace过程可以去掉了。
2.4:produces = "application/json; charset=utf-8"这句如果不加,返回的中文内容会是乱码。
3、表单提交内容处理程序片段
@RequestMapping(value = "/editWork", method = RequestMethod.POST) public ModelAndView editWork(@Valid @ModelAttribute("work") DesignerWorks work, BindingResult br, HttpServletRequest request) { ModelAndView mv = new ModelAndView(); mv.addObject("work", work); if (br.hasErrors()) { mv.addObject("currentMenuDesc", "当前目录:首页>系统管理>作品管理>编辑作品"); mv.addObject("backToURL", request.getContextPath() + "/workConfig/openWorkConfig"); mv.addObject("formActionURL", request.getContextPath() + "/workConfig/editWork"); mv.addObject("pageTitle", "编辑作品"); mv.setViewName("editWork"); } else { try { DesignerWorks workToSave = work; workToSave.setContent(workToSave.getContent().replace(pathUtil.getServerUrl(request), "")); workToSave.setContent(workToSave.getContent().replace(pathUtil.getUploadMediaRootUrl(), pathUtil.getServerUrl(request) + pathUtil.getUploadMediaRootUrl())); designerService.updateWork(work); } catch (Exception e) { mv.addObject(ConstantsUtil.ERROR_STACK, ExceptionUtils.getStackTrace(e)); mv.setViewName("error"); return mv; } mv.addObject(ConstantsUtil.BACK_TO_URL, request.getContextPath() + "/workConfig/openWorkConfig"); mv.setViewName("success"); } return mv; }
3.1:可以看到媒体文件url的replace的过程,getServerUrl的代码片段如下:
import java.io.File;
import java.net.InetAddress;
import javax.servlet.http.HttpServletRequest;
public static String getServerUrl(HttpServletRequest request) throws Exception{
return "http://"+InetAddress.getLocalHost().getHostAddress()+":"+request.getServerPort();
}
三、补充
1、问题:在之后的项目中,我使用了jquery.easyui.min.js的.form()来间接提交表单,发现由kindeditor生成的textarea内容始终无法被后台接收到。
2、调查:查看页面源码我发现,kindeditor工作时,是在被它修饰的textarea的上方加了一个div,用户输入的所有内容其实都是在编辑div中的内容而不是直接写在textarea里,
当用户使用传统方式提交表单时,kindeditor一定是自动有一个将用户输入内容粘贴入textarea的动作,而当使用js间接提交表单时,kindeditor的这个自动的动作没有被触发,所以
后台得到的是还未被粘贴内容的textarea的文本。
3、解决:最直接的想办法是手动调一下这个自动粘贴的程序。搜索了一下发现已有人解决。http://lxy.me/ajax-kindeditor-content-to-textarea.html
<script type="text/javascript">
KindEditor.ready(function(K){
K.create('textarea[name="content"]', {
themeType: 'simple',
resizeType: 1,
uploadJson: 'common/KEditor/upload_json.php',
fileManagerJson: 'common/KEditor/file_manager_json.php',
allowFileManager: true,
//下面这行代码就是关键的所在,当失去焦点时执行this.sync(),同步输入的值到textarea中;
afterBlur: function(){this.sync();}
});
});
</script>