網頁

ASP.NET MVC - DatePicker 日期選擇器 | (3) JSCal2

JSCal2 簡介

這一套 JSCal 已經出道很久了,我第一次在 ASP 程式的 Calendar 就是使用 JSCal 版,還不是 JSCal2,不過後來轉為付費版本且免費資源越來越多的情況下,就換其他選擇了。但今天我們還是選擇介紹它,因為我想透過 JSCal2 介紹一個 ASP.NET MVC 裡的功能,這個功能常讓人家誤解。

先把 Model、Controller \ Action、View 建立起來。

Model:BlogPostJSCal2.vb

Imports System.ComponentModel.DataAnnotations

''' <summary>
''' Blog 發佈文章類別
''' </summary>
Public Class BlogPostJSCal2
    ' 標題
    Public Property Title() As String
    ' 發佈日期
    <DataType(DataType.Date)>
    Public Property PostedOn() As DateTime
    ' 標籤
    Public Property Tags() As String
    ' 內容
    Public Property Content() As String
End Class

Controller \ Action:BlogPostJSCal2Controller.vb

Namespace Mvc3HTMLEditorAndDatePicker
    Public Class BlogPostJSCal2Controller
        Inherits System.Web.Mvc.Controller

        '
        ' GET: /BlogPostJSCal2/Create

        Function Create() As ActionResult
            Return View()
        End Function

        <HttpPost(), ActionName("Create")>
        Function Create_Post(model As BlogPostMy97) As ActionResult
            ViewBag.HtmlContent = model.Content
            Return View(model)
        End Function

    End Class
End Namespace

View:Create.aspx

<%@ Page Title="" Language="VB" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage(Of Mvc3HTMLEditorAndDatePicker.BlogPostJSCal2)" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    KKBruce : DatePicker (使用 JSCal2) 日期選擇器測試
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">

<h2>DatePicker (使用 JSCal2) 日期選擇器測試</h2>

<%-- The following line works around an ASP.NET compiler warning --%>
<%: "" %>

<script src="<%: Url.Content("~/Scripts/jquery.validate.min.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/Scripts/jquery.validate.unobtrusive.min.js") %>" type="text/javascript"></script>

<% Using Html.BeginForm() %>
    <%: Html.ValidationSummary(True) %>
    <fieldset>
        <legend>JSCal2 DatePicker</legend>

        <div class="editor-label">
            <%: Html.LabelFor(Function(model) model.Title) %>
        </div>
        <div class="editor-field">
            <%: Html.EditorFor(Function(model) model.Title) %>
            <%: Html.ValidationMessageFor(Function(model) model.Title) %>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(Function(model) model.PostedOn) %>
        </div>
        <div class="editor-field">
            <%: Html.EditorFor(Function(model) model.PostedOn) %>
            <%: Html.ValidationMessageFor(Function(model) model.PostedOn) %>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(Function(model) model.Tags) %>
        </div>
        <div class="editor-field">
            <%: Html.EditorFor(Function(model) model.Tags) %>
            <%: Html.ValidationMessageFor(Function(model) model.Tags) %>
        </div>

        <div class="editor-label">
            <%: Html.LabelFor(Function(model) model.Content) %>
        </div>
        <div class="editor-field">
            <%: Html.EditorFor(Function(model) model.Content) %>
            <%: Html.ValidationMessageFor(Function(model) model.Content) %>
        </div>

        <p>
            <input type="submit" value="Create" />
        </p>
    </fieldset>
<% End Using %>

<div>
    <%: Html.ActionLink("Back to List", "Index") %>
</div>

</asp:Content>

讓我們來使用 JSCal2 吧。

下載及複製 JSCal2


我們先下載 JSCal2-1.9 (目前最新) ,以下為授權內文:
The version you can download from here is fully functional, but the source code is minified and mangled (using the YUI compressor). If you want the full source code, please purchase a license.
Feel free to use the version distributed here for non-commercial purposes, i.e. on your personal blog, on a charity or educational website, in your free software, etc.
我們測試使用上沒有授權問題。

解壓縮後,
  1. 把 「JSCal2-1.9\src\js」下所有檔案複製到專案 「Scripts\JSCal2-1.9」 目錄之下,
  2. 「JSCal2-1.9\src\css」下有檔案複製到專案「Content\JSCal2-1.9」目錄之下。
設定與使用 JSCal2

在解壓縮的 JSCal2-1.9 目錄下,有完整的說明與範例文章,我們先看「JSCal2-1.9/demo/simple.html」這個文件 Source Code,以了解如何使用。

需要引用二個 JavaScript 與 三個 CSS 檔案:

<!-- JSCal2 -->
<script src="<%: Url.Content("~/Scripts/JSCal2-1.9/jscal2.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/Scripts/JSCal2-1.9/lang/cn.js") %>" type="text/javascript"></script>
<link href="<%: Url.Content("~/Content/JSCal2-1.9/jscal2.css") %>" rel="stylesheet" type="text/css" />
<link href="<%: Url.Content("~/Content/JSCal2-1.9/border-radius.css") %>" rel="stylesheet" type="text/css" />
<link href="<%: Url.Content("~/Content/JSCal2-1.9/steel/steel.css") %>" rel="stylesheet" type="text/css" />

然後看一下它所撰寫的 JavaScript,還好,很簡單易懂。

<input size="30" id="f_date1" /><button id="f_btn1">...</button><br />

<script type="text/javascript">
//<![CDATA[
Calendar.setup({
    inputField: "f_date1",  // 重要:選擇後的值要放到那個 input 欄位
    trigger: "f_btn1",      // 重要:JSCal2 必須由"button"觸發
    onSelect: function () { this.hide() },
    showTime: 12,
    dateFormat: "%Y-%m-%d %I:%M %p" // 日期格式:年-月-日 時:分 PM|AM
                                    // 例如,2011-10-06 04:30 PM
});
//]]>
</script>

依上面的注解,我們改寫一下此 JavaScript 與 Create.aspx:

<div class="editor-field">
    <%: Html.EditorFor(Function(model) model.PostedOn) %>
    <%: Html.ValidationMessageFor(Function(model) model.PostedOn) %>
</div>
<!-- 觸發 JSCal2 使用 -->
<button id="jscal_bnt">選擇日期</button>
<!-- 設定 JSCal2 -->
<script type="text/javascript">
//<![CDATA[
Calendar.setup({
    inputField: "PostedOn",
    trigger: "jscal_bnt", 
    onSelect: function () { this.hide() },
    showTime: 12,
    dateFormat: "%Y-%m-%d %I:%M %p" 
});
//]]>
</script>

ASP.NET MVC - JSCal2 執行畫面
圖一:ASP.NET MVC - JSCal2 執行畫面
到這裡我們已經完成了 JSCal2 在 Datepicker 的應用。

JSCal 行程曆範例

「你又想往下寫,是呀?」訪客說。
「多學一些,又不會怎樣。」KKBruce說:「寫的人又不你,累的人是我。」
「也對!」訪客說。
在解壓縮的目錄「JSCal2-1.9\demo」之下,有許多用好的範例,其中一個 dateInfo.html 引起我的興趣。它是一個類似程事曆範例,

JSCal2 dateInfo.html 範例
圖二:JSCal2 dateInfo.html 範例
以下是 dateInfo.html 內的 JavaScript 的程式碼:

<script type="text/javascript">
//<![CDATA[

var DATE_INFO = {
        20090507: { klass: "highlight", tooltip: "%Y/%m/%d (%A)<br />That was yesterday" },
        20090508: { klass: "highlight", tooltip: "And this is TODAY" }
};

function getDateInfo(date, wantsClassName) {
        var as_number = Calendar.dateToInt(date);
        if (as_number >= 20090518 && as_number <= 20090524)
                return {
                        klass   : "highlight2",
                        tooltip : "<div style='text-align: center'>This is the green week</div>"
                };
        return DATE_INFO[as_number];
};

var cal = Calendar.setup({
        cont     : "cont",
        fdow     : 1,
        date     : 20090501,
        dateInfo : getDateInfo
});

//]]>
</script>

你能發現,以上參數都是寫死的,一般而言,會像我在「ASP.NET+jqPlot實作線上即時統計圖表(Chart) 」所介紹的,這種程式碼,應該由後端去組合出來,然後回傳給前端,以保持前端動態且即時的資訊。

ASP.NET MVC - JavaScript ActionResult 使用介紹

這時候,就讓我們 ASP.NET MVC 中的 JavaScript ActionResult 上場吧!常看到有人問,這個 JavaScript ActionResult 是做什麼用?直接呼叫它,還會給我一個下載提示或顯示為純文字!常有人誤解它的用處,在這樣讓我們把 JavaScript ActionResult 的用處展示一下。

我們改寫上面 dateInfo.html 裡的 JavaScript,然後放到 BlogPostJSCal2Controller.vb 之中:

''' <summary>
''' 回傳行事曆所需要的JavaScript,可動態調整參數內容。
''' </summary>
''' <returns>JavaScript</returns>
Function Create_DateInfoJS() As JavaScriptResult

    ' 一般這裡是會去取得 Database 資訊
    ' 為簡化範例,我們只簡單使用變數
    Dim thisMonth As Date = Date.Now
    Dim starDay As Date = Date.Now 
    Dim endDay As Date = Date.Now.AddDays(7)

    Dim DateInfoJS As New System.Text.StringBuilder

    DateInfoJS.Append("var DATE_INFO = {")
    ' 單日動態
    DateInfoJS.Append("" & thisMonth.ToString("yyyyMM") & "01" & ": { klass: ""highlight"", tooltip: ""%Y/%m/%d (%A)<br />KKBruce說:初一要記得拜拜!"" },")
    DateInfoJS.Append("" & thisMonth.ToString("yyyyMM") & "15" & ": { klass: ""highlight"", tooltip: ""%Y/%m/%d (%A)<br />KKBruce說: 十五要記的觀賞月亮!"" }")
    DateInfoJS.Append("};")
    DateInfoJS.Append("")
    DateInfoJS.Append("function getDateInfo(date, wantsClassName) {")
    DateInfoJS.Append("var as_number = Calendar.dateToInt(date);")
    ' 一週動態
    DateInfoJS.Append("if (as_number >= " & starDay.ToString("yyyyMMdd") & " && as_number <= " & endDay.ToString("yyyyMMdd") & ")")
    DateInfoJS.Append("return {")
    DateInfoJS.Append("klass   : ""highlight2"",")
    DateInfoJS.Append("tooltip : ""<div style='text-align: center'>KKBruce說:希望接下來的一週每天都是green不要red!</div>""")
    DateInfoJS.Append("};")
    DateInfoJS.Append("return DATE_INFO[as_number];")
    DateInfoJS.Append("};")
    DateInfoJS.Append("")
    DateInfoJS.Append("var cal = Calendar.setup({")
    ' 這一個名稱需對應 div 的 id 名稱
    DateInfoJS.Append("cont     : ""cont"",")
    DateInfoJS.Append("fdow     : 1,")
    ' 讓它保持在本月第一天
    DateInfoJS.Append("date     : "& thisMonth.ToString("yyyyMM") & "01" &",")
    DateInfoJS.Append("dateInfo : getDateInfo")
    DateInfoJS.Append("});")

    Return JavaScript(DateInfoJS.ToString())
End Function

增加一個 Action,以強型別新增一個 View。

Action:Create_DateInfo

' GET: /BlogPostJSCal2DatePicker/Create_DateInfo

Function Create_DateInfo() As ActionResult
       Return View()
End Function

View:Create_DateInfo.aspx 引用相關 JavaScript
<!-- ASP.NET MVC Ajax 需引用 -->
<script src="<%: Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js") %>" type="text/javascript"></script>

<!-- 使用 JSCal2 所必須 Javascript 與 CSS -->
<script src="<%: Url.Content("~/Scripts/JSCal2-1.9/jscal2.js") %>" type="text/javascript"></script>
<script src="<%: Url.Content("~/Scripts/JSCal2-1.9/lang/cn.js") %>" type="text/javascript"></script>
<link href="<%: Url.Content("~/Content/JSCal2-1.9/jscal2.css") %>" rel="stylesheet" type="text/css" />
<link href="<%: Url.Content("~/Content/JSCal2-1.9/border-radius.css") %>" rel="stylesheet" type="text/css" />
<link href="<%: Url.Content("~/Content/JSCal2-1.9/matrix/matrix.css") %>" rel="stylesheet" type="text/css" />
<style type="text/css">
    .highlight { color: #f00 !important; }
    .highlight2 { color: #0f0 !important; font-weight: bold; }
</style>

然後,透過 Ajax 技術的幫忙,讓我們動態的去載入此日曆。

<div class="editor-field">
    <%-- 
    <%: Html.EditorFor(Function(model) model.PostedOn) %>
    <%: Html.ValidationMessageFor(Function(model) model.PostedOn) %>
    --%>
    <div id="datapicker">
        <div id="cont"></div>
    </div>
    <%: Ajax.ActionLink("載入日曆", "Create_DateInfoJS", 
                        new AjaxOptions With {.UpdateTargetId= "datapicker" }) %>
</div>

建置,執行網頁「http://localhost:6944/BlogPostJSCal2/Create_DateInfo」(記得換成你的 port)。

圖三:Ajax 動態載入 JavaScript
一開始好像不錯,不過你多按幾次會發現,前一次的行事曆不會消失。

圖四:Ajax 動態載入 - 前一次的效果不會消失
讓我們寫一小段 jQuery 程式碼來解決這個問題。

<div class="editor-field">
    <%-- 
    <%: Html.EditorFor(Function(model) model.PostedOn) %>
    <%: Html.ValidationMessageFor(Function(model) model.PostedOn) %>
    --%>
    <div id="datapicker">
        <div id="cont"></div>
    </div>
    <%: Ajax.ActionLink("載入日曆", "Create_DateInfoJS", 
                        new AjaxOptions With {.UpdateTargetId= "datapicker",
                                              .OnBegin = "RemovePreDataPicker" }) %>
    
    <script type="text/javascript">
        // 每次按載入日曆畫面會重覆出現日曆
        // 必須在每次更新前,先刪除前一個日曆
        function RemovePreDataPicker() {
            var datepickerId = $('#cont');
            datepickerId.find('table').each(function() {
                this.remove();
            });
        }
    </script>
</div>

建置,重新執行網頁。

JSCal2 使用心得

JSCal2 是一個開發很久的純 JavaScript 專案,也是穩定好用。而我想透過 JSCal2 裡的範例,讓大家了解 ASP.NET MVC 裡 JavaScript ActionResult 的用途。當我們的 JavaScript 裡的資料需要由「伺服器端即時提供」時,此時就是使用 JavaScript ActionResult 的好時機。

參考資料


1 則留言:

  1. 如何移除JSCal2按下enter觸發下拉選單的動作呢?謝謝

    回覆刪除

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。