打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Bootstrap summernote,超级漂亮的富文本编辑器

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

效果图2

效果图3

三、开讲内容

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

  1. summernote的页面布局(资源引入、初始参数)
  2. summernote从本地上传图片方法(前端onImageUpload方法、后端springMVC文件保存)
  3. 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • <!DOCTYPE html>html5的标记是必须的,注意千万不能是<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">这种doctype,否则summernote的组件显示怪怪的,按钮的大小布局不一致,这里就不再上图了,但是千万注意!
  • bootstrap 的版本号最好为v3.3.5

1、布局div

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

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

  1. name,为外层form表单提供summernote数据保存时的数据模型的属性名,和input标签的name属性作用一致,稍候在form提交的时候具体介绍。
  2. placeholder,很直白,为summernote提供初始状态的文本描述,当然还需要后续加工,div显然是不支持placeholder属性的。
  3. 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        });    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

使用jQuery获取到页面上的summernote,对其进行初始化,我们来详细介绍列出参数的用法(先不介绍图片上传的onImageUpload 方法)。

  1. lang ,指定语言为中文简体
  2. placeholder ,summernote初始化显示的内容。
  3. minHeight,最小高度为300,注意这里没有使用height,是有原因的,这里稍作解释,就不上图了。当使用height指定高度后,假如上传比height高的图片,summernote就不会自动调整高度,并且前文中“效果图3”中标出的红色区域会不贴着图片,而溢出到summernote外部。
  4. dialogsFade,增加summernote上弹出窗口滑进滑出的动态效果。
  5. dialogsInBody,这个属性也很关键,默认为false,字面上的意思是summernote的弹出框是否在body中(in嘛),设置为false时,dialog的式样会继承其上一级外部(如上文中的form-horizontal)容器式样,那么显示的效果就很别扭,这里也不再上图;那么设置为true时,就不会继承上一级外部div的属性啦,从属于body嘛。
  6. 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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

以上资源来自于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);});
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

那么此时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            });        });    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

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

    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);        }    },
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

这里就不做过多介绍了,可参照我之前写的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, "文件上传失败");        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
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;@Servicepublic 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) {    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
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());        }    }}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81

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

先看一下form的属性:

  1. enctype:”multipart/form-data”,表明为文件类型的form保存
  2. 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);    });}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80

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

// 富文本编辑器    $("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;        }    });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 通过form获取到summernote对象$this 后,通过!$this.summernote('isEmpty')来判断用户是否对富文本编辑器有内容上的填写,保证不为空,为空时,就弹出提示信息。
  • $this.summernote('code')可获得summernote编辑器的html内容,将其封装到input对象中,name为前文中div提供的name,供后端使用。

这里其他地方就不做多解释了,详细可参照Bootstrap wysiwyg富文本数据如何保存到mysql

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

<p><img src="//localhost:8080/ymeng/upload/2016033117093076.jpeg" style=""></p><p><br></p><p>你好,有兴趣可以加入到沉默王二的群啊<br></p>
  • 1
  • 1

页面效果为:


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 = "<input type='hidden' name='" + $this.attr("name") + "' value='" + $.base64.btoa($this.summernote('code')) + "' />";        $form.append(editor);    }});YUNM.debug("验证通过");
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

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

  1. 当summernote为空时,之前没有做在bootstrap的validator中,是因为还没有搞清楚summernote这种非input标签在validator中的使用,下面会做详细说明。
  2. 对summernote的内容加上了base64编码处理,这会有很多好处,稍候介绍。

3、base64的使用方法

js端我在Bootstrap wysiwyg富文本数据如何保存到mysql这篇文章中做了说明,此处不再说明。

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

import it.sauronsoftware.base64.Base64;deal.setDescription(StringEscapeUtils.escapeHtml(Base64.decode(description, "utf-8")));
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
  1. 首先,base64的import如上,来自于javabase64.jar包。
  2. decode的编码前端js使用的utf-8,此处自然也用utf-8。
  3. 至于StringEscapeUtils类,也是一个非常实用的工具类,有兴趣的可详细关注一下(主要可以对html等等特殊标签进行转义)。

4、summernote加入到bootstrap validator中

<div class="form-group">    <label for="" class="col-md-1 control-label">项目详情</label>    <div class="col-md-10">        <div class="summernote" name="description" data-bv-excluded="false" data-bv-notempty placeholder="请对项目进行详细的描述,使更多的人了解你的云梦"            action="${ctx}/file">${deal.description}</div>    </div></div>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  1. 注意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属性。
  2. 当然有了上述两个属性后,并不能保证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');            }        }    }},
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

在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) {
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

OK,等补上以上两个内容后,整个summernote就完整了。


感谢您阅读【沉默王二的博客】,如果王二的博客给您带来一丝帮助或感动,我(也就是王二)将不甚荣幸。
如果您碰巧喜欢,可以留言或者私信我,这将是我鼓捣更多优秀文章的最强动力。

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
Django使用summernote富文本编辑器,完整前后端
Jquery插件之ajaxForm
Customize summernote
bootstrapvalidator使用时需要注意的事项
JS中的JSON对象
html form表单提交数据并后台获取
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服