超漂亮的Bootstrap 富文本编辑器summernote

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的页面布局

rush:js;"> <Meta charset="UTF-8"> summernote - bs3fa4
required-validate" action="#" enctype="multipart/form-data" method="post" onsubmit="return iframeCallback(this,pageAjaxDone)">
支持jpg、jpeg、png、gif格式,大小不超过2.0M

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

1、布局div

rush:js;">

相信你也看到了我为div加上的三个属性name、placeholder、action,那么我们来详细介绍一下三个属性的作用:

name,为外层form表单提供summernote数据保存时的数据模型的属性名,和input标签的name属性作用一致,稍候在form提交的时候具体介绍。

placeholder,很直白,为summernote提供初始状态的文本描述,当然还需要后续加工,div显然是不支持placeholder属性的。

action,为图片上传提供后端接收地址,稍候在介绍图片上传onImageUpload会再次用到。 另外${deal.description}其实你不需要太多关注,和textarea的赋值的用法一致,就是单纯的显示保存后的内容

2、summernote初始化

rush:js;"> $('div.summernote').each(function() { var $this = $(this); var placeholder = $this.attr("placeholder") || ''; var url = $this.attr("action") || ''; $this.summernote({ lang : 'zh-CN',// 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方法怎么写?”,度娘大多会为你找到如下回答:

rush:js;"> $(\'.summernote\').summernote({ height:300,onImageUpload: function(files,editor,welEditable) { sendFile(files[0],welEditable); } }); }); function sendFile(file,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); } }); }

以上资源来自于stackoverflow。

但其实呢,summernote-develop版本的summernote已经不支持这种onImageUpload写法,那么如今的写法是什么样子呢?参照summernote的官网例子。

onImageUpload

rush:js;"> 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):

rush:js;"> 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,// div上的action cache : 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',function($image) { // todo,后续可以对image对象增加新的css式样等等,这里认 }); } } },// ajax请求失败时处理 error : YUNM.ajaxError }); }); } }

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

rush:js;"> 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("
" + xhr.responseText + "
"); } else { $.showErr("
Http status: " + xhr.status + " " + xhr.statusText + "
" + "
ajaxOptions: " + ajaxOptions + "
" + "
thrownError: " + thrownError + "
"); } },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增加文件的配置

rush:js;"> factorybean">

这里就不做过多介绍了,可参照我之前写的SpringMVC之context-dispatcher.xml,了解基本的控制器

2.2、FileController.java

rush:js;"> 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

rush:js;"> 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 fileMap = multipartRequest.getFileMap(); Iterator 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

rush:js;"> 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还需要上传一张项目封面。

rush:js;">
required-validate" action="#" enctype="multipart/form-data" method="post" onsubmit="return iframeCallback(this,pageAjaxDone)">

先看一下form的属性

enctype:”multipart/form-data”,表明为文件类型的form保存

iframeCallback方法,稍候详细介绍,主要是对有文件上传的form表单进行封装。

1、iframeCallback

rush:js;"> 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 = ""; $form.append(editor); } else { $.showErr("请填写项目详情"); return false; } }); if ($iframe.size() == 0) { $iframe = $("").appendTo("body"); } if (!form.ajax) { $form.append(''); } 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:'';") { // 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); }); }

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

rush:js;"> // 富文本编辑器 $("div.summernote",$form).each(function() { var $this = $(this); if (!$this.summernote('isEmpty')) { var editor = ""; $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

保存到数据库中是什么样子呢?

rush:js;">


你好,有兴趣可以加入到沉王二的群啊

页面效果为:

这里写图片描述

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

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

相关文章

Bootstrip HTML 查询搜索常用格式模版 &lt;form class=&...
如何在按钮上加红色数字 您可以使用Bootstrap的badge组件来在...
要让两个按钮左右排列,你可以使用 Bootstrap 的网格系统将它...
是的,可以将status设置为布尔类型,这样可以在前端使用复选...
前端工程师一般用的是Bootstrap的框架而不是样式,样式一般自...
起步导入:<linkrel="stylesheet"href="b...