在本文完成下挑战书的功能,其中里面也涉及到富文本编辑器的使用

1、生成challenge数据表

  在D:\medical\war\etc\db.txt文本中增加数据表challenge脚本,然后通过navicat工具把数据表在mysql中生成

/*创建挑战书记录表*/
CREATE TABLE CHALLENGE(challengeId int PRIMARY KEY NOT NULL, userId VARCHAR(20), title VARCHAR(128), depId int, prescript TEXT, challengers VARCHAR(256))
ENGINE=InnoDB DEFAULT CHARSET=UTF8

2、challenge数据表hibernate配制

  在D:\medical\war\etc\mapping目录下生成challenge.hbm.xml文件,里面填写如下内容

<?xml version="1.0" encoding="utf-8"?>  
<!DOCTYPE hibernate-mapping PUBLIC  
        "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
        "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
  
<hibernate-mapping package="com.medical.server.dao">
    <class name="ChallengeDAO" table="CHALLENGE">
    	<id name="challengeId" column="challengeId" type="int">
    		<generator class="increment"></generator>
    	</id>
        <property name="userId" column="userId" />
        <property name="depId" column="depId" />
        <property name="title" column="title" />
        <property name="prescript" column="prescript" />
        <property name="challengers" column="challengers" />
    </class>
</hibernate-mapping>


3、定义challenge.hbm.xml对应的POJO类

package com.medical.server.dao;

/**
 * 斗医系统发布挑战书处理类
 * 
 * @author qingkechina 2014-08-18
 */
public class ChallengeDAO
{
    /**
     * 挑战ID
     */
    private int challengeId = 0;
    
    /**
     * 挑战人
     */
    private String userId = null;
    
    /**
     * 科室ID
     */
    private int depId = 0;
    
    /**
     * 挑战标题
     */
    private String title = null;
    
    /**
     * 挑战内容
     */
    private String prescript = null;
    
    /**
     * 被挑战人
     */
    private String challengers = null;
    
    public int getChallengeId()
    {
        return challengeId;
    }
    
    public void setChallengeId(int challengeId)
    {
        this.challengeId = challengeId;
    }
    
    public String getUserId()
    {
        return userId;
    }
    
    public void setUserId(String userId)
    {
        this.userId = userId;
    }
    
    public int getDepId()
    {
        return depId;
    }
    
    public void setDepId(int depId)
    {
        this.depId = depId;
    }
    
    public String getTitle()
    {
        return title;
    }
    
    public void setTitle(String title)
    {
        this.title = title;
    }
    
    public String getPrescript()
    {
        return prescript;
    }
    
    public void setPrescript(String prescript)
    {
        this.prescript = prescript;
    }
    
    public String getChallengers()
    {
        return challengers;
    }
    
    public void setChallengers(String challengers)
    {
        this.challengers = challengers;
    }
}


4、由于涉及到对数据表challenge数据的读取与写入操作,所以定义一个ChallengeUtil类对POJO进行操作

package com.medical.server.util;

import org.hibernate.Session;
import org.hibernate.Transaction;

import com.medical.frame.util.FrameDBUtil;
import com.medical.server.dao.ChallengeDAO;

/**
 * 斗医系统服务端挑战书工具类
 * 
 * @author qingkechina 2014-08-18
 */
public class ChallengeUtil
{
    /**
     * 把挑战书记录入库
     */
    public static void insertChallenge(String userId, String title, int depId, String prescript, String challengers)
    {
        ChallengeDAO challengeDao = new ChallengeDAO();
        challengeDao.setChallengers(challengers);
        challengeDao.setDepId(depId);
        challengeDao.setPrescript(prescript);
        challengeDao.setTitle(title);
        challengeDao.setUserId(userId);
        
        Session session = FrameDBUtil.openSession();
        Transaction transaction = session.beginTransaction();
        session.save(challengeDao);
        transaction.commit();
        FrameDBUtil.closeSession();
    }
    
}


5、当用户登录系统在浏览器中发布挑战书时,需要调用到业务逻辑处理,所以定义PublishChallengeAction业务Java类,里面涉及对数据的校验

package com.medical.server.data;

import com.google.gson.Gson;
import com.medical.frame.FrameCache;
import com.medical.frame.FrameDefaultAction;
import com.medical.frame.FrameException;
import com.medical.frame.bean.FramePathBean;
import com.medical.frame.bean.FrameResultBean;
import com.medical.frame.constant.FrameErrorCode;
import com.medical.frame.util.FrameUtil;
import com.medical.server.dao.UserDAO;
import com.medical.server.util.ChallengeUtil;

/**
 * 斗医系统发布挑战书处理类
 * 
 * @author qingkechina 2014-08-18
 */
public class PublishChallengeAction extends FrameDefaultAction
{
    /**
     * 全局Gson对象
     */
    private final static Gson gson = new Gson();
    
    @Override
    public String execute()
        throws FrameException
    {
        // 用户尚未登录系统
        UserDAO loginUser = FrameCache.getInstance().getUserBySession(session);
        if (loginUser == null)
        {
            FrameResultBean resultBean = new FrameResultBean();
            resultBean.setErrorCode(FrameErrorCode.USER_NOT_LOGIN_ERROR);
            resultBean.setErrorDesc(FrameUtil.getErrorDescByCode(resultBean.getErrorCode()));
            return gson.toJson(resultBean);
        }
        
        // 获取挑战标题
        String title = this.getParameter("title");
        if (FrameUtil.isEmpty(title))
        {
            FrameResultBean resultBean = new FrameResultBean();
            resultBean.setErrorCode(FrameErrorCode.CHANLLENGE_TITLE_EMPTY);
            resultBean.setErrorDesc(FrameUtil.getErrorDescByCode(resultBean.getErrorCode()));
            return gson.toJson(resultBean);
        }
        
        // 获取科室ID
        int depId = -1;
        String departValue = getParameter("departId");
        if (FrameUtil.isEmpty(departValue) == false)
        {
            depId = Integer.valueOf(departValue);
        }
        if (depId == -1)
        {
            FrameResultBean resultBean = new FrameResultBean();
            resultBean.setErrorCode(FrameErrorCode.CHANLLENGE_DEP_EMPTY);
            resultBean.setErrorDesc(FrameUtil.getErrorDescByCode(resultBean.getErrorCode()));
            return gson.toJson(resultBean);
        }
        
        // 获取挑战药方内容、挑战人
        String prescript = getParameter("prescript");
        String challengers = getParameter("challengers");
        // 写入数据库
        ChallengeUtil.insertChallenge(loginUser.getUserId(), title, depId, prescript, challengers);
        
        // 设置返回路径
        FramePathBean pathBean = new FramePathBean();
        pathBean.setErrorCode(200);
        pathBean.setForwardPath("index.act?timestamp=" + System.currentTimeMillis());
        return gson.toJson(pathBean);
    }
}


6、如何调用到PublishChallengeAction业务处理类呢?我们在D:\medical\war\WEB-INF\config\challenge\challenge-data.xm定义,内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<business-config>
    <!--发布挑战书-->
    <business name="publishChallenge" business-class="com.medical.server.data.PublishChallengeAction" />
</business-config>


从上面的配置可以看出,当界面上触发按钮时,调用到publishChallenge的JS方法,从而触发业务逻辑。那么界面是什么样的呢?

wKioL1PyHLzho6wIAAEXjD_lwkw395.jpg


7、在上面的这个界面原型中,药方使用纯文本描述,理论上讲只要使用textarea标签就可以了,考虑到有时还可能会上传一些中药材的图片或病情图片,所以有一个上传图片的功能,这里面就需要使用HTML的富文本编辑器了。

    关于HTML富文本编辑器有很多,像百度的ueditor、Twitter的Bootstrap、Tower的Simditor等等,我们这里选择Kindeditor,为什么选择它呢?主要原因是我没有使用过,呵呵。

    下面就随着我进入Kindeditor世界吧!

(1)进入http://kindeditor.net/down.php下载

(2)下载后把kindeditor解压medical应用的war\js目录下

(3)由于我们使用的是Tomcat容器,所以只保留JSP的功能即可(即删除kindeditor下的asp、asp.net和php目录)

(4)由于medical应用并非发布版本,所以不需要压缩后的js(即删除kindeditor-min.js、kindeditor-all-min.js文件)

(5)有一个examples文件夹,从名称上看应该是一个使用示例,我们可以通过这学习这个例子快速入门

    完成了上述准备工作,下面就可以步入kindeditor之旅了!

8、定义challenge.html来绘制出上述界面(在D:\medical\war\module\challenge下增加challenge.html文件)

<!DOCTYPE HTML>
<html>
    <head>
        <title>斗医</title>
        <!--利于搜索引擎查询-->
        <meta name="description" content="斗医是一个医学交流平台" />
        <meta name="keywords" content="医学,交流,讨论" />
        <!--设置字符集-->
        <meta http-equiv="content-type" content="text/html;charset=utf-8" />
        <!--页面不缓存-->
        <meta http-equiv="pragma" content="no-cache" />
        <meta http-equiv="cache-control" content="no-cache,must-revalidate" />
        <meta http-equiv="expires" content="Wed, 26 Feb 1997 08:21:57 GMT" />
        <!--引入外部文件-->
        <link rel="stylesheet" type="text/css" href="theme/navigation/navigation.css">
        <link rel="stylesheet" type="text/css" href="theme/challenge/challenge.css">
        <script src="js/common/jquery.js"></script>
        <script src="js/kindeditor/kindeditor-all.js"></script>
        <script src="js/common/common.js"></script>
        <script src="js/challenge/challenge.js"></script>
    </head>

    <body>
        <!--系统导航菜单-->
        <div id="system_navigation_menu"></div>

        <!--系统内容部分-->
        <div class="system_content">
            <div class="challenge_content_wrapper">
                <div class="challenge_textarea_wrapper">
                    <textarea id="challenge_title_id" placeholder="写下您的问题"></textarea>
                </div>
                <div class="challenge_hint_info">
                    已超出<label class="challenge_hint_warn" id="challenge_title_hint_id"></label>字
                </div>

                <div class="challenge_text_desc">选择所属科室(必填):</div>
                <div class="challenge_depart_wrapper" id="challenge_depart_id"></div>

                <div class="challenge_text_desc">
                    药方说明(可选):
                    <span class="challenge_editor_switch" id="challenge_editor_switcher" display="default"></span>
                </div>
                <div class="challenge_textarea_wrapper">
                    <textarea id="challenge_prescript_id" placeholder="写下病人神、色、形、态、舌象等症状......"></textarea>
                </div>
                <div class="challenge_hint_info">
                    已超出<label class="challenge_hint_warn" id="challenge_prescript_hint_id"></label>字
                </div>

                <div class="challenge_text_desc">挑战人(可选):</div>
                <div class="challenge_textarea_wrapper">
                    <textarea id="challenge_challenger_id" placeholder="选择您的挑战人"></textarea>
                </div>

                <div class="challenge_text_desc">
                    <a href="javascript:publishChallenge()" class="challenge_confirm_publish">发布</a>
                </div>
            </div>
        </div>
    </body>
</html>


9、定义上面HTML对应的CSS样式文件(在D:\medical\war\theme\challenge下增加challenge.css文件)

/*********************************************************************/
/*                           系统下战书样式                          */
/*********************************************************************/
.challenge_content_wrapper{
    width: 710px;
    margin: 20px auto;
    font-size: 13px;
    border: 1px solid #CCC;
    overflow: hidden;
}

.challenge_textarea_wrapper{
    margin: 5px;
    padding: 8px 5px;
    line-height: 15px;
    box-shadow: 0 1px 1px rgba(0,0,0,.1) inset;
    border-radius: 3px;
    background: #FFF;
    border: 1px solid #ccc;
    color: #222;    
}

.challenge_richtext_wrapper{
    margin: 5px;
    padding: 0;
    line-height: 15px;
    background: #FFF;
    color: #222;    
}
/*********************************************************************/
/*                       下战书TextArea公共样式                      */
/*********************************************************************/
.challenge_textarea_wrapper textarea{
    height: 20px;
    line-height: 20px;
    width: 100%;
    vertical-align: bottom;
    /*移除滚动条*/
    overflow: hidden;
    /*自动换行*/
    word-wrap: break-word;
    /*移除边框*/
    border: 0;
    background-color: #FFF;
    /*去除改变大小拖拽柄*/
    resize: none;
    font-size: 13px;
    border-radius: 5px;
}

.challenge_textarea_wrapper textarea:focus{
    outline: 0;
    outline-offset: -2px;
}

/*********************************************************************/
/*                            药方说明样式                           */
/*********************************************************************/
.challenge_text_desc{
    margin: 25px 5px 0 6px;
}

.challenge_depart_wrapper{
    margin: 5px;
    line-height: 15px;
    background-color: #FFF;
    color: #222;
    overflow: hidden;
}

.challenge_depart_item, .challenge_depart_item_selected{
    float: left;
    min-width: 55px;
    width: auto;
    height: 22px;
    line-height: 22px;
    font-size: 13px;
    text-align: center;
    margin-right: 8px;
    background-color: #E1EAF2;
    border-radius: 6px;
    cursor: pointer;
}

.challenge_depart_item:hover, .challenge_depart_item_selected{
    color: #FFF;
    background-color: #225599;
}

#challenge_prescript_id{
    height: 80px;
    min-height: 80px;
}

.challenge_editor_switch{
    float: right;
    width: 16px;
    height: 16px;
    /*若无属性背景图片无法显示*/
    display: inline-block;
    background-image: url(../navigation/navigation.png);
    background-repeat: no-repeat;
    background-position: -80px -127px;
    cursor: pointer;
}

/*********************************************************************/
/*                            文本提示信息                           */
/*********************************************************************/
.challenge_hint_info{
    text-align: right;
    margin-right: 5px;
    font-size: 13px;
    color: #999;
    display: none;
}

.challenge_hint_warn{
    color: #C33;
}

/*********************************************************************/
/*                             下战书按钮                            */
/*********************************************************************/
.challenge_confirm_publish{
    float: right;
    margin: 0 3px 10px 0;
    color: #FFF;
    font-size: 14px;
    line-height: 1.7;
    padding: 4px 10px;
    display: inline-block;
    text-align: center;
    background-color: #1575D5;
    border: 1px solid #0D6EB8;
    border-radius: 3px;
    text-shadow: 0 -1px 0 rgba(0,0,0,.5);
    box-shadow: 0 1px 0 rgba(255,255,255,.2) inset,0 1px 0 rgba(0,0,0,.2);
}


10、为了响应界面上的动作,所以需要在D:\medical\war\js\challenge下增加challenge.js文件,该文件中有三个方法值得读者粗略地读一下,它们分别是bindEvent2Switcher、initInputComponent和publishChallenge,分别对应富文本与纯textarea切换按钮、自适应textarea高度和发布挑战书。其内容如下:

(function( window){
    $(document).ready(function(){
        // 生成系统菜单
        generateSystemMenu();
        // 选择下战书系统菜单
        selectSystemMenu("system_challenge_menu");
        // 获取用户简要信息
        getBreifUserInfo();
        // 初始化文本框
        initInputComponent();
        // 初始化科室类别数据
        initDepartData();
        // 切换开关按钮绑定事件
        bindEvent2Switcher();
    });

    // 当前选中的科室ID
    var CURRENT_SELECTED_ITEM = -1;

    // 当前编辑器ID
    var CURRENT_HTML_EDITOR = null;

    /**
     * 初始化文本框
     */
    function initInputComponent(){        
        var textareaArray = new Array("challenge_title_id", "challenge_prescript_id", "challenge_challenger_id");
        // 进入页面"标题textarea"获取焦点
        $("#" + textareaArray[0]).focus();
        
        $.each(textareaArray, function(i, item){
            var dynamicItem = $("#" + item);
            // 绑定PlaceHolder
            bindPlaceHolder(dynamicItem);
            
            dynamicItem.bind("keyup", function(event){
                // 设置textArea高度自适应
                autoAdaptHeight(this);
                // 长度超长时给出提示信息
                setLengthHint(this);
            });
        });
    }

    /**
     * textarea高度自适应
     */
    function autoAdaptHeight(component){
        var paddingTop = parseInt($(component).css("padding-top"));
        var paddingBtm = parseInt($(component).css("padding-bottom"));
        var scrollHeight = component.scrollHeight;
        var height = $(component).height();

        // 判断是否为chrome浏览器
        if(window.navigator.userAgent.indexOf("Chrome") > 0){
            if(scrollHeight - paddingTop - paddingBtm > height){
                $(component).css("height", scrollHeight);	
            }
            return;              
        }
        $(component).css("height", scrollHeight);
    }

    /**
     * textarea长度超出时提示
     */
    function setLengthHint(component){
        if(component.id == "challenge_title_id"){
            if(!component.value){
                return;
            }

            var titleId = $("#challenge_title_hint_id");
            if(component.value && component.value.length > 96){
                titleId.parent().show();
            } else {
                titleId.parent().hide();
            }

            titleId.text(component.value.length - 96);
        }
    }

    /**
     * 初始化科室类别数据
     */
    function initDepartData(){
        asyncRequest("gainDepart.data", null, function(result){
            var resultJson = eval(result);
            if(!resultJson){
                return;
            }

            $("#challenge_depart_id").empty();
            $.each(resultJson, function(i, item){
                var departItem = $("<div />").attr("class", "challenge_depart_item").attr("id", "challenge_depart_id_" + item.depId).text(item.depName);
                departItem.click(function(){
                    if(CURRENT_SELECTED_ITEM != -1 && CURRENT_SELECTED_ITEM != item.depId){
                        $("#challenge_depart_id_" + CURRENT_SELECTED_ITEM).attr("class", "challenge_depart_item");
                    }
                    CURRENT_SELECTED_ITEM = item.depId;
                    $("#challenge_depart_id_" + item.depId).attr("class", "challenge_depart_item_selected");
                });
                $("#challenge_depart_id").append(departItem);
            });
        });
    }
    
    /**
     * 切换开关按钮绑定事件
     */
    function bindEvent2Switcher(){
        var switcher = $("#challenge_editor_switcher");
        var display = switcher.attr("display");

        switcher.click(function(){
            var parent = $("#challenge_prescript_id").parent();
            if(display === "default"){
                // 设置父div样式
                parent.removeClass("challenge_textarea_wrapper").addClass("challenge_richtext_wrapper");
                // 设置编辑框的当前样式
                display = "custom";
                switcher.attr("display", "custom");
                var options = {resizeType: 0, items: ["bold", "italic", "underline", "strikethrough", "|", "insertorderedlist", "insertunorderedlist", "|", "image"]};
                CURRENT_HTML_EDITOR = KindEditor.create("#challenge_prescript_id", options);
            }
            else
            {
                // 设置父div样式
                parent.removeClass("challenge_richtext_wrapper").addClass("challenge_textarea_wrapper");
                // 设置编辑框的当前样式
                display = "default";
                switcher.attr("display", "default");
                CURRENT_HTML_EDITOR = null;
                KindEditor.remove("#challenge_prescript_id");
            }
        });
    }

    /**
     * 发布挑战书
     */
    function publishChallenge(){
        // 判断挑战话题
        var challengeTitle = $.trim($("#challenge_title_id").val());
        if(!challengeTitle){
            showSystemGlobalInfo("亲,您还没有写下问题呢~~~");
            return;
        }
        // 判断挑战科室
        if(CURRENT_SELECTED_ITEM === -1){
            showSystemGlobalInfo("亲,您还没有选择科室呢~~~");
            return;
        }
        // 挑战医方内容        
        var challengePrescript = $.trim($("#challenge_prescript_id").val());
        if(CURRENT_HTML_EDITOR){
            challengePrescript = CURRENT_HTML_EDITOR.html();
        }        
        // 挑战人
        var challengers =  $.trim($("#challenge_challenger_id").val());

        var data = {"title": challengeTitle, "departId": CURRENT_SELECTED_ITEM, "prescript": challengePrescript, "challengers": challengers};
        asyncRequest("publishChallenge.data", data, function(result)
        {
            // 跳转到相应页面
            var resultJson = eval(result);            
            top.location = resultJson.forwardPath;
        });
    }

    /**
     * 对外公开接口
     */
    window.publishChallenge = publishChallenge;
})( window );


11、下面验证一下效果:在浏览器中输入http://localhost:8080/medical,在菜单上选择“下战书”,在下战书页面输入相应的信息后点击“发布”,如下图:

wKiom1PyHyfRd1LKAAEdyIfYEY8285.jpg12、点击发布成功后进入系统主页面,若看到内容变化不用着急,因为这个功能尚未实现,我们查看一下数据库是否把此记录追加成功?

wKioL1PyIXWAi-DuAAIgQcOsn8s732.jpg

OK,富文本的简单应用就这样实现了,后面有机会再详细研读原码!