Rumah > Artikel > hujung hadapan web > Kemahiran editor teks kaya Bootstrap yang sangat cantik summernote_javascript
Summernote ialah editor What You See Is What You Get (WYSIWYG) yang ringkas, fleksibel, dibina pada jQuery dan Bootstrap. Summernote menyokong kekunci pintasan untuk semua operasi utama dan mempunyai API berkuasa yang menyediakan sejumlah besar pilihan penyesuaian untuk reka bentuk (lebar, tinggi, item aktif, dll.) dan kefungsian. Untuk bahasa atau rangka kerja skrip utama (PHP, Ruby, Django, NodeJS), projek ini menyediakan contoh penyepaduan.
Bootstrap summernote, seperti yang diterangkan di laman web rasminya, ialah "Editor Super Simple WYSIWYG", tetapi pada pendapat saya, ia lebih ringkas, lebih cantik dan lebih mudah digunakan daripada "bootstrap-wysiwyg" yang disediakan di laman web rasmi China bootstrap !
Walaupun saya telah mencuba menggunakan bootstrap-wysiwyg sebelum ini, anda boleh merujuk kepada cara menyimpan data teks kaya Bootstrap wysiwyg ke mysql, tetapi pengalaman di belakang memberitahu saya bahawa summernote sememangnya editor teks kaya yang lebih baik tunjuk pada karyanya. ! ! ! !
Selepas seharian meneroka, saya telah menguasai summernote, jadi untuk memberikan kemudahan kepada lebih ramai peminat front-end, saya akan mengambil banyak usaha untuk memperkenalkan summernote, yang merupakan bonus super.
1. Muat turun API dan kod sumber rasmi
Jika anda ingin melakukan tugas anda dengan baik, anda mesti mengasah alatan anda terlebih dahulu. Tugas pertama ialah mendapatkan kod sumber summernote dan memberitahu semua orang tentang API rasmi yang sepadan!
Tapak web rasmi (demo dan api)
Muat turun kod sumber Github, sila beri perhatian untuk memuat turun versi pembangunan
2. Rendering
Memaparkan 1
Rendering 2
Rendering 3
3. Kandungan kuliah
Arah umum ialah tiga kandungan berikut:
Reka letak halaman Summernote (pengenalan sumber, parameter awal)
Summernote memuat naik imej daripada kaedah tempatan (kaedah onImageUpload bahagian hadapan, penjimatan fail springMVC hujung belakang)
Penyerahan data borang di mana summernote berada
①, susun atur halaman nota musim panas
<!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>
teg HTML5 diperlukan, sila ambil perhatian bahawa ia mestilah bukan Doktype ini, jika tidak, komponen summernote akan dipaparkan secara pelik dan saiz serta susun atur butang akan menjadi tidak konsisten. Gambar tidak akan ditunjukkan di sini, tetapi pastikan anda memberi perhatian!
Nombor versi bootstrap adalah sebaik-baiknya v3.3.5
1. Div reka letak
<div class="summernote" name="description" placeholder="请对项目进行详细的描述,使更多的人了解你的" action="${ctx}/file">${deal.description}</div>
Saya percaya anda juga telah melihat tiga nama atribut, pemegang tempat dan tindakan yang saya tambahkan pada div, jadi mari perkenalkan fungsi tiga atribut secara terperinci:
nama memberikan nama atribut model data apabila data summernote disimpan untuk bentuk luar Ia mempunyai fungsi yang sama dengan atribut nama teg input Ia akan diperkenalkan secara terperinci kemudian apabila borang diserahkan.
Pemegang tempat adalah sangat mudah Ia memberikan penerangan teks tentang keadaan awal untuk nota musim panas, ia juga memerlukan pemprosesan seterusnya.
tindakan menyediakan alamat penerima bahagian belakang untuk muat naik imej Ia akan digunakan semula apabila memperkenalkan muat naik imej padaImageUpload kemudian.
Di samping itu, ${deal.description} sebenarnya tidak perlu diberi perhatian terlalu banyak. Ia selaras dengan penggunaan tugasan textarea, iaitu, ia hanya memaparkan kandungan yang disimpan.
2. Permulaan nota musim panas
$('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就不会自动调整高度,并且前文中“Kemahiran editor teks kaya Bootstrap yang sangat cantik summernote_javascript”中标出的红色区域会不贴着图片,而溢出到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="Kemahiran editor teks kaya Bootstrap yang sangat cantik summernote_javascript" ></p><p><br></p><p>你好,有兴趣可以加入到沉默王二的群啊<br></p>
页面效果为:
好了,好了,终于写完了,没想到写的这么累,如果你有什么新鲜的玩意,也可以联系我啊,欢迎你的指导!
关于Bootstrap 富文本编辑器summernote小编就给大家介绍到这里,希望对大家有所帮助!有不同见解欢迎提出宝贵意见,共同学习进步!