├── README.md ├── bower.json ├── dist ├── angular-simple-calendar.css ├── angular-simple-calendar.js └── calendarTemplate.html └── src ├── angular-simple-calendar.coffee └── angular-simple-calendar.scss /README.md: -------------------------------------------------------------------------------- 1 | # angular-simple-calendar 2 | 3 | [](http://unmaintained.tech/) 4 | 5 | Simple customizable AngularJS calendar with no dependencies. Supports simple events (only date and title). 6 | 7 | ## Install 8 | 9 | Install via bower: 10 | 11 | ```bash 12 | bower install --save angular-simple-calendar 13 | ``` 14 | 15 | Add to index.html: 16 | 17 | ```html 18 | 19 | 20 | ``` 21 | 22 | Inject ``'500tech.simple-calendar'`` into your main module: 23 | 24 | ```javascript 25 | angular.module('App', [ 26 | // ... other dependencies 27 | '500tech.simple-calendar' 28 | ]) 29 | ``` 30 | 31 | ## Usage 32 | 33 | Add ```` directive to your html file. 34 | 35 | Simple calendar takes a few options: 36 | 37 | ```javascript 38 | app.controller('UsersIndexController', ['$scope', function($scope) { 39 | // ... code omitted ... 40 | // Dates can be passed as strings or Date objects 41 | $scope.calendarOptions = { 42 | defaultDate: "2016-10-10", 43 | minDate: new Date(), 44 | maxDate: new Date([2020, 12, 31]), 45 | dayNamesLength: 1, // How to display weekdays (1 for "M", 2 for "Mo", 3 for "Mon"; 9 will show full day names; default is 1) 46 | multiEventDates: true, // Set the calendar to render multiple events in the same day or only one event, default is false 47 | maxEventsPerDay: 1, // Set how many events should the calendar display before showing the 'More Events' message, default is 3; 48 | eventClick: $scope.eventClick, 49 | dateClick: $scope.dateClick 50 | }; 51 | 52 | $scope.events = [ 53 | {title: 'NY', date: new Date([2015, 12, 31])}, 54 | {title: 'ID', date: new Date([2015, 6, 4])} 55 | ]; 56 | }]); 57 | ``` 58 | 59 | ## Events 60 | 61 | You can pass two functions in options: eventClick and/or dateClick. 62 | 63 | If clicked date has an event on it, eventClick will fire, otherwise will dateClick. 64 | 65 | Both functions can get an object with data about clicked date: 66 | 67 | ```javascript 68 | { 69 | year: 2014, 70 | month: 0, // Regular JS month number, starts with 0 for January 71 | day: 23, 72 | event: { // event will only be added for dates that have an event. 73 | title: "Some event", 74 | date: [Date Object] 75 | } 76 | } 77 | ``` 78 | 79 | 80 | ## Customization 81 | 82 | Simple calendar is very easy to customize via css: 83 | 84 | ```scss 85 | simple-calendar { 86 | * { 87 | user-select: none; 88 | } 89 | 90 | .calendar{ 91 | padding: 0; 92 | border: 1px solid #dddddd; 93 | background-color: #fff; 94 | } 95 | 96 | .move-month { cursor: pointer; } 97 | .prev-month { float: left; } 98 | .next-month { float: right; } 99 | 100 | .current-month { 101 | width: 100%; 102 | text-align: center; 103 | padding: 20px 8px; 104 | } 105 | 106 | .week { 107 | height: 30px; 108 | 109 | .day:last-child { 110 | border-right: none; 111 | } 112 | } 113 | 114 | .weekday { text-align: center; } 115 | 116 | .weekday, .day { 117 | display: inline-block; 118 | width: calc(100% / 7); 119 | } 120 | 121 | .day { 122 | height: 30px; 123 | padding: 2px; 124 | border: 1px solid #dddddd; 125 | border-bottom: none; 126 | border-left: none; 127 | overflow: hidden; 128 | 129 | &:hover { cursor: pointer; } 130 | &.default { background-color: lightblue; } 131 | &.event { background-color: #fdf3ea; } 132 | 133 | &.disabled { 134 | cursor: default; 135 | color: silver; 136 | background-color: white; 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | ## License 143 | 144 | MIT Licensed 145 | 146 | Copyright (c) 2014, [500Tech](http://500tech.com) 147 | 148 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 149 | documentation files (the "Software"), to deal in the Software without restriction, including without limitation the 150 | rights to use, copy, modify, merge, publish, distribute, sub-license, and/or sell copies of the Software, and to 151 | permit persons to whom the Software is furnished to do so, subject to the following conditions: 152 | 153 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 154 | Software. 155 | 156 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 157 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 158 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 159 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 160 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-simple-calendar", 3 | "version": "1.1.4", 4 | "authors": [ 5 | "Ilya Gelman " 6 | ], 7 | "description": "Simple customizable AngularJS calendar with no dependencies and timed events", 8 | "main": ["dist/angular-simple-calendar.css", "dist/angular-simple-calendar.js"], 9 | "keywords": [ 10 | "angular", 11 | "angularjs", 12 | "calendar", 13 | "date" 14 | ], 15 | "license": "MIT", 16 | "ignore": [ 17 | "**/.*" 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /dist/angular-simple-calendar.css: -------------------------------------------------------------------------------- 1 | simple-calendar * { 2 | user-select: none; 3 | } 4 | 5 | simple-calendar .calendar{ 6 | padding: 0; 7 | border: 1px solid #dddddd; 8 | user-select: none; 9 | background-color: #fff; 10 | } 11 | 12 | simple-calendar .move-month { 13 | cursor: pointer; 14 | } 15 | 16 | simple-calendar .prev-month { 17 | float: left; 18 | } 19 | 20 | simple-calendar .next-month { 21 | float: right; 22 | } 23 | 24 | simple-calendar .current-month { 25 | text-align: center; 26 | padding: 20px 8px; 27 | } 28 | 29 | /* *********************** */ 30 | /* START Week CSS Settings */ 31 | /* *********************** */ 32 | 33 | simple-calendar .week { 34 | height: 55px; 35 | } 36 | 37 | simple-calendar .week:last-child .day { 38 | height: inherit; 39 | overflow:hidden; 40 | border-bottom: 1px solid #dddddd; 41 | } 42 | 43 | /* ********************* */ 44 | /* END Week CSS Settings */ 45 | /* ********************* */ 46 | 47 | /* ************************** */ 48 | /* START Weekday CSS Settings */ 49 | /* ************************** */ 50 | 51 | simple-calendar .weekday { 52 | text-align: center; 53 | padding-right: 0.9px; 54 | border-top: 1px solid #dddddd; 55 | } 56 | 57 | simple-calendar .weekday:first-child { 58 | text-align: center; 59 | border-left: 1px solid #dddddd; 60 | } 61 | 62 | simple-calendar .weekday:last-child { 63 | text-align: center; 64 | border-right: 1px solid #dddddd; 65 | } 66 | 67 | /* ************************ */ 68 | /* END Weekday CSS Settings */ 69 | /* ************************ */ 70 | 71 | simple-calendar .week .day:last-child { 72 | border-right: 1px solid #dddddd; 73 | } 74 | 75 | simple-calendar .week .day:nth-child(1) { 76 | border-left: 1px solid #dddddd; 77 | } 78 | 79 | simple-calendar .week .day:nth-child(7) { 80 | border-right: 1px solid #dddddd; 81 | } 82 | 83 | simple-calendar .weekday, .day { 84 | display: inline-block; 85 | width: calc(95% / 7); 86 | } 87 | 88 | simple-calendar .day { 89 | height: inherit; 90 | padding-bottom: 2px; 91 | padding-top: 2px; 92 | border: 1px solid #dddddd; 93 | border-bottom: none; 94 | border-left: none; 95 | overflow: hidden; 96 | } 97 | 98 | simple-calendar .day:hover { 99 | cursor: pointer; 100 | } 101 | 102 | simple-calendar .day.default { 103 | background-color: lightblue; 104 | } 105 | 106 | simple-calendar .day.event { 107 | background-color: #fdf3ea; 108 | } 109 | 110 | simple-calendar .day.disabled { 111 | cursor: default; 112 | color: silver; 113 | background-color: white; 114 | } 115 | -------------------------------------------------------------------------------- /dist/angular-simple-calendar.js: -------------------------------------------------------------------------------- 1 | angular.module('500tech.simple-calendar', []).directive('simpleCalendar', function () { 2 | return { 3 | restrict: 'E', 4 | scope: { 5 | options: '=?', 6 | events: '=?' 7 | }, 8 | templateUrl: 'calendarTemplate.html', 9 | controller: ['$scope', function ($scope) { 10 | var MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']; 11 | var WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; 12 | var calculateSelectedDate, calculateWeeks, allowedDate, bindEvent; 13 | 14 | $scope.options = $scope.options || {}; 15 | $scope.options.dayNamesLength = $scope.options.dayNamesLength || 1; 16 | $scope.options.multiEventDates = $scope.options.multiEventDates || false; 17 | $scope.options.maxEventsPerDay = $scope.options.maxEventsPerDay || 3; 18 | 19 | $scope.onClick = function (date) { 20 | if (!date || date.disabled) { return; } 21 | if (date.event) { 22 | $scope.options.eventClick(date); 23 | } else { 24 | $scope.options.dateClick(date); 25 | } 26 | }; 27 | 28 | if ($scope.options.minDate) { 29 | $scope.options.minDate = new Date($scope.options.minDate); 30 | } 31 | 32 | if ($scope.options.maxDate) { 33 | $scope.options.maxDate = new Date($scope.options.maxDate); 34 | } 35 | 36 | bindEvent = function (date) { 37 | if (!date || !$scope.events) { return; } 38 | date.event = []; 39 | $scope.events.forEach(function(event) { 40 | event.date = new Date(event.date); 41 | if (date.year === event.date.getFullYear() && date.month === event.date.getMonth() && date.day === event.date.getDate()) { 42 | date.event.push(event); 43 | } 44 | }); 45 | }; 46 | 47 | allowedDate = function (date) { 48 | if (!$scope.options.minDate && !$scope.options.maxDate) { 49 | return true; 50 | } 51 | var currDate = new Date([date.year, date.month + 1, date.day]); 52 | if ($scope.options.minDate && (currDate < $scope.options.minDate)) { return false; } 53 | if ($scope.options.maxDate && (currDate > $scope.options.maxDate)) { return false; } 54 | return true; 55 | }; 56 | 57 | $scope.allowedPrevMonth = function () { 58 | var prevYear = null; 59 | var prevMonth = null; 60 | if (!$scope.options.minDate) { return true; } 61 | var currMonth = MONTHS.indexOf($scope.selectedMonth); 62 | if (currMonth === 0) { 63 | prevYear = ($scope.selectedYear - 1); 64 | } else { 65 | prevYear = $scope.selectedYear; 66 | } 67 | if (currMonth === 0) { 68 | prevMonth = 11; 69 | } else { 70 | prevMonth = (currMonth - 1); 71 | } 72 | if (prevYear < $scope.options.minDate.getFullYear()) { return false; } 73 | if (prevYear === $scope.options.minDate.getFullYear()) { 74 | if (prevMonth < $scope.options.minDate.getMonth()) { return false; } 75 | } 76 | return true; 77 | }; 78 | 79 | $scope.allowedNextMonth = function () { 80 | var nextYear = null; 81 | var nextMonth = null; 82 | if (!$scope.options.maxDate) { return true; } 83 | var currMonth = MONTHS.indexOf($scope.selectedMonth); 84 | if (currMonth === 11) { 85 | nextYear = ($scope.selectedYear + 1); 86 | } else { 87 | nextYear = $scope.selectedYear; 88 | } 89 | if (currMonth === 11) { 90 | nextMonth = 0; 91 | } else { 92 | nextMonth = (currMonth + 1); 93 | } 94 | if (nextYear > $scope.options.maxDate.getFullYear()) { return false; } 95 | if (nextYear === $scope.options.maxDate.getFullYear()) { 96 | if (nextMonth > $scope.options.maxDate.getMonth()) { return false; } 97 | } 98 | return true; 99 | }; 100 | 101 | calculateWeeks = function () { 102 | $scope.weeks = []; 103 | var week = null; 104 | var daysInCurrentMonth = new Date($scope.selectedYear, MONTHS.indexOf($scope.selectedMonth) + 1, 0).getDate(); 105 | for (var day = 1; day < daysInCurrentMonth + 1; day += 1) { 106 | var dayNumber = new Date($scope.selectedYear, MONTHS.indexOf($scope.selectedMonth), day).getDay(); 107 | week = week || [null, null, null, null, null, null, null]; 108 | week[dayNumber] = { 109 | year: $scope.selectedYear, 110 | month: MONTHS.indexOf($scope.selectedMonth), 111 | day: day 112 | }; 113 | 114 | if (allowedDate(week[dayNumber])) { 115 | if ($scope.events) { bindEvent(week[dayNumber]); } 116 | } else { 117 | week[dayNumber].disabled = true; 118 | } 119 | 120 | if (dayNumber === 6 || day === daysInCurrentMonth) { 121 | $scope.weeks.push(week); 122 | week = undefined; 123 | } 124 | } 125 | }; 126 | 127 | calculateSelectedDate = function () { 128 | if ($scope.options.defaultDate) { 129 | $scope.options._defaultDate = new Date($scope.options.defaultDate); 130 | } else { 131 | $scope.options._defaultDate = new Date(); 132 | } 133 | 134 | $scope.selectedYear = $scope.options._defaultDate.getFullYear(); 135 | $scope.selectedMonth = MONTHS[$scope.options._defaultDate.getMonth()]; 136 | $scope.selectedDay = $scope.options._defaultDate.getDate(); 137 | calculateWeeks(); 138 | }; 139 | 140 | $scope.weekDays = function (size) { 141 | return WEEKDAYS.map(function(name) { return name.slice(0, size) }); 142 | }; 143 | 144 | $scope.isDefaultDate = function (date) { 145 | if (!date) { return; } 146 | return date.year === $scope.options._defaultDate.getFullYear() && 147 | date.month === $scope.options._defaultDate.getMonth() && 148 | date.day === $scope.options._defaultDate.getDate() 149 | }; 150 | 151 | $scope.prevMonth = function () { 152 | if (!$scope.allowedPrevMonth()) { return; } 153 | var currIndex = MONTHS.indexOf($scope.selectedMonth); 154 | if (currIndex === 0) { 155 | $scope.selectedYear -= 1; 156 | $scope.selectedMonth = MONTHS[11]; 157 | } else { 158 | $scope.selectedMonth = MONTHS[currIndex - 1]; 159 | } 160 | calculateWeeks(); 161 | }; 162 | 163 | $scope.nextMonth = function () { 164 | if (!$scope.allowedNextMonth()) { return; } 165 | var currIndex = MONTHS.indexOf($scope.selectedMonth); 166 | if (currIndex === 11) { 167 | $scope.selectedYear += 1; 168 | $scope.selectedMonth = MONTHS[0]; 169 | } else { 170 | $scope.selectedMonth = MONTHS[currIndex + 1]; 171 | } 172 | calculateWeeks(); 173 | }; 174 | 175 | $scope.$watch('options.defaultDate', function() { 176 | calculateSelectedDate(); 177 | }); 178 | 179 | $scope.$watch('events', function() { 180 | calculateWeeks(); 181 | }); 182 | 183 | }] 184 | } 185 | }); 186 | -------------------------------------------------------------------------------- /dist/calendarTemplate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | ‹ 4 | 5 | {{ selectedMonth }} 6 | 7 | {{ selectedYear }} 8 | 9 | › 10 | 11 | 12 | 13 | 14 | {{ day }} 15 | 16 | 17 | 18 | 22 | {{ date.day || " " }} 23 | {{ events.title || " " }}More Events 24 | {{ date.event[0].title || " " }} 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/angular-simple-calendar.coffee: -------------------------------------------------------------------------------- 1 | angular.module('500tech.calendar', []).directive 'calendar', -> 2 | restrict: 'E' 3 | scope: 4 | options: '=?' 5 | events: '=?' 6 | template: ''' 7 | 8 | 9 | 10 | ‹ 11 | 12 | {{ selectedMonth }} 13 | 14 | {{ selectedYear }} 15 | 16 | › 17 | 18 | 19 | 20 | {{ day }} 21 | 22 | 23 | 24 | 28 | {{ date.day || ' ' }} 29 | {{ date.event.title || ' ' }} 30 | 31 | 32 | 33 | 34 | ''' 35 | controller: [ "$scope", ($scope) -> 36 | MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'] 37 | WEEKDAYS = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'] 38 | 39 | $scope.options ?= {} 40 | $scope.options.dayNamesLength ?= 3 41 | 42 | $scope.onClick = (date) -> 43 | return if !date || date.disabled 44 | if date.event 45 | $scope.options.eventClick(date) 46 | else 47 | $scope.options.dateClick(date) 48 | 49 | if $scope.options.minDate 50 | $scope.options.minDate = new Date($scope.options.minDate) 51 | 52 | if $scope.options.maxDate 53 | $scope.options.maxDate = new Date($scope.options.maxDate) 54 | 55 | bindEvent = (date) -> 56 | return unless date || $scope.events 57 | for event in $scope.events 58 | event.date = new Date(event.date) 59 | date.event = event if date.year == event.date.getFullYear() && date.month == event.date.getMonth() && date.day == event.date.getDate() 60 | 61 | allowedDate = (date) -> 62 | return true unless $scope.options.minDate || $scope.options.maxDate 63 | currDate = new Date([date.year, date.month + 1, date.day]) 64 | return false if $scope.options.minDate && currDate < $scope.options.minDate 65 | return false if $scope.options.maxDate && currDate > $scope.options.maxDate 66 | true 67 | 68 | $scope.allowedPrevMonth = -> 69 | return true unless $scope.options.minDate 70 | currMonth = MONTHS.indexOf($scope.selectedMonth) 71 | prevYear = if currMonth == 0 then ($scope.selectedYear - 1) else $scope.selectedYear 72 | prevMonth = if currMonth == 0 then 11 else (currMonth - 1) 73 | return false if prevYear < $scope.options.minDate.getFullYear() 74 | if prevYear == $scope.options.minDate.getFullYear() 75 | return false if prevMonth < $scope.options.minDate.getMonth() 76 | true 77 | 78 | $scope.allowedNextMonth = -> 79 | return true unless $scope.options.maxDate 80 | currMonth = MONTHS.indexOf($scope.selectedMonth) 81 | nextYear = if currMonth == 11 then ($scope.selectedYear + 1) else $scope.selectedYear 82 | nextMonth = if currMonth == 11 then 0 else (currMonth + 1) 83 | return false if nextYear > $scope.options.maxDate.getFullYear() 84 | if nextYear == $scope.options.maxDate.getFullYear() 85 | return false if nextMonth > $scope.options.maxDate.getMonth() 86 | true 87 | 88 | calculateWeeks = -> 89 | $scope.weeks = [] 90 | week = null 91 | daysInCurrentMonth = new Date($scope.selectedYear, MONTHS.indexOf($scope.selectedMonth) + 1, 0).getDate() 92 | for day in [1..daysInCurrentMonth] 93 | dayNumber = new Date($scope.selectedYear, MONTHS.indexOf($scope.selectedMonth), day).getDay() 94 | week ?= [null, null, null, null, null, null, null] 95 | week[dayNumber] = 96 | year: $scope.selectedYear 97 | month: MONTHS.indexOf($scope.selectedMonth) 98 | day: day 99 | if allowedDate(week[dayNumber]) 100 | bindEvent(week[dayNumber]) if $scope.events 101 | else 102 | week[dayNumber].disabled = true 103 | 104 | if dayNumber == 6 || day == daysInCurrentMonth 105 | $scope.weeks.push(week) 106 | week = undefined 107 | 108 | calculateSelectedDate = -> 109 | if $scope.options.defaultDate 110 | $scope.options._defaultDate = new Date($scope.options.defaultDate) 111 | else 112 | $scope.options._defaultDate = new Date() 113 | 114 | $scope.selectedYear = $scope.options._defaultDate.getFullYear() 115 | $scope.selectedMonth = MONTHS[$scope.options._defaultDate.getMonth()] 116 | $scope.selectedDay = $scope.options._defaultDate.getDate() 117 | calculateWeeks() 118 | 119 | $scope.weekDays = (size = 9) -> 120 | WEEKDAYS.map (name) -> name.slice(0, size) 121 | 122 | $scope.isDefaultDate = (date) -> 123 | return unless date 124 | date.year == $scope.options._defaultDate.getFullYear() && 125 | date.month == $scope.options._defaultDate.getMonth() && 126 | date.day == $scope.options._defaultDate.getDate() 127 | 128 | $scope.prevMonth = -> 129 | return unless $scope.allowedPrevMonth() 130 | currIndex = MONTHS.indexOf($scope.selectedMonth) 131 | if currIndex == 0 132 | $scope.selectedYear -= 1 133 | $scope.selectedMonth = MONTHS[11] 134 | else 135 | $scope.selectedMonth = MONTHS[currIndex - 1] 136 | calculateWeeks() 137 | 138 | $scope.nextMonth = -> 139 | return unless $scope.allowedNextMonth() 140 | currIndex = MONTHS.indexOf($scope.selectedMonth) 141 | if currIndex == 11 142 | $scope.selectedYear += 1 143 | $scope.selectedMonth = MONTHS[0] 144 | else 145 | $scope.selectedMonth = MONTHS[currIndex + 1] 146 | calculateWeeks() 147 | 148 | $scope.$watch 'options.defaultDate', -> 149 | calculateSelectedDate() 150 | 151 | $scope.$watch 'events', -> 152 | calculateWeeks() 153 | ] -------------------------------------------------------------------------------- /src/angular-simple-calendar.scss: -------------------------------------------------------------------------------- 1 | simple-calendar { 2 | * { 3 | user-select: none; 4 | } 5 | 6 | .calendar{ 7 | padding: 0; 8 | border: 1px solid #dddddd; 9 | background-color: #fff; 10 | } 11 | 12 | .move-month { cursor: pointer; } 13 | .prev-month { float: left; } 14 | .next-month { float: right; } 15 | 16 | .current-month { 17 | width: 100%; 18 | text-align: center; 19 | padding: 20px 8px; 20 | } 21 | 22 | .week { 23 | height: 30px; 24 | 25 | .day:last-child { 26 | border-right: none; 27 | } 28 | } 29 | 30 | .weekday { text-align: center; } 31 | 32 | .weekday, .day { 33 | display: inline-block; 34 | width: calc(100% / 7); 35 | } 36 | 37 | .day { 38 | height: 30px; 39 | padding: 2px; 40 | border: 1px solid #dddddd; 41 | border-bottom: none; 42 | border-left: none; 43 | overflow: hidden; 44 | 45 | &:hover { cursor: pointer; } 46 | &.default { background-color: lightblue; } 47 | &.event { background-color: #fdf3ea; } 48 | 49 | &.disabled { 50 | cursor: default; 51 | color: silver; 52 | background-color: white; 53 | } 54 | } 55 | } --------------------------------------------------------------------------------