Home  >  Article  >  Web Front-end  >  超漂亮的Bootstrap 富文本编辑器summernote_javascript技巧

超漂亮的Bootstrap 富文本编辑器summernote_javascript技巧

WBOY
WBOYOriginal
2016-05-16 15:06:362500browse

 Summernote 是一个简单,灵活,所见即所得(WYSIWYG)的编辑器,基于 jQuery 和 Bootstrap 构建。Summernote 所有主要的操作都支持快捷键,有一个功能强大的 API,它提供了大量的自定义选项的设计(宽,高,有效的项目等等)和功能。对于主要的脚本语言或框架(PHP,Ruby,Django,NodeJS),该项目有提供了集成示例。

Bootstrap 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

效果图1

效果图2

效果图2

效果图3

效果图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 dialogs
dialogsInBody : true,// Dialogs can be placed in body, not in
// summernote.
disableDragAndDrop : false,// default false You can disable drag
// and drop
callbacks : {
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>

html5的标记是必须的,注意千万不能是这种doctype,否则summernote的组件显示怪怪的,按钮的大小布局不一致,这里就不再上图了,但是千万注意!
bootstrap 的版本号最好为v3.3.5

1、布局div

<div class="summernote" name="description" placeholder="请对项目进行详细的描述,使更多的人了解你的" action="${ctx}/file">${deal.description}</div>

相信你也看到了我为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 dialogs
dialogsInBody : true,// Dialogs can be placed in body, not in
// summernote.
disableDragAndDrop : false,// default false You can disable drag
// and drop
});
});

使用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>

以上资源来自于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);
});

那么此时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上的action
cache : 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
});
});
}
}

注释当中加的很详细,这里把其他关联的代码一并贴出,仅供参照。

debug : function(msg) {
if (this._set.debug) {
if (typeof (console) != "undefined")
console.log(msg);
else
alert(msg);
}
},
jsonEval : function(data) {
try {
if ($.type(data) == 'string')
return eval('(' + data + ')');
else
return 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);
}
},

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>

这里就不做过多介绍了,可参照我之前写的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);
@Autowired
private 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, "文件上传失败");
}
}
}

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("获取上传文件...");
// 转换为文件类型的request
MultipartHttpServletRequest 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) {
}
}

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());
}
}
}

后端文件保存方法也非常简单,懂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)">

先看一下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
// Safari
iframe.src == "javascript:'<html></html>';") { // For FF, IE
return;
}
var doc = iframe.contentDocument || iframe.document;
// fixing Opera 9.26,10.00
if (doc.readyState && doc.readyState != 'complete')
return;
// fixing Opera 9.64
if (doc.body && doc.body.innerHTML == "false")
return;
var response;
if (doc.XMLDocument) {
// response is a xml document Internet Explorer property
response = 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 text
response = doc.body.innerHTML;
}
} else {
// response is a xml document
response = doc;
}
callback(response);
});
}

贴上全部代码以供参考,但是这里我们只讲以下部分:

// 富文本编辑器
$("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;
}
});

通过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"  alt="超漂亮的Bootstrap 富文本编辑器summernote_javascript技巧" ></p><p><br></p><p>你好,有兴趣可以加入到沉默王二的群啊<br></p>

页面效果为:

这里写图片描述

好了,好了,终于写完了,没想到写的这么累,如果你有什么新鲜的玩意,也可以联系我啊,欢迎你的指导!

关于Bootstrap 富文本编辑器summernote小编就给大家介绍到这里,希望对大家有所帮助!有不同见解欢迎提出宝贵意见,共同学习进步!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn