angular-material-calendar.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. angular.module("materialCalendar", ["ngMaterial", "ngSanitize"]);
  2. angular.module("materialCalendar").constant("materialCalendar.config", {
  3. version: "0.2.13",
  4. debug: document.domain.indexOf("localhost") > -1
  5. });
  6. angular.module("materialCalendar").config(["materialCalendar.config", "$logProvider", "$compileProvider", function (config, $logProvider, $compileProvider) {
  7. if (config.debug) {
  8. $logProvider.debugEnabled(false);
  9. $compileProvider.debugInfoEnabled(false);
  10. }
  11. }]);
  12. angular.module("materialCalendar").service("materialCalendar.Calendar", [function () {
  13. function Calendar(year, month, options) {
  14. var now = new Date();
  15. this.setWeekStartsOn = function (i) {
  16. var d = parseInt(i || 0, 10);
  17. if (!isNaN(d) && d >= 0 && d <= 6) {
  18. this.weekStartsOn = d;
  19. } else {
  20. this.weekStartsOn = 0;
  21. }
  22. return this.weekStartsOn;
  23. };
  24. this.options = angular.isObject(options) ? options : {};
  25. this.year = now.getFullYear();
  26. this.month = now.getMonth();
  27. this.weeks = [];
  28. this.weekStartsOn = this.setWeekStartsOn(this.options.weekStartsOn);
  29. this.next = function () {
  30. if (this.start.getMonth() < 11) {
  31. this.init(this.start.getFullYear(), this.start.getMonth() + 1);
  32. return;
  33. }
  34. this.init(this.start.getFullYear() + 1, 0);
  35. };
  36. this.prev = function () {
  37. if (this.month) {
  38. this.init(this.start.getFullYear(), this.start.getMonth() - 1);
  39. return;
  40. }
  41. this.init(this.start.getFullYear() - 1, 11);
  42. };
  43. // Month should be the javascript indexed month, 0 is January, etc.
  44. this.init = function (year, month) {
  45. var now = new Date();
  46. this.year = angular.isDefined(year) ? year : now.getFullYear();
  47. this.month = angular.isDefined(month) ? month : now.getMonth();
  48. var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  49. var monthLength = daysInMonth[this.month];
  50. // Figure out if is a leap year.
  51. if (this.month === 1) {
  52. if ((this.year % 4 === 0 && this.year % 100 !== 0) || this.year % 400 === 0) {
  53. monthLength = 29;
  54. }
  55. }
  56. // First day of calendar month.
  57. this.start = new Date(this.year, this.month, 1);
  58. var date = angular.copy(this.start);
  59. while (date.getDay() !== this.weekStartsOn) {
  60. date.setDate(date.getDate() - 1);
  61. monthLength++;
  62. }
  63. // Last day of calendar month.
  64. while (monthLength % 7 !== 0) {
  65. monthLength++;
  66. }
  67. this.weeks = [];
  68. for (var i = 0; i < monthLength; ++i) {
  69. // Let's start a new week.
  70. if (i % 7 === 0) {
  71. this.weeks.push([]);
  72. }
  73. // Add copy of the date. If not a copy,
  74. // it will get updated shortly.
  75. this.weeks[this.weeks.length - 1].push(angular.copy(date));
  76. // Increment it.
  77. date.setDate(date.getDate() + 1);
  78. }
  79. };
  80. this.init(year, month);
  81. }
  82. return Calendar;
  83. }]);
  84. angular.module("materialCalendar").service("MaterialCalendarData", [function () {
  85. function CalendarData() {
  86. this.data = {};
  87. this.getDayKey = function(date) {
  88. return [date.getFullYear(), date.getMonth() + 1, date.getDate()].join("-");
  89. };
  90. this.setDayContent = function(date, content) {
  91. this.data[this.getDayKey(date)] = content || this.data[this.getDayKey(date)] || "";
  92. };
  93. }
  94. return new CalendarData();
  95. }]);
  96. angular.module("materialCalendar").directive("calendarMd", ["$compile", "$parse", "$http", "$q", "materialCalendar.Calendar", "MaterialCalendarData", function ($compile, $parse, $http, $q, Calendar, CalendarData) {
  97. var defaultTemplate = "<md-content layout='column' layout-fill md-swipe-left='next()' md-swipe-right='prev()'><md-toolbar><div class='md-toolbar-tools' layout='row'><md-button class='md-icon-button' ng-click='prev()' aria-label='Previous month'><md-tooltip ng-if='::tooltips()'>Previous month</md-tooltip>&laquo;</md-button><div flex></div><h2 class='calendar-md-title'><span>{{ calendar.start | date:titleFormat:timezone }}</span></h2><div flex></div><md-button class='md-icon-button' ng-click='next()' aria-label='Next month'><md-tooltip ng-if='::tooltips()'>Next month</md-tooltip>&raquo;</md-button></div></md-toolbar><!-- agenda view --><md-content ng-if='weekLayout === columnWeekLayout' class='agenda'><div ng-repeat='week in calendar.weeks track by $index'><div ng-if='sameMonth(day)' ng-class='{&quot;disabled&quot; : isDisabled(day), active: active === day }' ng-click='handleDayClick(day)' ng-repeat='day in week' layout><md-tooltip ng-if='::tooltips()'>{{ day | date:dayTooltipFormat:timezone }}</md-tooltip><div>{{ day | date:dayFormat:timezone }}</div><div flex ng-bind-html='dataService.data[dayKey(day)]'></div></div></div></md-content><!-- calendar view --><md-content ng-if='weekLayout !== columnWeekLayout' flex layout='column' class='calendar'><div layout='row' class='subheader'><div layout-padding class='subheader-day' flex ng-repeat='day in calendar.weeks[0]'><md-tooltip ng-if='::tooltips()'>{{ day | date:dayLabelTooltipFormat }}</md-tooltip>{{ day | date:dayLabelFormat }}</div></div><div ng-if='week.length' ng-repeat='week in calendar.weeks track by $index' flex layout='row'><div tabindex='{{ sameMonth(day) ? (day | date:dayFormat:timezone) : 0 }}' ng-repeat='day in week track by $index' ng-click='handleDayClick(day)' flex layout layout-padding ng-class='{&quot;disabled&quot; : isDisabled(day), &quot;active&quot;: isActive(day), &quot;md-whiteframe-12dp&quot;: hover || focus }' ng-focus='focus = true;' ng-blur='focus = false;' ng-mouseleave='hover = false' ng-mouseenter='hover = true'><md-tooltip ng-if='::tooltips()'>{{ day | date:dayTooltipFormat }}</md-tooltip><div>{{ day | date:dayFormat }}</div><div flex ng-bind-html='dataService.data[dayKey(day)]'></div></div></div></md-content></md-content>";
  98. var injectCss = function () {
  99. var styleId = "calendarMdCss";
  100. if (!document.getElementById(styleId)) {
  101. var head = document.getElementsByTagName("head")[0];
  102. var css = document.createElement("style");
  103. css.type = "text/css";
  104. css.id = styleId;
  105. css.innerHTML = "calendar-md md-content>md-content.agenda>*>* :not(:first-child),calendar-md md-content>md-content.calendar>:not(:first-child)>* :last-child{overflow:hidden;text-overflow:ellipsis}calendar-md{display:block;max-height:100%}calendar-md .md-toolbar-tools h2{overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}calendar-md md-content>md-content{border:1px solid rgba(0,0,0,.12)}calendar-md md-content>md-content.agenda>*>*{border-bottom:1px solid rgba(0,0,0,.12)}calendar-md md-content>md-content.agenda>*>.disabled{color:rgba(0,0,0,.3);pointer-events:none;cursor:auto}calendar-md md-content>md-content.agenda>*>* :first-child{padding:12px;width:200px;text-align:right;color:rgba(0,0,0,.75);font-weight:100;overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}calendar-md md-content>md-content>*>*{min-width:48px}calendar-md md-content>md-content.calendar>:first-child{background:rgba(0,0,0,.02);border-bottom:1px solid rgba(0,0,0,.12);margin-right:0;min-height:36px}calendar-md md-content>md-content.calendar>:not(:first-child)>*{border-bottom:1px solid rgba(0,0,0,.12);border-right:1px solid rgba(0,0,0,.12);cursor:pointer}calendar-md md-content>md-content.calendar>:not(:first-child)>:hover{background:rgba(0,0,0,.04)}calendar-md md-content>md-content.calendar>:not(:first-child)>.disabled{color:rgba(0,0,0,.3);pointer-events:none;cursor:auto}calendar-md md-content>md-content.calendar>:not(:first-child)>.active{box-shadow:0 1px 3px 0 rgba(0,0,0,.2),0 1px 1px 0 rgba(0,0,0,.14),0 2px 1px -1px rgba(0,0,0,.12);background:rgba(0,0,0,.02)}calendar-md md-content>md-content.calendar>:not(:first-child)>* :first-child{padding:0}";
  106. head.insertBefore(css, head.firstChild);
  107. }
  108. };
  109. return {
  110. restrict: "E",
  111. scope: {
  112. ngModel: "=?",
  113. template: "&",
  114. templateUrl: "=?",
  115. onDayClick: "=?",
  116. onPrevMonth: "=?",
  117. onNextMonth: "=?",
  118. calendarDirection: "=?",
  119. dayContent: "&?",
  120. timezone: "=?",
  121. titleFormat: "=?",
  122. dayFormat: "=?",
  123. dayLabelFormat: "=?",
  124. dayLabelTooltipFormat: "=?",
  125. dayTooltipFormat: "=?",
  126. weekStartsOn: "=?",
  127. tooltips: "&?",
  128. clearDataCacheOnLoad: "=?",
  129. disableFutureSelection: "=?"
  130. },
  131. link: function ($scope, $element, $attrs) {
  132. // Add the CSS here.
  133. injectCss();
  134. var date = new Date();
  135. var month = parseInt($attrs.startMonth || date.getMonth());
  136. var year = parseInt($attrs.startYear || date.getFullYear());
  137. $scope.columnWeekLayout = "column";
  138. $scope.weekLayout = "row";
  139. $scope.timezone = $scope.timezone || null;
  140. $scope.noCache = $attrs.clearDataCacheOnLoad || false;
  141. // Parse the parent model to determine if it's an array.
  142. // If it is an array, than we'll automatically be able to select
  143. // more than one date.
  144. if ($attrs.ngModel) {
  145. $scope.active = $scope.$parent.$eval($attrs.ngModel);
  146. if ($attrs.ngModel) {
  147. $scope.$watch("$parent." + $attrs.ngModel, function (val) {
  148. $scope.active = val;
  149. });
  150. }
  151. } else {
  152. $scope.active = null;
  153. }
  154. // Set the defaults here.
  155. $scope.titleFormat = $scope.titleFormat || "MMMM yyyy";
  156. $scope.dayLabelFormat = $scope.dayLabelFormat || "EEE";
  157. $scope.dayLabelTooltipFormat = $scope.dayLabelTooltipFormat || "EEEE";
  158. $scope.dayFormat = $scope.dayFormat || "d";
  159. $scope.dayTooltipFormat = $scope.dayTooltipFormat || "fullDate";
  160. $scope.disableFutureSelection = $scope.disableFutureSelection || false;
  161. $scope.sameMonth = function (date) {
  162. var d = angular.copy(date);
  163. return d.getFullYear() === $scope.calendar.year &&
  164. d.getMonth() === $scope.calendar.month;
  165. };
  166. $scope.isDisabled = function (date) {
  167. if ($scope.disableFutureSelection && date > new Date()) { return true; }
  168. return !$scope.sameMonth(date);
  169. };
  170. $scope.calendarDirection = $scope.calendarDirection || "horizontal";
  171. $scope.$watch("calendarDirection", function (val) {
  172. $scope.weekLayout = val === "horizontal" ? "row" : "column";
  173. });
  174. $scope.$watch("weekLayout", function () {
  175. year = $scope.calendar.year;
  176. month = $scope.calendar.month;
  177. bootstrap();
  178. });
  179. var handleCb = function (cb, data) {
  180. (cb || angular.noop)(data);
  181. };
  182. var dateFind = function (arr, date) {
  183. var index = -1;
  184. angular.forEach(arr, function (d, k) {
  185. if (index < 0) {
  186. if (angular.equals(date, d)) {
  187. index = k;
  188. }
  189. }
  190. });
  191. return index;
  192. };
  193. $scope.isActive = function (date) {
  194. var match;
  195. var active = angular.copy($scope.active);
  196. if (!angular.isArray(active)) {
  197. match = angular.equals(date, active);
  198. } else {
  199. match = dateFind(active, date) > -1;
  200. }
  201. return match;
  202. };
  203. $scope.prev = function () {
  204. $scope.calendar.prev();
  205. var data = {
  206. year: $scope.calendar.year,
  207. month: $scope.calendar.month + 1
  208. };
  209. setData();
  210. handleCb($scope.onPrevMonth, data);
  211. };
  212. $scope.next = function () {
  213. $scope.calendar.next();
  214. var data = {
  215. year: $scope.calendar.year,
  216. month: $scope.calendar.month + 1
  217. };
  218. setData();
  219. handleCb($scope.onNextMonth, data);
  220. };
  221. $scope.handleDayClick = function (date) {
  222. if($scope.disableFutureSelection && date > new Date()) {
  223. return;
  224. }
  225. var active = angular.copy($scope.active);
  226. if (angular.isArray(active)) {
  227. var idx = dateFind(active, date);
  228. if (idx > -1) {
  229. active.splice(idx, 1);
  230. } else {
  231. active.push(date);
  232. }
  233. } else {
  234. if (angular.equals(active, date)) {
  235. active = null;
  236. } else {
  237. active = date;
  238. }
  239. }
  240. $scope.active = active;
  241. if ($attrs.ngModel) {
  242. $parse($attrs.ngModel).assign($scope.$parent, angular.copy($scope.active));
  243. }
  244. handleCb($scope.onDayClick, angular.copy(date));
  245. };
  246. // Small helper function to set the contents of the template.
  247. var setTemplate = function (contents) {
  248. $element.html(contents);
  249. $compile($element.contents())($scope);
  250. };
  251. var init = function () {
  252. $scope.calendar = new Calendar(year, month, {
  253. weekStartsOn: $scope.weekStartsOn || 0
  254. });
  255. var deferred = $q.defer();
  256. // Allows fetching of dynamic templates via $http.
  257. if ($scope.templateUrl) {
  258. $http
  259. .get($scope.templateUrl)
  260. .success(deferred.resolve)
  261. .error(deferred.reject);
  262. } else {
  263. deferred.resolve($scope.template() || defaultTemplate);
  264. }
  265. return deferred.promise;
  266. };
  267. $scope.dataService = CalendarData;
  268. // Set the html contents of each date.
  269. var getDayKey = function (date) {
  270. return $scope.dataService.getDayKey(date);
  271. };
  272. $scope.dayKey = getDayKey;
  273. var getDayContent = function (date) {
  274. // Initialize the data in the data array.
  275. if ($scope.noCache) {
  276. $scope.dataService.setDayContent(date, "");
  277. } else {
  278. $scope.dataService.setDayContent(date, ($scope.dataService.data[getDayKey(date)] || ""));
  279. }
  280. var cb = ($scope.dayContent || angular.noop)();
  281. var result = (cb || angular.noop)(date);
  282. // Check for async function. This should support $http.get() and also regular $q.defer() functions.
  283. if (angular.isObject(result) && "function" === typeof result.success) {
  284. result.success(function (html) {
  285. $scope.dataService.setDayContent(date, html);
  286. });
  287. } else if (angular.isObject(result) && "function" === typeof result.then) {
  288. result.then(function (html) {
  289. $scope.dataService.setDayContent(date, html);
  290. });
  291. } else {
  292. $scope.dataService.setDayContent(date, result);
  293. }
  294. };
  295. var setData = function () {
  296. angular.forEach($scope.calendar.weeks, function (week) {
  297. angular.forEach(week, getDayContent);
  298. });
  299. };
  300. window.data = $scope.data;
  301. var bootstrap = function () {
  302. init().then(function (contents) {
  303. setTemplate(contents);
  304. setData();
  305. });
  306. };
  307. $scope.$watch("weekStartsOn", init);
  308. bootstrap();
  309. // These are for tests, don't remove them..
  310. $scope._$$init = init;
  311. $scope._$$setTemplate = setTemplate;
  312. $scope._$$bootstrap = bootstrap;
  313. }
  314. };
  315. }]);