Bootstrap 可视化编辑器summernote
版权声明:本文出自沉默王二的博客,转载必须注明出处。技术交流群 120926808
目录(?)[+]
Bootstrap 可视化HTML编辑器之summernote,用其官网上的介绍就是“Super Simple WYSIWYG editor”,不过在我看来,与bootstrap中文官网上提供的“bootstrap-wysiwyg”要更simple,更漂亮,更好用!
虽然我之前尝试过使用bootstrap-wysiwyg,可参照Bootstrap wysiwyg富文本数据如何保存到mysql,但事后诸葛亮的经验告诉我,summernote绝对是更佳的富文本编辑器,这里对其工作team点三十二个赞!!!!!
经过一天时间的探索,对summernote有所掌握,那么为了更广大前端爱好者提供便利,我将费劲一番心血来介绍一下summernote,超级福利啊。
一、官方API和源码下载
工欲善其事必先利其器,首先把summernote的源码拿到以及对应官方API告诉大家是首个任务!
官网(demo和api)
github源码下载,注意下载开发版
二、效果图
效果图1
效果图2
效果图3
三、开讲内容
大的方向为以下三个内容:
- summernote的页面布局(资源引入、初始参数)
- summernote从本地上传图片方法(前端onImageUpload方法、后端springMVC文件保存)
- summernote所在form表单的数据提交
①、summernote的页面布局
<!DOCTYPE html>
<html lang="zh-CN">
<%@ include file="/components/common/taglib.jsp"%>
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" /><title>summernote - bs3fa4</title><!-- include jquery -->
<script type="text/javascript" src="${ctx}/components/jquery/jquery.js"></script><!-- include libs stylesheets --><link type="text/css" rel="stylesheet" href="${ctx}/components/bootstrap/css/bootstrap.css" />
<script type="text/javascript" src="${ctx}/components/bootstrap/js/bootstrap.min.js"></script><!-- include summernote -->
<link type="text/css" rel="stylesheet" href="${ctx}/components/summernote/summernote.css" />
<script type="text/javascript" src="${ctx}/components/summernote/summernote.js"></script>
<script type="text/javascript" src="${ctx}/components/summernote/lang/summernote-zh-CN.js"></script><script type="text/javascript">$('div.summernote').each(function() {var $this = $(this);var placeholder = $this.attr("placeholder") || '';var url = $this.attr("action") || '';$this.summernote({lang : 'zh-CN',placeholder : placeholder,minHeight : 300,dialogsFade : true,// Add fade effect on dialogsdialogsInBody : true,// Dialogs can be placed in body, not in// summernote.disableDragAndDrop : false,// default false You can disable drag// and dropcallbacks : {onImageUpload : function(files) {var $files = $(files);$files.each(function() {var file = this;var data = new FormData();data.append("file", file);$.ajax({data : data,type : "POST",url : url,cache : false,contentType : false,processData : false,success : function(response) {var json = YUNM.jsonEval(response);YUNM.debug(json);YUNM.ajaxDone(json);if (json[YUNM.keys.statusCode] == YUNM.statusCode.ok) {// 文件不为空if (json[YUNM.keys.result]) {var imageUrl = json[YUNM.keys.result].completeSavePath;$this.summernote('insertImage', imageUrl, function($image) {});}}},error : YUNM.ajaxError});});}}});});</script>
</head>
<body>
<div class="container"><form class="form-horizontal required-validate" action="#" enctype="multipart/form-data" method="post" onsubmit="return iframeCallback(this, pageAjaxDone)"><div class="form-group"><label for="" class="col-md-2 control-label">项目封面</label><div class="col-md-8 tl th"><input type="file" name="image" class="projectfile" value="${deal.image}"/><p class="help-block">支持jpg、jpeg、png、gif格式,大小不超过2.0M</p></div></div><div class="form-group"><label for="" class="col-md-2 control-label">项目详情</label><div class="col-md-8"><div class="summernote" name="description" placeholder="请对项目进行详细的描述,使更多的人了解你的" action="${ctx}/file">${deal.description}</div></div></div></form>
</div>
</body>
</html>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
<!DOCTYPE html>
html5的标记是必须的,注意千万不能是<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
这种doctype,否则summernote的组件显示怪怪的,按钮的大小布局不一致,这里就不再上图了,但是千万注意!- bootstrap 的版本号最好为v3.3.5
1、布局div
<div class="summernote" name="description" placeholder="请对项目进行详细的描述,使更多的人了解你的" action="${ctx}/file">${deal.description}</div>
- 1
- 1
相信你也看到了我为div加上的三个属性name、placeholder、action,那么我们来详细介绍一下三个属性的作用:
- name,为外层form表单提供summernote数据保存时的数据模型的属性名,和input标签的name属性作用一致,稍候在form提交的时候具体介绍。
- placeholder,很直白,为summernote提供初始状态的文本描述,当然还需要后续加工,div显然是不支持placeholder属性的。
- action,为图片上传提供后端接收地址,稍候在介绍图片上传onImageUpload会再次用到。
另外${deal.description}其实你不需要太多关注,和textarea的赋值的用法一致,就是单纯的显示保存后的内容。
2、summernote初始化
$('div.summernote').each(function() {var $this = $(this);var placeholder = $this.attr("placeholder") || '';var url = $this.attr("action") || '';$this.summernote({lang : 'zh-CN',placeholder : placeholder,minHeight : 300,dialogsFade : true,// Add fade effect on dialogsdialogsInBody : true,// Dialogs can be placed in body, not in// summernote.disableDragAndDrop : false,// default false You can disable drag// and drop});});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
使用jQuery获取到页面上的summernote,对其进行初始化,我们来详细介绍列出参数的用法(先不介绍图片上传的onImageUpload 方法)。
- lang ,指定语言为中文简体
- placeholder ,summernote初始化显示的内容。
- minHeight,最小高度为300,注意这里没有使用height,是有原因的,这里稍作解释,就不上图了。当使用height指定高度后,假如上传比height高的图片,summernote就不会自动调整高度,并且前文中“效果图3”中标出的红色区域会不贴着图片,而溢出到summernote外部。
- dialogsFade,增加summernote上弹出窗口滑进滑出的动态效果。
- dialogsInBody,这个属性也很关键,默认为false,字面上的意思是summernote的弹出框是否在body中(in嘛),设置为false时,dialog的式样会继承其上一级外部(如上文中的form-horizontal)容器式样,那么显示的效果就很别扭,这里也不再上图;那么设置为true时,就不会继承上一级外部div的属性啦,从属于body嘛。
- disableDragAndDrop,设置为false吧,有的时候拖拽会出点问题,你可实践。
②、summernote从本地上传图片方法
1、前端onImageUpload方法
假如问度娘如下的话:“onImageUpload方法怎么写?”,度娘大多会为你找到如下回答:
$(\'.summernote\').summernote({height:300,onImageUpload: function(files, editor, welEditable) {sendFile(files[0],editor,welEditable);}});});function sendFile(file, editor, welEditable) {data = new FormData();data.append("file", file);url = "http://localhost/spichlerz/uploads";$.ajax({data: data,type: "POST",url: url,cache: false,contentType: false,processData: false,success: function (url) {editor.insertImage(welEditable, url);}});
}
</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
以上资源来自于stackoverflow。
但其实呢,summernote-develop版本的summernote已经不支持这种onImageUpload写法,那么如今的写法是什么样子呢?参照summernote的官网例子。
onImageUpload
Override image upload handler(default: base64 dataURL on IMG tag). You can upload image to server or AWS S3: more…
// onImageUpload callback
$('#summernote').summernote({callbacks: {onImageUpload: function(files) {// upload image to server and create imgNode...$summernote.summernote('insertNode', imgNode);}}
});// summernote.image.upload
$('#summernote').on('summernote.image.upload', function(we, files) {// upload image to server and create imgNode...$summernote.summernote('insertNode', imgNode);
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
那么此时onImageUpload的具体写法呢?(后端为springMVC):
callbacks : {// onImageUpload的参数为files,summernote支持选择多张图片onImageUpload : function(files) {var $files = $(files);// 通过each方法遍历每一个file$files.each(function() {var file = this;// FormData,新的form表单封装,具体可百度,但其实用法很简单,如下var data = new FormData();// 将文件加入到file中,后端可获得到参数名为“file”data.append("file", file);// ajax上传$.ajax({data : data,type : "POST",url : url,// div上的actioncache : false,contentType : false,processData : false,// 成功时调用方法,后端返回json数据success : function(response) {// 封装的eval方法,可百度var json = YUNM.jsonEval(response);// 控制台输出返回数据YUNM.debug(json);// 封装方法,主要是显示错误提示信息YUNM.ajaxDone(json);// 状态ok时if (json[YUNM.keys.statusCode] == YUNM.statusCode.ok) {// 文件不为空if (json[YUNM.keys.result]) {// 获取后台数据保存的图片完整路径var imageUrl = json[YUNM.keys.result].completeSavePath;// 插入到summernote$this.summernote('insertImage', imageUrl, function($image) {// todo,后续可以对image对象增加新的css式样等等,这里默认});}}},// ajax请求失败时处理error : YUNM.ajaxError});});}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
注释当中加的很详细,这里把其他关联的代码一并贴出,仅供参照。
debug : function(msg) {if (this._set.debug) {if (typeof (console) != "undefined")console.log(msg);elsealert(msg);}},
jsonEval : function(data) {try {if ($.type(data) == 'string')return eval('(' + data + ')');elsereturn data;} catch (e) {return {};}},ajaxError : function(xhr, ajaxOptions, thrownError) {if (xhr.responseText) {$.showErr("<div>" + xhr.responseText + "</div>");} else {$.showErr("<div>Http status: " + xhr.status + " " + xhr.statusText + "</div>" + "<div>ajaxOptions: " + ajaxOptions + "</div>"+ "<div>thrownError: " + thrownError + "</div>");}},ajaxDone : function(json) {if (json[YUNM.keys.statusCode] == YUNM.statusCode.error) {if (json[YUNM.keys.message]) {YUNM.debug(json[YUNM.keys.message]);$.showErr(json[YUNM.keys.message]);}} else if (json[YUNM.keys.statusCode] == YUNM.statusCode.timeout) {YUNM.debug(json[YUNM.keys.message]);$.showErr(json[YUNM.keys.message] || YUNM.msg("sessionTimout"), YUNM.loadLogin);}},
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
2、后端springMVC文件保存
2.1、为springMVC增加文件的配置
<bean id="multipartResolver"class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="UTF-8"><property name="maxUploadSize" value="1024000000"></property></bean><mvc:annotation-driven conversion-service="conversionService" /><bean id="conversionService"class="org.springframework.format.support.FormattingConversionServiceFactoryBean"><property name="converters"><list><!-- 这里使用string to date可以将dao在jsp到controller转换的时候直接将string格式的日期转换为date类型 --><bean class="com.honzh.common.plugin.StringToDateConverter" />
<!-- 为type为file类型的数据模型增加转换器 --><bean class="com.honzh.common.plugin.CommonsMultipartFileToString" /></list></property></bean>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
这里就不做过多介绍了,可参照我之前写的SpringMVC之context-dispatcher.xml,了解基本的控制器
2.2、FileController.java
package com.honzh.spring.controller;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;import com.honzh.common.base.UploadFile;
import com.honzh.spring.service.FileService;@Controller
@RequestMapping(value = "/file")
public class FileController extends BaseController {private static Logger logger = Logger.getLogger(FileController.class);@Autowiredprivate FileService fileService;@RequestMapping("")public void index(HttpServletRequest request, HttpServletResponse response) {logger.debug("获取上传文件...");try {UploadFile uploadFiles = fileService.saveFile(request);renderJsonDone(response, uploadFiles);} catch (Exception e) {logger.error(e.getMessage());logger.error(e.getMessage(), e);renderJsonError(response, "文件上传失败");}}}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
2.3、FileService.java
package com.honzh.spring.service;import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;import javax.servlet.http.HttpServletRequest;import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;import com.honzh.common.Variables;
import com.honzh.common.base.UploadFile;
import com.honzh.common.util.DateUtil;@Service
public class FileService {private static Logger logger = Logger.getLogger(FileService.class);public UploadFile saveFile(HttpServletRequest request) throws IOException {logger.debug("获取上传文件...");// 转换为文件类型的requestMultipartHttpServletRequest multipartRequest = (MultipartHttpServletRequest) request;// 获取对应file对象Map<String, MultipartFile> fileMap = multipartRequest.getFileMap();Iterator<String> fileIterator = multipartRequest.getFileNames();// 获取项目的相对路径(http://localhost:8080/file)String requestURL = request.getRequestURL().toString();String prePath = requestURL.substring(0, requestURL.indexOf(Variables.ctx));while (fileIterator.hasNext()) {String fileKey = fileIterator.next();logger.debug("文件名为:" + fileKey);// 获取对应文件MultipartFile multipartFile = fileMap.get(fileKey);if (multipartFile.getSize() != 0L) {validateImage(multipartFile);// 调用saveImage方法保存UploadFile file = saveImage(multipartFile);file.setPrePath(prePath);return file;}}return null;}private UploadFile saveImage(MultipartFile image) throws IOException {String originalFilename = image.getOriginalFilename();logger.debug("文件原始名称为:" + originalFilename);String contentType = image.getContentType();String type = contentType.substring(contentType.indexOf("/") + 1);String fileName = DateUtil.getCurrentMillStr() + new Random().nextInt(100) + "." + type;// 封装了一个简单的file对象,增加了几个属性UploadFile file = new UploadFile(Variables.save_directory, fileName);file.setContentType(contentType);logger.debug("文件保存路径:" + file.getSaveDirectory());// 通过org.apache.commons.io.FileUtils的writeByteArrayToFile对图片进行保存FileUtils.writeByteArrayToFile(file.getFile(), image.getBytes());return file;}private void validateImage(MultipartFile image) {}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
2.4、UploadFile.java
package com.honzh.common.base;import java.io.File;import com.honzh.common.Variables;public class UploadFile {private String saveDirectory;private String fileName;private String contentType;private String prePath;private String completeSavePath;private String relativeSavePath;public UploadFile(String saveDirectory, String filesystemName) {this.saveDirectory = saveDirectory;this.fileName = filesystemName;}public String getFileName() {return fileName;}public String getSaveDirectory() {return saveDirectory;}public String getContentType() {return contentType;}public void setContentType(String contentType) {this.contentType = contentType;}public String getPrePath() {if (prePath == null) {return "";}return prePath;}public void setPrePath(String prePath) {this.prePath = prePath;setCompleteSavePath(prePath + getRelativeSavePath());}public String getCompleteSavePath() {return completeSavePath;}public void setCompleteSavePath(String completeSavePath) {this.completeSavePath = completeSavePath;}public String getRelativeSavePath() {return relativeSavePath;}public void setRelativeSavePath(String relativeSavePath) {this.relativeSavePath = relativeSavePath;}public void setSaveDirectory(String saveDirectory) {this.saveDirectory = saveDirectory;}public void setFileName(String fileName) {this.fileName = fileName;}public File getFile() {if (getSaveDirectory() == null || getFileName() == null) {return null;} else {setRelativeSavePath(Variables.ctx + "/" + Variables.upload + "/" + getFileName());return new File(getSaveDirectory() + "/" + getFileName());}}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
后端文件保存方法也非常简单,懂Java的同学都可以看得懂,那么对于后端不使用springmvc的同学,你可以再找找方法。
辛苦的介绍完前两节后,我们来一个动态图看一下效果吧!
③. summernote所在form表单的数据提交
这里,我们再回顾一下summernote所在的form表单,其中还包含了一个普通file的input标签,也就是说,该form还需要上传一张项目封面。
<form class="form-horizontal required-validate" action="#" enctype="multipart/form-data" method="post" onsubmit="return iframeCallback(this, pageAjaxDone)">
- 1
- 1
先看一下form的属性:
- enctype:”multipart/form-data”,表明为文件类型的form保存
- iframeCallback方法,稍候详细介绍,主要是对有文件上传的form表单进行封装。
1、iframeCallback
function iframeCallback(form, callback) {YUNM.debug("带文件上传处理");var $form = $(form), $iframe = $("#callbackframe");var data = $form.data('bootstrapValidator');if (data) {if (!data.isValid()) {return false;}}// 富文本编辑器$("div.summernote", $form).each(function() {var $this = $(this);if (!$this.summernote('isEmpty')) {var editor = "<input type='hidden' name='" + $this.attr("name") + "' value='" + $this.summernote('code') + "' />";$form.append(editor);} else {$.showErr("请填写项目详情");return false;}});if ($iframe.size() == 0) {$iframe = $("<iframe id='callbackframe' name='callbackframe' src='about:blank' style='display:none'></iframe>").appendTo("body");}if (!form.ajax) {$form.append('<input type="hidden" name="ajax" value="1" />');}form.target = "callbackframe";_iframeResponse($iframe[0], callback || YUNM.ajaxDone);
}
function _iframeResponse(iframe, callback) {var $iframe = $(iframe), $document = $(document);$document.trigger("ajaxStart");$iframe.bind("load", function(event) {$iframe.unbind("load");$document.trigger("ajaxStop");if (iframe.src == "javascript:'%3Chtml%3E%3C/html%3E';" || // For// Safariiframe.src == "javascript:'<html></html>';") { // For FF, IEreturn;}var doc = iframe.contentDocument || iframe.document;// fixing Opera 9.26,10.00if (doc.readyState && doc.readyState != 'complete')return;// fixing Opera 9.64if (doc.body && doc.body.innerHTML == "false")return;var response;if (doc.XMLDocument) {// response is a xml document Internet Explorer propertyresponse = doc.XMLDocument;} else if (doc.body) {try {response = $iframe.contents().find("body").text();response = jQuery.parseJSON(response);} catch (e) { // response is html document or plain textresponse = doc.body.innerHTML;}} else {// response is a xml documentresponse = doc;}callback(response);});
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
贴上全部代码以供参考,但是这里我们只讲以下部分:
// 富文本编辑器$("div.summernote", $form).each(function() {var $this = $(this);if (!$this.summernote('isEmpty')) {var editor = "<input type='hidden' name='" + $this.attr("name") + "' value='" + $this.summernote('code') + "' />";$form.append(editor);} else {$.showErr("请填写项目详情");return false;}});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 通过form获取到summernote对象$this 后,通过
!$this.summernote('isEmpty')
来判断用户是否对富文本编辑器有内容上的填写,保证不为空,为空时,就弹出提示信息。 $this.summernote('code')
可获得summernote编辑器的html内容,将其封装到input对象中,name为前文中div提供的name,供后端使用。
这里其他地方就不做多解释了,详细可参照Bootstrap wysiwyg富文本数据如何保存到mysql。
保存到数据库中是什么样子呢?
<p><img src="http://localhost:8080/ymeng/upload/2016033117093076.jpeg" style=""></p><p><br></p><p>你好,有兴趣可以加入到沉默王二的群啊<br></p>
- 1
- 1
页面效果为:
2、新版iframeCallback方法
var $form = $(form), $iframe = $("#callbackframe");YUNM.debug("验证其他简单组件");var data = $form.data('bootstrapValidator');if (data) {if (!data.isValid()) {return false;}
}// 富文本编辑器
$("div.summernote", $form).each(function() {var $this = $(this);if ($this.summernote('isEmpty')) {} else {YUNM.debug($this.summernote('code'));// 使用base64对内容进行编码// 1.解决复制不闭合的html文档,保存后显示错乱的bug// 2.解决文本中特殊字符导致的bugvar editor = "<input type='hidden' name='" + $this.attr("name") + "' value='" + $.base64.btoa($this.summernote('code')) + "' />";$form.append(editor);}
});YUNM.debug("验证通过");
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
比对之前的代码,可以发现代码有两处发生了变化:
- 当summernote为空时,之前没有做在bootstrap的validator中,是因为还没有搞清楚summernote这种非input标签在validator中的使用,下面会做详细说明。
- 对summernote的内容加上了base64编码处理,这会有很多好处,稍候介绍。
3、base64的使用方法
js端我在Bootstrap wysiwyg富文本数据如何保存到mysql这篇文章中做了说明,此处不再说明。
可能会有同学需要JavaScript端的base64编码,而需要在springMVC后端使用base64的解码,那么此处介绍一个jar包(Java Base64.jar),使用方法很简单,下载好jar包后,就可以使用如下方法解码:
import it.sauronsoftware.base64.Base64;deal.setDescription(StringEscapeUtils.escapeHtml(Base64.decode(description, "utf-8")));
- 1
- 2
- 3
- 1
- 2
- 3
- 首先,base64的import如上,来自于javabase64.jar包。
- decode的编码前端js使用的utf-8,此处自然也用utf-8。
- 至于StringEscapeUtils类,也是一个非常实用的工具类,有兴趣的可详细关注一下(主要可以对html等等特殊标签进行转义)。
4、summernote加入到bootstrap validator中
<div class="form-group"><label for="" class="col-md-1 control-label">项目详情</label><div class="col-md-10"><div class="summernote" name="description" data-bv-excluded="false" data-bv-notempty placeholder="请对项目进行详细的描述,使更多的人了解你的云梦"action="${ctx}/file">${deal.description}</div></div>
</div>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 注意data-bv-excluded=”false”(由于summernote使用了div作为form表单的呈现形式,非一般的input标签,所以此处要将该name=”description”的field标识为非excluded,默认的validator是不对“[‘:disabled’, ‘:hidden’, ‘:not(:visible)’]”三种标签做处理的,而summernote会默认作为disabled的一种,那么设置上data-bv-excluded=”false” 后,validator将会对summernote做非空的判断)、data-bv-notempty属性。
- 当然有了上述两个属性后,并不能保证validator的有效性,那么接下来,请继续看。
onChange : function(contents, $editable) {if ($this.parents().length > 0) {var $form = $this.parents().find("form.required-validate", $p);if ($form.length > 0) {var data = $form.data('bootstrapValidator');YUNM.debug($this.summernote('isEmpty'));if ($this.summernote('isEmpty')) {data.updateStatus($this.attr("name"), 'INVALID');} else {data.updateStatus($this.attr("name"), 'VALID');}}}},
onInit : function() {if ($this.parents().length > 0) {var $form = $this.parents().find("form.required-validate", $p);if ($form.length > 0) {var data = $form.data('bootstrapValidator');if (!$this.summernote('isEmpty')) {data.updateStatus($this.attr("name"), 'VALID');}}}
},
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
在summernote的callbacks中加入onChange 、onInit,当文本域发生变化、初始化时,对summernote在form中的验证字段进行状态的更新,validator中使用updateStatus方法。
/*** Update all validating results of field** @param {String|jQuery} field The field name or field element* @param {String} status The status. Can be 'NOT_VALIDATED', 'VALIDATING', 'INVALID' or 'VALID'* @param {String} [validatorName] The validator name. If null, the method updates validity result for all validators* @returns {BootstrapValidator}*/updateStatus: function(field, status, validatorName) {
OK,等补上以上两个内容后,整个summernote就完整了。
Bootstrap 可视化编辑器summernote相关推荐
- 3款BootStrap可视化工具
25学堂看到最近很多朋友在学习Bootstrap前端主题框架.顾让25学堂的小编给大家找来了3款适合Bootstrap初学者使用的可视化工具.希望对大家有所帮助. 第一款Bootstrap的可视化制作 ...
- Bootstrap 可视化HTML编辑器,summernote
Bootstrap 可视化HTML编辑器之summernote,用其官网上的介绍就是"Super Simple WYSIWYG editor",不过在我看来,与bootstrap中 ...
- BootStrap富文本编辑器Summernote
BootStrap富文本编辑器Summernote https://blog.csdn.net/qq_40205116/article/details/94287053
- 超棒的 15 款 Bootstrap UI 编辑器(有几款可视化可视化)
(https://www.oschina.net/news/48134/the-best-bootstrap-ui-editors) 自从 2011 年 Mark Otto 和 Jacob Th ...
- Bootstrap在线编辑器简单分享
Bootstrap 已经使响应式网站开发变得简单很多. 但是如果你不必手动写全部代码,事情会如何呢? 如果你可以自由地选择你想要使用的Bootstrap 组件.并可以把它们拖拽到画布中,事情会如何呢? ...
- 富文本编辑器summernote
版权声明 本文原创作者:谷哥的小弟 作者博客地址:http://blog.csdn.net/lfdfhl summernote概述 summernote是一款轻巧.友好.易于集成.使用方便的富文本编辑 ...
- 基于jQuery的富文本编辑器summernote插件的使用教程
基于jQuery的富文本编辑器summernote插件的使用教程 基于jQuery的富文本编辑器summernote插件的使用教程 一:最近项目中遇到使用富文本编辑器的功能, JQuery的富文本编辑 ...
- 主流的Bootstrap 在线编辑器
尽管 Bootstrap 使得响应式网站开发变得简单很多,但是如果你不必手动写全部代码,事情会如何呢? 如果你可以自由地选择你想要使用的Bootstrap 组件.并可以把它们拖拽到画布中,事情会如何呢 ...
- android窗口泄漏,isInEditMode解决可视化编辑器无法识别自定义控件的问题
android窗口泄漏,isInEditMode解决可视化编辑器无法识别自定义控件的问题 参考文章: (1)android窗口泄漏,isInEditMode解决可视化编辑器无法识别自定义控件的问题 ( ...
- Bootstrap UI 编辑器
http://www.runoob.com/bootstrap/bootstrap-ui-editor.html Bootstrap UI 编辑器 以下是 15 款最好的 Bootstrap 编辑器或 ...
最新文章
- [毕业生的商业软件开发之路]C#类型样式
- 搭建 数字证书_CA认证介绍及搭建过程
- cocos2d-x解决中文乱码问题的几种办法
- 家长必看 父母须知孩子社交圈九要点(组图)
- 【UI/UX】GUI设计指南
- Applese 的 QQ 群
- 基于jquery的从一个页面跳转到另一个页面的指定位置的实现代码
- cmd命令行怎样运行python_在CMD命令行中运行python脚本的方法
- 灰度董事总经理:BTC突破2万美元并不令人惊讶
- 程序员应该具备哪些素质
- Newton迭代法求无约束目标函数极小值matlab实现
- flash做动画教程(基础篇)
- 傲梅分区助手克隆Linux硬盘,傲梅分区助手复制磁盘或克隆磁盘到另外磁盘
- java coap_CoAP协议-以Californium(Java)为例的CoAP初步实现
- python求平方值,python – 字典的平方值
- 云存储解决方案-阿里云OSS
- C博客作业00--我的第一篇博客
- 第一阶段:Python开发基础 day18 模块的使用(三)
- HP LaserJet Pro 300 彩色打印机 M351a - 每次重启电脑都提示安装驱动
- 地理信息系统明年将服务全运会