2017年5月22日 星期一

如何使用angular-material-calendar

雖然已經邁入angular 4 的階段,但目前支援的套件仍然很少,雖然有些跟不上潮流,但angualr1仍有不少的粉絲,所以先討論如何angular 1 的行事曆套件 angular-material-calendar,因為剛好案子有需要所以拿來研究看看,也許以後會改用angular4 calendar 版本… 呵呵

下戴相關套件
npm install --save angular-material-calendar

引用angular-material-calendar
@section Header {
<link href="~/Scripts/Bootstrap/3.0.2/css/bootstrap.min.css" rel="stylesheet" />
<link href="~/Content/ian-style.css?@version" rel="stylesheet" />
<link href="/SharedJS/AngularJS/Material/angular-material.min.css" rel="stylesheet" />
<link href="/SharedJS/AngularJS/Material/angular-material-calendar.min.css" rel="stylesheet" />
}
@section Scripts {
<script src="~/Scripts/jquery-1.8.2.min.js"></script>
<script src="/SharedJS/AngularJS/1.4.8/angular.min.js"></script>
<script src="/SharedJS/AngularJS/1.4.8/angular-animate.min.js"></script>
<script src="/SharedJS/AngularJS/1.4.8/angular-sanitize.js"></script>
<script src="/SharedJS/AngularJS/1.4.8/angular-aria.min.js"></script>
<script src="/SharedJS/AngularJS/1.4.8/angular-route.min.js"></script>
<script src="/SharedJS/AngularJS/1.4.8/angular-messages.min.js"></script>
<script src="/SharedJS/AngularJS/Material/angular-material.min.js"></script>
<script src="/SharedJS/AngularJS/Material/angular-material-calendar.min.js"></script>
<script src="~/Scripts/Bootstrap-UI/ui-bootstrap-tpls-0.10.0.min.js"></script>
<script src="~/Areas/Holiday/Scripts/holidayFactory.js"></script>
<script src="~/Areas/Holiday/Scripts/IndexController.js"></script>
<script src="~/Areas/Holiday/Scripts/popupController.js"></script>
}
因為筆者也有使用angular-material.js的相關特效,所以相依性的套件也一併加入引用參考,請各位視情況加入參考吧~ 但最重要的是 angular-material-calendarcss js 是必要元件

行事曆的頁面
<calendar-md flex layout layout-fill
calendar-direction="direction"
on-prev-month="prevMonth"
on-next-month="nextMonth"
on-day-click="dayClick"
title-format="'MMMM y'"
ng-model='selectedDate'
week-starts-on="firstDayOfWeek"
data-start-month="{{calendarMonth}}"
data-start-year="{{calendarYear}}"
tooltips="tooltips"
day-format="dayFormat"
day-label-format="'EEE'"
day-label-tooltip-format="'EEEE'"
day-tooltip-format="'fullDate'"
day-content="setDayContent"
disable-future-selection="false"></calendar-md>
雖然頁面上的參數那麼多… 其實先知道如何設定起始年份和起始月份,就可以了喔… 如果想進一步的瞭解format 格式有哪些… 還請各位自行研究,目前僅探討簡單的功能。

行事曆的事件有哪些?
var app = angular.module('Holiday', ['ui.bootstrap', 'ngMaterial', 'materialCalendar']);
app.controller('IndexController', function ($scope, $http, $filter, $q, $timeout, $modal, MaterialCalendarData, holidayFactory) {
$scope.calendarYear=new date().getFullYear(); //設定calendar的起始年
$scope.calendarMonth=new date().getMonth(); //設定calendar的起始月份
$scope.dayFormat = "d";
$scope.selectedDate = null;
$scope.selectedDate = []; //選擇日期
$scope.firstDayOfWeek = 0; //設定每週開始【星期日】,以此類推
$scope.setDirection = function (direction) {
$scope.direction = direction;
$scope.dayFormat = direction === "vertical" ? "EEEE, MMMM d" : "d";
};
//點選行事曆的某天後,觸發的事件
$scope.dayClick = function (date) {
$scope.toggleModal(date);
};
//可以設定行事曆的內容
$scope.setDayContent = function (date) {
//此函式將會被多次觸發,因為一個月份的日期有三十多天,所以此函式會被多次重複呼叫,而傳入的參
date 會陸續傳進不同的日期 (1~30)
var contentDay = "";
//此函式會被多次呼叫,所以如果在此函式中透過API或後端取資料,將會造會效能低下,所以需要
後端取得的值,暫存在頁面中。
取得設定行事曆的值以json暫存 id=’holiday’text,並取回結果
var jsonHolidays = $("#holidays").val();
if (jsonHolidays.length > 0) {
var holidays = JSON.parse(jsonHolidays);
contentDay = $filter("date")(date, "yyyy-MM-dd"); //取得日期轉格式
var checkData = holidays[contentDay]; //確認此日期是否為假日
if (checkData == undefined) {
return "<p><br><br><p>"; //如果非假日,傳回的內容
} else {
//判斷如果為假日列表有此日期,再判斷(是假日),傳回紅字,否傳回籃字
if (holidays[contentDay].isHoliday == true) {
return "<font color=red>" + holidays[contentDay].holidayName + "</font>";
} else {
return "<font color=blue>" + holidays[contentDay].holidayName + "</font>";
}
}
} else {
//如果判斷頁面上的id=’holiday’text 並無值(第一次讀取時),將會進入此區塊,因為要從後端
讀取資料,雖然再將值回傳到行事曆中… 所以需要用非同步的處理
var deferred = $q.defer();//先設定行事曆非同步
var startDate = $filter("date")(date, "yyyy") + "-" + "01" + "-" + "01";
var endDate = $filter("date")(date, "yyyy") + "-" + "12" + "-" + "31";
//從後端讀取假日列表的APPI
$scope.hoilday = $scope.reloadData(startDate, endDate).then(function (result) {
contentDay = $filter("date")(date, "yyyy-MM-dd");
$("#holidays").val(angular.toJson(result));
holidays = result;
var checkData = holidays[contentDay];
if (checkData == undefined) {
//以非同步處理結果
deferred.resolve("<p><br><br><p>");
} else {
if (holidays[contentDay].isHoliday == true) {
deferred.resolve("<font color=red>" + holidays[contentDay].holidayName + "</font>");
} else {
deferred.resolve("<font color=blue>" + holidays[contentDay].holidayName + "</font>");
}
}
});
//回傳非同步結果
return deferred.promise;
}
}
//設定某日期的行事曆的函式(
稱自訂)
$scope.setContentViaService = function (setDate,isHoliday,holidayName) {
//某種情況下,需要 UPDATE 某一天的日期的內容,只需呼叫 MaterialCalendarData
var holidays = JSON.parse($("#holidays").val());
var contentDay = $filter("date")(setDate, "yyyy-MM-dd");
if (isHoliday == true) {
MaterialCalendarData.setDayContent(setDate, "<p><font color=red>" + holidayName + "</font></p>");
} else {
MaterialCalendarData.setDayContent(setDate, "<p><font color=blue>" + holidayName + "</font></p>");
}
}
//出現子視窗: 情境中為了在行事曆點選某一天後,就能讀取該日期的內容, 就在controller引用 $model,可以另外設定子視窗的單獨的網頁內容和JavaScript,以利於未來高度重複利用
$scope.toggleModal = function (holiday_datetime) {
$scope.modInstance;
$scope.holiday_datetime = holiday_datetime; //透過$scope 傳遞參數給另一個controller
$scope.modInstance = $modal.open({
templateUrl: 'myModalContent.html',
scope: $scope,
controller: "popupController"
});
}

popupController.js
app.controller("popupController", function ($scope, $http, $filter, $q, $timeout, $modal,$controller, MaterialCalendarData, holidayFactory)
{
$scope.form = {};
var curentDay = $filter("date")($scope.holiday_datetime, "yyyy-MM-dd");
var startDate = $filter("date")($scope.holiday_datetime, "yyyy") + "-" + "01" + "-" + "01";
var endDate = $filter("date")($scope.holiday_datetime, "yyyy") + "-" + "12" + "-" + "31";
$scope.curentDay = curentDay;
$scope.form.isHoliday = true; //預設為假日
holidayFactory.LoadHoliday(curentDay).then(function (result) {
var loadHolidayObj = JSON.parse(result.data.data);
if (loadHolidayObj != undefined) {
$scope.form.holidayName = loadHolidayObj.holidayName;
$scope.form.isHoliday = loadHolidayObj.isHoliday;
}
});

$scope.save = function () {
var msg = "";
$scope.holidayNameEmpty = false;
if ($scope.form.holidayName == undefined || $scope.form.holidayName == "") {
msg = "請輸入假日名稱";
$scope.holidayNameEmpty = true;
}
if (msg.length == 0) {
holidayFactory.SaveDate(curentDay, $scope.form.holidayName, $scope.form.isHoliday).then(function () {
holidayFactory.GetHolidays(startDate, endDate).then(function (result) {
$("#holidays").val(result.data.data); //put the holiday value in page
})
});

//在某些情況下,無法完全脫離父 controller 的相依性,利用當行事曆要傳遞參數給子視窗,仍然要呼叫另一個controller 的某事件處理,所以只好透過呼叫另一個controller方法,呼叫它的方法。
var testCtrlViewModel = $scope.$new();
$controller("IndexController", { $scope: testCtrlViewModel });
testCtrlViewModel.setContentViaService($scope.holiday_datetime, $scope.form.isHoliday, $scope.form.holidayName);
$scope.modInstance.dismiss('cancel'); //子視窗自動隱藏
}
}
$scope.close = function () {
$scope.modInstance.dismiss('cancel');
}
});

Index.cshtml主畫面要引用子視窗的html
<script type="text/ng-template" id="myModalContent.html">
@{ Html.RenderPartial("@popup"); }
</script>
</div>

@popup.cshtml (子視窗的畫面內容)
<div class="modal-header btn-primary">
<button type="button" class="close" ng-click="close(false)" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">行事曆假日設定【 {{holiday}} </h4>
</div>
<div class="modal-body">
<form>
<div class="form-group">
<label for="holidayName">假日名稱</label>
<input type="text" ng-model="form.holidayName" class="form-control" id="holidayName"placeholder="名稱" maxlength="30" />
<br />
<label for="isHoliday">是否假日</label>
<md-checkbox id="isHoliday" aria-label="假日" ng-model="form.isHoliday"></md-checkbox>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" ng-click="close()" class="btn btn-default" data-dismiss="modal">No</button>
<button type="button" ng-click="save()" class="btn btn-primary" data-dismiss="modal">Yes</button>
</div>


子視窗的ng-model雙向綁定,需要以物件為單位,例如 form.holidayName, form.isHoliday 而無法以holidayName isHolidayng-model命名,否則無法進行雙向綁定…

畫面呈現:



補充說明: 因為案子的需要,剛好要取得政府機關的行事曆API,所以透過Factory的功能,將所有呼叫API集中,方便日後管理


HoldayFactroy.js
function initFactory(app) {
app.factory("holidayFactory", function ($http, $q) {
return {
LoadHoliday: function (settingDate) {
return $http({
method: "Post",
data: { settingDate: settingDate },
url: "/Web/Holiday/LoadHoliday",
cache: true
});
},
}
})
}

HomeController.cs
[HttpPost]
public async Task<bool> LoadGovSchedule(int year) {
try
{
return controllerHolidayObj.LoadGovSchedule (year);
}
catch (Exception ex)
{
_log.ErrorException("SaveGovSchedule: ", ex);
throw new Exception(ex.Message);
}
}
透過controller 可以取得angular傳入的參數,僅供參考,未來也可考慮用WebAPI或其他分散式API,達到資料共享和重複利用。

ControllerHolidy.cs
private List<viewHoliday> LoadGovSchedule(int year) {
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("http://data.ntpc.gov.tw/api/v1/rest/datastore/382000000A-000077-002");
request.Method = "GET";
JavaScriptSerializer json_serializer = new JavaScriptSerializer();
List<viewHoliday> govSchedule = new List<viewHoliday>();
DateTime dtGovDateTime = new DateTime();
string holidayName = "";

using (WebResponse wr = request.GetResponse())
{
using (StreamReader myStreamReader = new StreamReader(wr.GetResponseStream()))
{
string data = myStreamReader.ReadToEnd();
dynamic jsonObject = json_serializer.Deserialize<dynamic>(data);
dynamic result = jsonObject["result"];
dynamic records = result["records"];
foreach (dynamic item in records)
{
holidayName = HolidayName(item["name"], item["holidayCategory"]);
dtGovDateTime = DateTime.ParseExact(item["date"], "yyyy/M/d", null);
if (dtGovDateTime.Year == year) {
govSchedule.Add(new viewHoliday()
{
holidayDate = dtGovDateTime,
holidayName = holidayName,
isHoliday = (item["isHoliday"] == "") ? true : false
});
}
}
}
}
return govSchedule;
}
catch (Exception ex)
{
throw new Exception("controllerHoliday.LoadGovSchedule", ex);
}
}


以上用來示範如何取得JSON格式傳回的訊息