Home >Web Front-end >JS Tutorial >Super beautiful Bootstrap rich text editor summernote_javascript skills

Super beautiful Bootstrap rich text editor summernote_javascript skills

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

 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

Super beautiful Bootstrap rich text editor summernote_javascript skills

效果圖2

Super beautiful Bootstrap rich text editor summernote_javascript skills

效果圖3

Super beautiful Bootstrap rich text editor summernote_javascript skills

三、開講內容

大的方向為以下三個:

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就不会自动调整高度,并且前文中“Super beautiful Bootstrap rich text editor summernote_javascript skills”中标出的红色区域会不贴着图片,而溢出到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的同学,你可以再找找方法。

辛苦的介绍完前两节后,我们来一个动态图看一下效果吧!

Super beautiful Bootstrap rich text editor summernote_javascript skills

③. 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="Super beautiful Bootstrap rich text editor summernote_javascript skills" ></p><p><br></p><p>你好,有兴趣可以加入到沉默王二的群啊<br></p>

页面效果为:

Super beautiful Bootstrap rich text editor summernote_javascript skills

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

关于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