img summernote 加类_Bootstrap 可视化HTML编辑器,summernote

Bootstrap 可视化HTML编辑器之summernote,用其官网上的介绍就是“Super Simple WYSIWYG editor”,不过在我看来,与bootstrap中文官网上提供的“bootstrap-wysiwyg”要更simple,更漂亮,更好用!

虽然我之前尝试过使用bootstrap-wysiwyg,可参照Bootstrap wysiwyg富文本数据如何保存到mysql,但事后诸葛亮的经验告诉我,summernote绝对是更佳的富文本编辑器,这里对其工作team点三十二个赞!!!!!

经过一天时间的探索,对summernote有所掌握,那么为了更广大前端爱好者提供便利,我将费劲一番心血来介绍一下summernote,超级福利啊。

一、官方API和源码下载

工欲善其事必先利其器,首先把summernote的源码拿到以及对应官方API告诉大家是首个任务!

二、效果图

效果图1

效果图2

效果图3

三、开讲内容

大的方向为以下三个内容:

summernote的页面布局(资源引入、初始参数)

summernote从本地上传图片方法(前端onImageUpload方法、后端springMVC文件保存)

summernote所在form表单的数据提交

①、summernote的页面布局

summernote - bs3fa4

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

});

});

}

}

});

});

项目封面

支持jpg、jpeg、png、gif格式,大小不超过2.0M

项目详情

${deal.description}

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

bootstrap 的版本号最好为v3.3.5

1、布局div

${deal.description}

相信你也看到了我为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);

}

});

}

以上资源来自于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("

" + 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增加文件的配置

class="org.springframework.web.multipart.commons.CommonsMultipartResolver" p:defaultEncoding="UTF-8">

class="org.springframework.format.support.FormattingConversionServiceFactoryBean">

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

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的属性:

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 = "";

$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);

});

}

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

// 富文本编辑器

$("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,供后端使用。

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

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

页面效果为:

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.解决文本中特殊字符导致的bug

var editor = "";

$form.append(editor);

}

});

YUNM.debug("验证通过");

比对之前的代码,可以发现代码有两处发生了变化:

当summernote为空时,之前没有做在bootstrap的validator中,是因为还没有搞清楚summernote这种非input标签在validator中的使用,下面会做详细说明。

对summernote的内容加上了base64编码处理,这会有很多好处,稍候介绍。

3、base64的使用方法

可能会有同学需要javascript端的base64编码,而需要在springMVC后端使用base64的解码,那么此处介绍一个jar包(Java Base64.jar),使用方法很简单,下载好jar包后,就可以使用如下方法解码:

import it.sauronsoftware.base64.Base64;

deal.setDescription(StringEscapeUtils.escapeHtml(Base64.decode(description, "utf-8")));

首先,base64的import如上,来自于javabase64.jar包。

decode的编码前端js使用的utf-8,此处自然也用utf-8。

至于StringEscapeUtils类,也是一个非常实用的工具类,有兴趣的可详细关注一下(主要可以对html等等特殊标签进行转义)。

4、summernote加入到bootstrap validator中

项目详情

action="${ctx}/file">${deal.description}

注意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');

}

}

}

},

在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就完整了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值