1、布局div

<div class="summernote" name="description" placeholder="請對項目進行詳細的描述,使更多的人了解你的" action="${ctx}/file">${deal.description}</div>

相信你也看到了我為div加上的三個屬性name、placeholder、action,那么我們來詳細介紹一下三個屬性的作用:

  1. name,為外層form表單提供summernote數(shù)據(jù)保存時的數(shù)據(jù)模型的屬性名,和input標簽的name屬性作用一致,稍候在form提交的時候具體介紹。
  2. placeholder,很直白,為summernote提供初始狀態(tài)的文本描述,當然還需要后續(xù)加工,div顯然是不支持placeholder屬性的。
  3. action,為圖片上傳提供后端接收地址,稍候在介紹圖片上傳onImageUpload會再次用到。

另外${deal.description}其實你不需要太多關(guān)注,和textarea的賦值的用法一致,就是單純的顯示保存后的內(nèi)容。

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,對其進行初始化,我們來詳細介紹列出參數(shù)的用法(先不介紹圖片上傳的onImageUpload 方法)。

  1. lang ,指定語言為中文簡體
  2. placeholder ,summernote初始化顯示的內(nèi)容。
  3. minHeight,最小高度為300,注意這里沒有使用height,是有原因的,這里稍作解釋,就不上圖了。當使用height指定高度后,假如上傳比height高的圖片,summernote就不會自動調(diào)整高度,并且前文中“效果圖3”中標出的紅色區(qū)域會不貼著圖片,而溢出到summernote外部。
  4. dialogsFade,增加summernote上彈出窗口滑進滑出的動態(tài)效果。
  5. dialogsInBody,這個屬性也很關(guān)鍵,默認為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已經(jīng)不支持這種onImageUpload寫法,那么如今的寫法是什么樣子呢?參照summernote的官網(wǎng)例子。

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的參數(shù)為files,summernote支持選擇多張圖片
    onImageUpload : function(files) {
        var $files = $(files);

        // 通過each方法遍歷每一個file
        $files.each(function() {
            var file = this;
            // FormData,新的form表單封裝,具體可百度,但其實用法很簡單,如下
            var data = new FormData();

            // 將文件加入到file中,后端可獲得到參數(shù)名為“file”
            data.append("file", file);

            // ajax上傳
            $.ajax({
                data : data,
                type : "POST",
                url : url,// div上的action
                cache : false,
                contentType : false,
                processData : false,

                // 成功時調(diào)用方法,后端返回json數(shù)據(jù)
                success : function(response) {
                    // 封裝的eval方法,可百度
                    var json = YUNM.jsonEval(response);

                    // 控制臺輸出返回數(shù)據(jù)
                    YUNM.debug(json);

                    // 封裝方法,主要是顯示錯誤提示信息
                    YUNM.ajaxDone(json);

                    // 狀態(tài)ok時
                    if (json[YUNM.keys.statusCode] == YUNM.statusCode.ok) {
                        // 文件不為空
                        if (json[YUNM.keys.result]) {

                            // 獲取后臺數(shù)據(jù)保存的圖片完整路徑
                            var imageUrl = json[YUNM.keys.result].completeSavePath;

                            // 插入到summernote
                            $this.summernote('insertImage', imageUrl, function($image) {
                                // todo,后續(xù)可以對image對象增加新的css式樣等等,這里默認
                            });
                        }
                    }

                },
                // ajax請求失敗時處理
                error : YUNM.ajaxError
            });
        });
    }
}

注釋當中加的很詳細,這里把其他關(guān)聯(lián)的代碼一并貼出,僅供參照。

    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轉(zhuǎn)換的時候直接將string格式的日期轉(zhuǎn)換為date類型 -->
                <bean class="com.honzh.common.plugin.StringToDateConverter" />
<!--                為type為file類型的數(shù)據(jù)模型增加轉(zhuǎn)換器 -->
                <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("獲取上傳文件...");

        // 轉(zhuǎn)換為文件類型的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);

                // 調(diào)用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的同學,你可以再找找方法。


辛苦的介紹完前兩節(jié)后,我們來一個動態(tài)圖看一下效果吧!
這里寫圖片描述

③. summernote所在form表單的數(shù)據(jù)提交

這里,我們再回顧一下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("請?zhí)顚戫椖吭斍?);
            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("請?zhí)顚戫椖吭斍?);
            return false;
        }

    });

這里其他地方就不做多解釋了,詳細可參照Bootstrap wysiwyg富文本數(shù)據(jù)如何保存到mysql

保存到數(shù)據(jù)庫中是什么樣子呢?

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

頁面效果為:

這里寫圖片描述


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對內(nèi)容進行編碼
        // 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("驗證通過");

比對之前的代碼,可以發(fā)現(xiàn)代碼有兩處發(fā)生了變化:

  1. 當summernote為空時,之前沒有做在bootstrap的validator中,是因為還沒有搞清楚summernote這種非input標簽在validator中的使用,下面會做詳細說明。
  2. 對summernote的內(nèi)容加上了base64編碼處理,這會有很多好處,稍候介紹。

3、base64的使用方法

js端我在Bootstrap wysiwyg富文本數(shù)據(jù)如何保存到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. 首先,base64的import如上,來自于javabase64.jar包。
  2. decode的編碼前端js使用的utf-8,此處自然也用utf-8。
  3. 至于StringEscapeUtils類,也是一個非常實用的工具類,有興趣的可詳細關(guān)注一下(主要可以對html等等特殊標簽進行轉(zhuǎn)義)。

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. 注意data-bv-excluded=”false”(由于summernote使用了div作為form表單的呈現(xiàn)形式,非一般的input標簽,所以此處要將該name=”description”的field標識為非excluded,默認的validator是不對“[‘:disabled’, ‘:hidden’, ‘:not(:visible)’]”三種標簽做處理的,而summernote會默認作為disabled的一種,那么設置上data-bv-excluded=”false” 后,validator將會對summernote做非空的判斷)、data-bv-notempty屬性。
  2. 當然有了上述兩個屬性后,并不能保證validator的有效性,那么接下來,請繼續(xù)看。
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,當文本域發(fā)生變化、初始化時,對summernote在form中的驗證字段進行狀態(tài)的更新,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,等補上以上兩個內(nèi)容后,整個summernote就完整了。


感謝您閱讀【沉默王二的博客】,如果王二的博客給您帶來一絲幫助或感動,我(也就是王二)將不甚榮幸。
如果您碰巧喜歡,可以留言或者私信我,這將是我鼓搗更多優(yōu)秀文章的最強動力。

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    來自: WindySky > 《bootstrap》

    舉報/認領(lǐng)

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多
    喜歡該文的人也喜歡 更多
    熱門閱讀 換一換