软件世界网 购物 网址 三丰软件 | 小说 美女秀 图库大全 游戏 笑话 | 下载 开发知识库 新闻 开发 图片素材
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
移动开发 架构设计 编程语言 Web前端 互联网
开发杂谈 系统运维 研发管理 数据库 云计算 Android开发资料
  软件世界网 -> Web前端 -> Bootstrapsummernote,超级漂亮的富文本编辑器 -> 正文阅读
Web前端 最新文章
10分钟
SSM框架SSM项目源码SSM源码下载java框架整合
javascript入门
JavaScript常用对象Array(2)
8.Smarty3:模版中的内置函数
表单脚本
iTextSharp5.0页眉页脚及Asp.net预览的实现
MVC基础学习—理论篇
JavaScript
http协议中get与post区别详解

[Web前端]Bootstrapsummernote,超级漂亮的富文本编辑器

  2016-04-01 16:49:15

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

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

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

以上资源来自于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的属性:
  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);
    });
}

贴上全部代码以供参考,但是这里我们只讲以下部分:
// 富文本编辑器
    $("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" style=""></p><p><br></p><p>你好,有兴趣可以加入到沉默王二的群啊<br></p>

页面效果为:



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


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

上一篇文章      下一篇文章      查看所有文章
2016-04-01 16:48:17  
360图书馆 论文大全 母婴/育儿 软件开发资料 网页快照 文字转语音 购物精选 软件 美食菜谱 新闻中心 电影下载 小游戏 Chinese Culture
生肖星座解梦 三沣玩客 拍拍 视频 开发 Android开发 站长 古典小说 网文精选 搜图网 天下美图 中国文化英文 多播视频 装修知识库
2017-1-22 16:07:06
多播视频美女直播
↓电视,电影,美女直播,迅雷资源↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  软件世界网 --