專案有個需求,要在網頁上呈現多間會議室一天的使用登記狀況。'傳統思維"可能會傾向用Table <td>模擬出時段區塊,然後將預約起迄時間範圍內的<td>用colspan併成一塊。不過如果要做到登記時間以10分鐘為單位,意味著每個小時要切割成6個<td>,即便沒有任何預約也是,讓Table充滿一堆無用又囉嗦的廢物Tag。加上計算哪幾個<td>要併在一起,得找出第一個<td>加colspan,並將後面<td>省略,演算法不怎麼單純。分析起來,這就是該用CSS取代Table做法實作Layout的經典情境。

寫了一段jQuery程式做出POC(Proof of Concept)供同事參考,示範使用DIV+CSS達成上述效果,並並盡可能簡化為呼叫函數就可以完成。一魚兩吃,PO出來供大家參考,順便蒐集諸位先進達人們的回饋指教。

//輸入起/迄整點、寬、高及是否顯示時間標題列,傳回顯示全日時段狀態的<div>
function genTimeline(startHour, endHour, width, height, incHeader) {
    $.Darkthread.tools.addDefaultStyles(".dttl_BookingBlock", {
        ".dttl_BookingBlock": {
            float: "left", position: "absolute",
            margin: "0px", padding: "0px", height: "100%",
            "font-size": "9pt", "background-color": "#dddddd",
            "border-left": "solid #444444 1px",
            "border-right": "solid #444444 1px"
        },
        ".dttl_Timeline": {
            border: "solid black 1px", position: "relative"
        },
        ".dttl_TimelineHeader": {
            border: "solid black 1px",
            backgroundColor: "#dddddd",
            "font-size": "8pt", position: "relative"
        },
        ".dttl_SplitLine": {
            float: "left", position: "absolute",
            "border-left": "solid gray 1px", 
            width: "1px", height: "100%"
        }
    });
 
    var $tl = $("<div class='dttl_Timeline' />");
    $tl.height(height).width(width);
    var hourWidth = width / (endHour - startHour);
    var $h = $("<div />");
    if (incHeader)
        $h.width(width).height(15).addClass("dttl_TimelineHeader");
    for (var i = startHour; i < endHour; i++) {
        var vLine =
            "<div class='dttl_SplitLine' style='left:" +
            hourWidth * (i - startHour) + "px;' />";
        $tl.append(vLine);
        if (incHeader)
            $h.append("<div>" + i + "</div>").append(vLine);
    }
    if (incHeader) {
        $h.find("div").css({
            float: "left", margin: "0px", width: hourWidth + "px", 
            textAlign: "center"
        });
    }
    var $container = $("<div class='dttl_Container' />");
    $container.append($h).append($tl)
    .data("hourWidth", hourWidth)
    .data("startHour", startHour)
    .data("endHour", endHour);
    return $container;
}
 
//display會直接顯示在方格中,可支援HTML語法
//detail以title="..."方式處理,請給純文字
//cssOption可為css屬性物件,例如: { border:"...", color:"..." }或是className
//會傳回<div> jQuery物件供後續應用
function addBooking(timeline, startTime, endTime, display, detail, cssOption) {
 
    if (cssOption == undefined || typeof cssOption == "string") 
        cssOption = { };
 
    var $tl = $(timeline);
    var sh = $tl.data("startHour"), eh = $tl.data("endHour");
    //將時間先轉成小數
    var p = startTime.split(':');
    var st = parseInt(p[0]) + (parseFloat(p[1]) / 60);
    //小於startHour, 以startHour為準
    if (st < sh) st = sh;
    
    p = endTime.split(':');
    var et = parseInt(p[0]) + (parseFloat(p[1]) / 60);
    //大於endHour, 以endHour為準
    if (et > eh) et = eh;
 
    //算出起始位置及寬度
    var hw = $tl.data("hourWidth");
    var w = (et - st) * hw;
    var x = (st - sh) * hw;
 
    //加入顯示元素
    var $container = $tl.children("div:last");
    
    var $dv = $("<div class=\"dttl_BookingBlock\"/>");
    $dv.css(cssOption).css({ 
        left: x + "px", width: w + "px",
        opacity: 0.7
    });
 
    $dv.html("<span style='padding: 2px;'>" + display + "</span>")
    .attr("title", startTime + "-" + endTime + "\n" + detail);
    if (typeof cssOption == "string")
        $dv.addClass(cssOption);
 
    $container.append($dv);
    
    return $dv;
}

以下是應用範例:

<script src="jquery-1.4.2.js" type="text/javascript"></script>
<script src="jquery.darkthread.tools.js" type="text/javascript"></script>
<script src="TimelineUtil.js" type="text/javascript"></script>
<script type="text/javascript">
    $(function() {
        var $d = genTimeline(9, 21, 800, 30, true);
        $("body").append($d);
 
        var $booking;
        //truncText可幫忙取前10個字            
        var longText = "超長文字測試,利用truncDetail()取前10個字元";
        $booking = addBooking($d, "14:30", "17:00",
            $.Darkthread.tools.truncText(longText, 10), longText);
        //允許重疊顯示
        $booking = addBooking($d, "16:30", "20:00", "來亂的", "咬我呀!");
  //cssOption可指定CSS
        var cssOption = { backgroundColor: "green", color: "yellow" };
        $booking = addBooking($d, "09:30", "10:00", "Jeffrey", 
                              "重要會議", cssOption);
 
        //cssOption傳入字串時,視為className
        $booking = addBooking($d, "10:30", "12:00", 
                                "Meeting", "MouseOver特效", "booking");
        //addBooking會傳回<div>的jQuery物件,可進行加上hover/click事件等操作
        $booking.hover(function() {
            $(this).addClass("inverse");
        }, function() {
            $(this).removeClass("inverse");
        }).css("cursor", "pointer").click(function() {
            alert("Hi!");
        });
 
        //產生100條時間軸各有12筆登錄,測試速度
        var $tbl = $(
        "<table cellspacing='0' cellpadding='0' style='margin-top: 10px;' />");
        for (var i = 0; i < 100; i++) {
            $tr = $("<tr><td width='10%' /><td width='90%' /></tr>");
            $tr.find("td:first").text("Room" + i);
            var $timeline = genTimeline(9, 21, 800, 30, i == 0);
            $tr.find("td:last").append($timeline);
            $tbl.append($tr);
            for (var j = 9; j < 21; j++) {
                $booking = addBooking($timeline, j + ":00", j + ":30", 
                    "Test-" + i + "-" + j, "Test");
            }
        }
        $("body").append($tbl);
    });
</script>
<style type="text/css">
    .booking
    {
        background-color: Green;
        color: Yellow;
    }
    div.inverse 
    {
        background-color: black;
        color: White;
    }
</style>

【註】只是POC,防呆、檢核及彈性都很薄弱,不足處請自理!


Comments

# by 大估

我就是用table拼<td>出來的... 寫那段演算法…花了快兩天… XD

# by Jquery新手

請問那個darkthread.tools.js有開放下載嗎:P

# by Jeffrey

to Jquery新手, darkthread.tools.js是我常用jQuery函數的大雜燴,還在不斷擴充修改中,也不敢確認有無重大Bug,計劃自己先用過一段時間確認穩定後,將來會再找黃道吉日Release。

Post a comment