├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── app ├── app.js ├── eventCalendar-primary.scss ├── index.html ├── pages │ └── home │ │ ├── home.controller.js │ │ └── home.html └── style.css ├── bower.json ├── dist ├── angular-material-event-calendar.css ├── angular-material-event-calendar.js ├── angular-material-event-calendar.min.css └── angular-material-event-calendar.min.js ├── gulp ├── config.js ├── cssBuild.js ├── indexBuild.js └── jsBuild.js ├── gulpfile.js ├── index.js ├── karma.conf.js ├── package.json └── src ├── eventCalendar-colors.scss ├── eventCalendar-theme.scss ├── eventCalendar.js ├── eventCalendar.scss ├── icons ├── ic_close_black_24px.svg └── ic_keyboard_arrow_right_black_24px.svg └── js ├── eventCalendar.directive.js ├── eventCalendarBuilder.service.js ├── eventCalendarMonth.directive.js ├── eventCalendarNext.directive.js ├── eventCalendarPrev.directive.js ├── eventCalendarTitle.directive.js ├── eventCalendarToday.directive.js └── utij.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | 39 | bower_components 40 | 41 | **/.DS_Store 42 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 0.0.6 (2016-09-24) 3 | 4 | * Add `auto-height` attribute 5 | 6 | 7 | 8 | ## 0.0.6 (2016-09-07) 9 | 10 | * Add Today button directive 11 | * Add `md-create-event-click` attribute that will trigger on month cell click. This passes a `$date` paramater 12 | * Add `md-show-create-link` attribute to display a create link on cell hover 13 | * Add `md-create-disabled` to disable and hide create 14 | * Fix event display on last day of week 15 | 16 | 17 | 18 | 19 | ## 0.0.5 (2016-09-05) 20 | 21 | * Add previos and next month days to calendar view. These contain events 22 | * Update theme to default to white for header background 23 | * Add `md-primary` styling for header background and selected events. You can add the `md-primary` class to the `me-event-calendar` directive. 24 | * Fix event sorting in month view to fit more events in a day 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Ben Rubin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # angular-material-event-calendar 2 | A calendar module that is based on material design concepts. 3 | The calendar module was built to run as a standalone component, and alongside of ngMaterial. If you use this component with ngMaterial then it will use the themes you have setup and use the $$dateLocal settings to display and format the dates. 4 | 5 |
6 | Angular Material calendar 7 |
8 |
9 | 10 | 11 | Quick Links: 12 | * [Installation](#installaton) 13 | * [Building](#building) 14 | * [Run Tests](#tests) 15 | * [Usage](#usage) 16 | * [Colors](#colors) 17 | * [Documentation](#documentation) 18 | * [FAQ](#faq) 19 | 20 | 21 | 22 | ## Installation 23 | 24 | #### Bower 25 | 26 | Change to your project's root directory. 27 | 28 | ```bash 29 | # To install latest 30 | bower install angular-material-event-calendar 31 | 32 | # To install latest and update bower.json 33 | bower install angular-material-event-calendar --save 34 | ``` 35 | 36 | 37 | #### Npm 38 | 39 | Change to your project's root directory. 40 | 41 | ```bash 42 | # To install latest 43 | npm install angular-material-event-calendar 44 | 45 | # To install latest and update package.json 46 | npm install angular-material-event-calendar --save 47 | ``` 48 | 49 | 50 | #### setup 51 | 52 | install modules 53 | 54 | ```bash 55 | # install npm modules 56 | npm install 57 | 58 | # install bower components 59 | bower install 60 | ``` 61 | 62 | Include the `material.components.eventCalendar` module as a dependency in your application. 63 | 64 | ```javascript 65 | // with ngMaterial 66 | angular.module('myApp', ['ngMaterial', 'material.components.eventCalendar']); 67 | 68 | // without ngMaterial 69 | angular.module('myApp', ['material.components.eventCalendar']); 70 | ``` 71 | 72 | 73 | 74 | 75 | ## Building 76 | 77 | You can easily build using gulp. 78 | 79 | The built files will be created in the `dist` folder 80 | 81 | Run the **gulp** tasks: 82 | 83 | ```bash 84 | # To run locally. This will kick of the watch process 85 | # navigate to `localhost:8080` 86 | gulp 87 | 88 | # To build the js and css files to the `/build` directory 89 | gulp build 90 | ``` 91 | 92 | 93 | ## Run Tests 94 | 95 | Test using Karma 96 | Run the **gulp** tasks: 97 | 98 | ```bash 99 | gulp test 100 | ``` 101 | 102 | 103 | 104 | 105 | ## Usage 106 | 107 | **Example** 108 | 109 | ```html 110 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | ``` 128 | 129 | 130 | 131 | ## Colors 132 | 133 | **With Angular Material** 134 | 135 | If you want to have the header and selected elements use the primary color for their backgrounds the just add the `md-primary` class 136 | ```html 137 | 140 | 141 | ``` 142 | 143 | 144 | **Without Angular Material** 145 | 146 | If you want to change the header and selected event background colors you add this [scss](https://github.com/B-3PO/angular-material-event-calendar/blob/master/app/eventCalendar-primary.scss) file after the `angular-material-event-calendar.css` file 147 | 148 | Primary Color scss: [Click Here](https://github.com/B-3PO/angular-material-event-calendar/blob/master/app/eventCalendar-primary.scss) 149 | 150 | 151 | ## Documentation 152 | 153 | To add eventCalendar to you angular-material project, include the `material.components.eventCalendar` module as a dependency in your application. 154 | 155 | ```javascript 156 | angular.module('myApp', ['ngMaterial', 'material.components.eventCalendar']); 157 | ``` 158 | 159 | 160 | * [Event Object](#eventobject) 161 | * [mdEventCalendar](#mdEventCalendar) 162 | * [mdEventCalendarHeader](#mdEventCalendarHeader) 163 | * [mdEventCalendarNext](#mdEventCalendarNext) 164 | * [mdEventCalendarPrev](#mdEventCalendarPrev) 165 | * [mdEventCalendarTitle](#mdEventCalendarTitle) 166 | * [mdEventCalendarToday](#mdEventCalendarToday) 167 | 168 | 169 | 170 | 171 | ### Event Object 172 | 173 | ## Event Object 174 | 175 | ```javascript 176 | { 177 | title: 'Event Title', 178 | start: new Date(), 179 | end: new Date(), 180 | allDay: false 181 | } 182 | ``` 183 | 184 | #### Attributes 185 | 186 | | Param | Type | Details | 187 | | :--: | :--: | :--: | 188 | | title | string |

Event Tile

| 189 | | start | date/iso |

Start date

| 190 | | end | date/iso= |

Optional end date

| 191 | | allDay | boolean |

If set to true, no time will be displayed on event

| 192 | 193 | 194 | 195 | ### Directives 196 | 197 | 198 | ## mdEventCalendar 199 | 200 | ```html 201 | 211 | ... 212 | 213 | ``` 214 | 215 | #### Attributes 216 | 217 | | Param | Type | Details | 218 | | :--: | :--: | :--: | 219 | | ng-model | model= |

Optional model to hold selected event object

| 220 | | md-events | array |

Array of events

| 221 | | md-label | string=title |

Property name for title display

| 222 | | md-event-click | function |

Function to be called on event click. You can pass in $selectedEvent to get the event object you clicked on

| 223 | | md-create-event-click | function |

Function to be called when empty area of day is clicked. You can pass in $date to get the days date you clicked on

| 224 | | md-show-create-link | boolean |

Show `Create` in the top right corner when cell is hovered over

| 225 | | md-create-disabled | boolean |

Hides create link and disabled create click event

| 226 | | auto-height | number |

Calendar will fill to the bottom of the window. You can pass it a number(pixels) as an offset

| 227 | 228 | 229 | 230 | 231 | ## mdEventCalendarHeader 232 | 233 | The header is a container for the previous, next, and title directives. You can also add other elements to this. 234 | ```html 235 | 236 | ... 237 | 238 | ``` 239 | 240 | #### Classes 241 | 242 | | Param | Type | Details | 243 | | :--: | :--: | :--: | 244 | | md-center | css |

Center content inside of header

| 245 | 246 | 247 | ## mdEventCalendarNext 248 | 249 | This is the next arrow that will advance the current view by month/week/day. You can add this the header in any order 250 | ```html 251 | 252 | 253 | ``` 254 | 255 | 256 | ## mdEventCalendarPrev 257 | 258 | This is the prev arrow that will change the current view by month/week/day. You can add this the header in any order 259 | ```html 260 | 261 | 262 | ``` 263 | 264 | 265 | ## mdEventCalendarTitle 266 | 267 | This title will show the appropriate title for the calendar view 268 | ```html 269 | 270 | 271 | ``` 272 | 273 | ## mdEventCalendarToday 274 | 275 | A button that can be clicked to take the month to the current month. This button is disabled if you are already on the current month 276 | ```html 277 | 278 | 279 | ``` 280 | 281 | 282 | 283 | 284 | 285 | # FAQ 286 | 287 | #### Do i need to use ngMaterial? 288 | No, but you will not get the lovely theme colors. 289 | 290 | 291 | #### Where is my week/day views? 292 | On their way, this component is under active development. 293 | 294 | #### Will this support mobile? 295 | Mobile is in the roadmap and will be released in future versions. 296 | -------------------------------------------------------------------------------- /app/app.js: -------------------------------------------------------------------------------- 1 | angular.module('eventCalendarApp', [ 2 | 'ngRoute', 3 | 'ngAnimate', 4 | 'ngMaterial', 5 | 'material.components.eventCalendar' 6 | ]) 7 | .config(configApp); 8 | 9 | 10 | configApp.$inject = ['$routeProvider']; 11 | function configApp($routeProvider) { 12 | $routeProvider 13 | .when('/', { 14 | templateUrl: 'pages/home/home.html', 15 | controller: 'HomeController', 16 | controllerAs: 'vm' 17 | }) 18 | .otherwise('/'); 19 | } 20 | -------------------------------------------------------------------------------- /app/eventCalendar-primary.scss: -------------------------------------------------------------------------------- 1 | $primary: #1458b1; 2 | $primaryContrastFont: #FFF; 3 | 4 | 5 | md-event-calendar:not(._md) { 6 | md-event-calendar-header { 7 | color: $primaryContrastFont; 8 | background: $primary; 9 | 10 | md-event-calendar-next, md-event-calendar-prev { 11 | .md-arrow svg { 12 | fill: $primaryContrastFont; 13 | } 14 | } 15 | } 16 | 17 | 18 | md-event-calendar-month { 19 | .md-event-calendar-month-row-header { 20 | color: $primaryContrastFont; 21 | background: $primary; 22 | } 23 | 24 | .md-event-calendar-month-row { 25 | .md-event-calendar-month-cell { 26 | &.today { 27 | box-shadow: inset 0px 0px 0px 1px $primary; 28 | 29 | .md-event-calendar-month-cell-content { 30 | .md-event-calendar-cell-data-label { 31 | color: $primary; 32 | } 33 | } 34 | 35 | .md-event-calendar-month-cell-divider { 36 | border-color: $primary; 37 | } 38 | } 39 | } 40 | } 41 | 42 | 43 | .md-event-calendar-cell-event { 44 | &.md-selected { 45 | color: $primaryContrastFont; 46 | background: $primary; 47 | 48 | &.md-continue-left, &.md-end-left { 49 | &:after { 50 | border-right-color: $primary; 51 | } 52 | } 53 | 54 | &.md-continue-right, &.md-start-right { 55 | &:after { 56 | border-left-color: $primary; 57 | } 58 | } 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Angular Material Test 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /app/pages/home/home.controller.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('eventCalendarApp') 3 | .controller('HomeController', HomeController); 4 | 5 | 6 | function HomeController($scope, $timeout) { 7 | 8 | $scope.events = [ 9 | { 10 | start: getDate(-6, 10), 11 | end: getDate(-6, 11), 12 | title: 'Event 1' 13 | }, 14 | { 15 | start: getDate(0, 10), 16 | end: getDate(1, 11), 17 | title: 'Event 1' 18 | }, 19 | { 20 | start: getDate(1, 11), 21 | end: getDate(2, 12), 22 | title: 'Event 2' 23 | }, 24 | { 25 | start: getDate(2, 12), 26 | end: getDate(3, 13), 27 | title: 'Event 3' 28 | }, 29 | { 30 | start: getDate(4, 12), 31 | end: getDate(5, 13), 32 | title: 'Event 4' 33 | }, 34 | { 35 | start: getDate(5, 12), 36 | end: getDate(6, 13), 37 | title: 'Event 5' 38 | }, 39 | { 40 | start: getDate(6, 12), 41 | end: getDate(6, 13), 42 | title: 'Event 6' 43 | }, 44 | { 45 | start: getDate(6, 12), 46 | allDay: true, 47 | title: 'Event 7' 48 | }, 49 | 50 | 51 | 52 | 53 | { 54 | start: getDate(8, 12), 55 | end: getDate(8, 13), 56 | title: 'Event 5' 57 | }, 58 | { 59 | start: getDate(8, 12), 60 | end: getDate(8, 13), 61 | title: 'Event 6' 62 | }, 63 | { 64 | start: getDate(8, 12), 65 | allDay: true, 66 | title: 'Event 7' 67 | } 68 | ]; 69 | $scope.selected = $scope.events[0]; 70 | 71 | $scope.eventClicked = function (item) { 72 | console.log(item); 73 | }; 74 | 75 | $scope.createClicked = function (date) { 76 | console.log(date); 77 | }; 78 | 79 | function getDate(offsetDays, hour) { 80 | offsetDays = offsetDays || 0; 81 | var offset = offsetDays * 24 * 60 * 60 * 1000; 82 | var date = new Date(new Date().getTime() + offset); 83 | if (hour) { date.setHours(hour); } 84 | return date; 85 | } 86 | 87 | 88 | $scope.dis = false; 89 | // $timeout(function () { 90 | // $scope.events.push({ 91 | // date: new Date(new Date().getTime() + 48 * 60 * 60 * 1000), 92 | // label: 'Event Three' 93 | // }); 94 | // }, 1000) 95 | } 96 | -------------------------------------------------------------------------------- /app/pages/home/home.html: -------------------------------------------------------------------------------- 1 | 11 | 12 |
13 | 14 | 15 | 16 |
17 | 18 | 19 |
20 |
21 |
22 | -------------------------------------------------------------------------------- /app/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | overflow: hidden; 3 | max-width: 100%; 4 | max-height: 100%; 5 | } 6 | 7 | .view-container { 8 | padding-left: 12px; 9 | padding-right: 12px; 10 | } 11 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-material-event-calendar", 3 | "version": "0.1.4", 4 | "description": "Angular material event calander component", 5 | "main": [ 6 | "dist/angular-material-event-calendar.js", 7 | "dist/angular-material-event-calendar.css" 8 | ], 9 | "repository": { 10 | "type": "git", 11 | "url": "git@github.com:B-3PO/angular-material-event-calendar.git" 12 | }, 13 | "authors": [ 14 | "Ben Rubin" 15 | ], 16 | "license": "MIT", 17 | "keywords": [ 18 | "material", 19 | "material-design", 20 | "design", 21 | "angular", 22 | "component", 23 | "calendar", 24 | "event", 25 | "md" 26 | ], 27 | "ignore": [ 28 | "**/.*", 29 | "node_modules", 30 | "bower_components", 31 | "app", 32 | "src", 33 | "gulp" 34 | ], 35 | "dependencies": { 36 | "angular": "^1.5.7", 37 | "angular-animate": "^1.5.7", 38 | "angular-messages": "^1.5.7", 39 | "angular-aria": "^1.5.7", 40 | "angular-material": "^1.1.0" 41 | }, 42 | "devDependencies": { 43 | "angular-mocks": "^1.5.7", 44 | "angular-route": "^1.5.7" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /dist/angular-material-event-calendar.css: -------------------------------------------------------------------------------- 1 | md-event-calendar:not(._md) md-event-calendar-header { 2 | color: #666; 3 | background: #FFF; 4 | border-color: #DDD; } 5 | md-event-calendar:not(._md) md-event-calendar-header md-event-calendar-next .md-arrow svg, md-event-calendar:not(._md) md-event-calendar-header md-event-calendar-prev .md-arrow svg { 6 | fill: #666; } 7 | 8 | md-event-calendar:not(._md) .md-button:not([disabled]) { 9 | color: #333; } 10 | md-event-calendar:not(._md) .md-button:not([disabled]):hover { 11 | background: rgba(158, 158, 158, 0.2); } 12 | 13 | md-event-calendar:not(._md) .md-button[disabled] { 14 | color: #CCC; } 15 | 16 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row-header { 17 | color: #999; 18 | background: #FFF; 19 | border-color: #DDD; } 20 | 21 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row { 22 | background: #FFF; 23 | border-color: #DDD; } 24 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell-divider { 25 | border-color: #DDD; } 26 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell { 27 | border-color: #DDD; } 28 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-create-link { 29 | color: #4189b8; } 30 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label { 31 | color: #999; } 32 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-event-show-more-link { 33 | color: #4189b8; } 34 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.different-month { 35 | background: #F5F5F5; } 36 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today { 37 | box-shadow: inset 0px 0px 0px 1px #AAA; } 38 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label { 39 | color: #666; } 40 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today .md-event-calendar-month-cell-divider { 41 | border-color: #AAA; } 42 | md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell:last-child { 43 | border-color: #DDD; } 44 | 45 | md-event-calendar:not(._md) .md-event-calendar-cell-event { 46 | background: #DDD; 47 | color: #666; } 48 | md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected { 49 | color: #EEE; 50 | background: #888; } 51 | md-event-calendar:not(._md) .md-event-calendar-cell-event.md-continue-left:after, md-event-calendar:not(._md) .md-event-calendar-cell-event.md-end-left:after { 52 | border-right-color: #DDD; } 53 | md-event-calendar:not(._md) .md-event-calendar-cell-event.md-continue-right:after, md-event-calendar:not(._md) .md-event-calendar-cell-event.md-start-right:after { 54 | border-left-color: #DDD; } 55 | md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected.md-continue-left:after, md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected.md-end-left:after { 56 | border-right-color: #888; } 57 | md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected.md-continue-right:after, md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected.md-start-right:after { 58 | border-left-color: #888; } 59 | 60 | md-event-calendar:not(._md) .md-event-calendar-show-more-container .md-event-calendar-show-more-date-label { 61 | color: #999; } 62 | 63 | md-event-calendar:not(._md) .md-event-calendar-show-more-container .md-event-calendar-show-more-close svg { 64 | fill: #999; } 65 | 66 | md-event-calendar { 67 | display: block; } 68 | md-event-calendar md-event-calendar-header { 69 | -webkit-box-orient: horizontal; 70 | -webkit-box-direction: normal; 71 | -ms-flex-direction: row; 72 | flex-direction: row; 73 | display: -webkit-box; 74 | display: -ms-flexbox; 75 | display: flex; 76 | line-height: 64px; 77 | -webkit-box-align: center; 78 | -ms-flex-align: center; 79 | align-items: center; 80 | border-style: solid; 81 | border-width: 1px 1px 0 1px; } 82 | md-event-calendar md-event-calendar-header.md-center { 83 | -webkit-box-pack: center; 84 | -ms-flex-pack: center; 85 | justify-content: center; } 86 | md-event-calendar md-event-calendar-header md-event-calendar-title { 87 | display: block; 88 | min-width: 170px; 89 | text-align: center; 90 | font-size: 20px; } 91 | md-event-calendar md-event-calendar-header md-event-calendar-next, md-event-calendar md-event-calendar-header md-event-calendar-prev { 92 | display: block; } 93 | md-event-calendar md-event-calendar-header md-event-calendar-next .md-arrow, md-event-calendar md-event-calendar-header md-event-calendar-prev .md-arrow { 94 | cursor: pointer; 95 | height: 24px; 96 | width: 24px; } 97 | md-event-calendar md-event-calendar-header md-event-calendar-next .md-arrow.md-left-arrow, md-event-calendar md-event-calendar-header md-event-calendar-prev .md-arrow.md-left-arrow { 98 | -webkit-transform: rotate(180deg); 99 | transform: rotate(180deg); } 100 | 101 | md-event-calendar .md-event-calendar-month-cell-content .md-event-calendar-create-link { 102 | opacity: 0; } 103 | 104 | md-event-calendar .md-event-calendar-month-cell-content:hover .md-event-calendar-create-link { 105 | opacity: 1; } 106 | 107 | md-event-calendar md-event-calendar-month.md-event-hover .md-event-calendar-month-cell-content .md-event-calendar-create-link { 108 | opacity: 0; } 109 | 110 | md-event-calendar md-event-calendar-month.fitted { 111 | display: -webkit-box; 112 | display: -ms-flexbox; 113 | display: flex; 114 | -webkit-box-orient: vertical; 115 | -webkit-box-direction: normal; 116 | -ms-flex-direction: column; 117 | flex-direction: column; } 118 | 119 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row-header { 120 | display: -webkit-box; 121 | display: -ms-flexbox; 122 | display: flex; 123 | -webkit-box-orient: horizontal; 124 | -webkit-box-direction: normal; 125 | -ms-flex-direction: row; 126 | flex-direction: row; 127 | min-height: 36px; 128 | height: 36px; 129 | -webkit-box-align: end; 130 | -ms-flex-align: end; 131 | align-items: flex-end; 132 | font-size: 12px; 133 | font-weight: 500; 134 | padding-bottom: 12px; 135 | border-style: solid; 136 | border-width: 0 1px 1px 1px; } 137 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row-header .md-event-calendar-month-cell-header { 138 | -webkit-box-flex: 1; 139 | -ms-flex: 1; 140 | flex: 1; 141 | padding-left: 6px; } 142 | 143 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row { 144 | -webkit-box-flex: 1; 145 | -ms-flex: 1; 146 | flex: 1; 147 | display: -webkit-box; 148 | display: -ms-flexbox; 149 | display: flex; 150 | -webkit-box-orient: horizontal; 151 | -webkit-box-direction: normal; 152 | -ms-flex-direction: row; 153 | flex-direction: row; 154 | border-style: solid; 155 | border-width: 0 0 1px 0; } 156 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell { 157 | position: relative; 158 | -webkit-box-flex: 1; 159 | -ms-flex: 1; 160 | flex: 1; } 161 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-spacer { 162 | margin-top: 100%; } 163 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-divider { 164 | position: absolute; 165 | top: 0; 166 | bottom: 0; 167 | left: 0; 168 | border-style: solid; 169 | border-width: 0 1px 0 0; } 170 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content { 171 | position: absolute; 172 | top: 0; 173 | bottom: 0; 174 | left: 0; 175 | right: 0; } 176 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-create-link { 177 | -ms-flex-item-align: center; 178 | align-self: center; 179 | text-transform: uppercase; 180 | font-size: 14px; 181 | font-weight: 500; 182 | padding-right: 12px; 183 | cursor: pointer; 184 | -webkit-transition: opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); 185 | transition: opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); } 186 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label { 187 | font-size: 13px; 188 | padding: 8px; 189 | -webkit-box-flex: 1; 190 | -ms-flex: 1; 191 | flex: 1; } 192 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-event-spacer { 193 | margin: 4px 0 4px 0; 194 | height: 23px; } 195 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-event-show-more-link { 196 | font-size: 13px; 197 | padding: 4px; 198 | padding-left: 8px; 199 | cursor: pointer; } 200 | md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell:last-child { 201 | border-style: solid; 202 | border-width: 0 1px 0 0; } 203 | 204 | md-event-calendar .md-event-calendar-cell-event { 205 | font-size: 12px; 206 | min-height: 15px; 207 | padding: 4px; 208 | cursor: pointer; } 209 | md-event-calendar .md-event-calendar-cell-event.md-single { 210 | margin: 4px; 211 | border-radius: 2px; 212 | text-overflow: ellipsis; 213 | white-space: nowrap; 214 | overflow: hidden; } 215 | md-event-calendar .md-event-calendar-cell-event.md-start { 216 | margin: 4px 0 4px 4px; 217 | border-radius: 2px 0 0 2px; 218 | white-space: nowrap; 219 | z-index: 1; 220 | position: relative; } 221 | md-event-calendar .md-event-calendar-cell-event.md-start-right { 222 | margin: 4px 13px 4px 4px; 223 | border-radius: 2px 0 0 2px; } 224 | md-event-calendar .md-event-calendar-cell-event.md-end { 225 | margin: 4px 4px 4px 0; 226 | border-radius: 0 2px 2px 0; } 227 | md-event-calendar .md-event-calendar-cell-event.md-end-left { 228 | margin: 4px 4px 4px 13px; 229 | border-radius: 0 2px 2px 0; } 230 | md-event-calendar .md-event-calendar-cell-event.md-continue, md-event-calendar .md-event-calendar-cell-event.md-continue-both { 231 | margin: 4px 0 4px 0; 232 | border-radius: 0; } 233 | md-event-calendar .md-event-calendar-cell-event.md-continue-right { 234 | margin: 4px 13px 4px 0; 235 | border-radius: 0; 236 | white-space: nowrap; } 237 | md-event-calendar .md-event-calendar-cell-event.md-continue-left { 238 | margin: 4px 0 4px 13px; 239 | border-radius: 0; 240 | white-space: nowrap; } 241 | md-event-calendar .md-event-calendar-cell-event.md-continue-right:after, md-event-calendar .md-event-calendar-cell-event.md-start-right:after { 242 | content: ''; 243 | position: absolute; 244 | height: 0; 245 | width: 0; 246 | right: 0; 247 | margin-top: -4px; 248 | border-top: 12px solid transparent; 249 | border-bottom: 11.5px solid transparent; 250 | border-left: 13px solid #EEE; } 251 | md-event-calendar .md-event-calendar-cell-event.md-continue-left:after, md-event-calendar .md-event-calendar-cell-event.md-end-left:after { 252 | content: ''; 253 | position: absolute; 254 | height: 0; 255 | width: 0; 256 | left: 0; 257 | margin-top: -4px; 258 | border-top: 12px solid transparent; 259 | border-bottom: 11.5px solid transparent; 260 | border-right: 13px solid #EEE; } 261 | md-event-calendar .md-event-calendar-cell-event .md-event-calendar-cell-event-time { 262 | font-weight: 500; 263 | padding-right: 6px; 264 | pointer-events: none; } 265 | md-event-calendar .md-event-calendar-cell-event span { 266 | pointer-events: none; } 267 | 268 | md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-single, md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-end, md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-start-right { 269 | padding-left: 16px; } 270 | 271 | md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-single, md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-end, md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-start-right { 272 | margin-left: 0; } 273 | 274 | md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-single, md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-end, md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-end-left { 275 | margin-right: 0; } 276 | 277 | md-event-calendar .md-event-calendar-show-more-container { 278 | opacity: 0; 279 | position: absolute; 280 | top: 0; 281 | left: 1px; 282 | width: 180px; 283 | background: #FFF; 284 | padding: 12px; 285 | padding-top: 7px; 286 | border-radius: 2px; 287 | z-index: 9; 288 | box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2), 0 13px 19px 2px rgba(0, 0, 0, 0.14), 0 5px 24px 4px rgba(0, 0, 0, 0.12); } 289 | md-event-calendar .md-event-calendar-show-more-container .md-event-calendar-show-more-content { 290 | position: relative; } 291 | md-event-calendar .md-event-calendar-show-more-container.show { 292 | opacity: 1; } 293 | md-event-calendar .md-event-calendar-show-more-container.show:not(.no-transition) { 294 | -webkit-transition: opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); 295 | transition: opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); } 296 | md-event-calendar .md-event-calendar-show-more-container .md-event-calendar-show-more-close { 297 | display: -webkit-box; 298 | display: -ms-flexbox; 299 | display: flex; 300 | position: absolute; 301 | top: 6px; 302 | right: 7px; 303 | width: 16px; 304 | height: 16px; 305 | cursor: pointer; } 306 | md-event-calendar .md-event-calendar-show-more-container .md-event-calendar-show-more-close svg { 307 | pointer-events: none; } 308 | md-event-calendar .md-event-calendar-show-more-container .md-event-calendar-show-more-date-label { 309 | font-size: 13px; 310 | padding: 6px; 311 | margin-left: -11px; 312 | margin-top: -5px; } 313 | 314 | md-event-calendar.md-create-disabled md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-create-link { 315 | display: none; } 316 | -------------------------------------------------------------------------------- /dist/angular-material-event-calendar.js: -------------------------------------------------------------------------------- 1 | (function(){"use strict";/** 2 | * @ngdoc module 3 | * @name material.components.eventCalendar 4 | * 5 | * @description 6 | * Calendar Component 7 | */ 8 | addEventCalendarTheme.$inject = ["$injector", "$provide", "EVENT_CALENDAR_THEME"]; 9 | angular 10 | .module('material.components.eventCalendar', []) 11 | .config(addEventCalendarTheme); 12 | 13 | 14 | /*@ngInject*/ 15 | function addEventCalendarTheme($injector, $provide, EVENT_CALENDAR_THEME) { 16 | var $mdThemingProvider; 17 | 18 | // if using angular material, then register the event theme css 19 | if ($injector.has('$mdThemingProvider')) { 20 | $mdThemingProvider = $injector.get('$mdThemingProvider'); 21 | $mdThemingProvider.registerStyles(EVENT_CALENDAR_THEME); 22 | } else { 23 | $provide.decorator('$$rAF', ["$delegate", rAFDecorator]); 24 | } 25 | } 26 | 27 | 28 | // polly fill rAF throttle if not using angular material 29 | function rAFDecorator($delegate) { 30 | $delegate.throttle = function(cb) { 31 | var queuedArgs, alreadyQueued, queueCb, context; 32 | return function debounced() { 33 | queuedArgs = arguments; 34 | context = this; 35 | queueCb = cb; 36 | if (!alreadyQueued) { 37 | alreadyQueued = true; 38 | $delegate(function() { 39 | queueCb.apply(context, Array.prototype.slice.call(queuedArgs)); 40 | alreadyQueued = false; 41 | }); 42 | } 43 | }; 44 | }; 45 | return $delegate; 46 | } 47 | }()); 48 | (function(){"use strict";angular.module("material.components.eventCalendar").run(["$templateCache", function($templateCache) {$templateCache.put("icons/ic_close_black_24px.svg","\n \n \n"); 49 | $templateCache.put("icons/ic_keyboard_arrow_right_black_24px.svg","\n \n \n\n");}]);}()); 50 | (function(){"use strict";angular.module("material.components.eventCalendar") 51 | 52 | .constant("EVENT_CALENDAR_THEME", "md-event-calendar._md md-event-calendar-header {\n color: '{{foreground-1}}';\n background: '{{background-hue-1}}';\n border-color: '{{foreground-4}}'; }\n md-event-calendar._md md-event-calendar-header md-event-calendar-next .md-arrow svg, md-event-calendar._md md-event-calendar-header md-event-calendar-prev .md-arrow svg {\n fill: '{{foreground-2}}'; }\n\nmd-event-calendar._md md-event-calendar-month .md-event-calendar-month-row-header {\n color: '{{foreground-3}}';\n background: '{{background-hue-1}}';\n border-color: '{{foreground-4}}'; }\n\nmd-event-calendar._md md-event-calendar-month .md-event-calendar-month-row {\n background: '{{background-hue-1}}';\n border-color: '{{foreground-4}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell-divider {\n border-color: '{{foreground-4}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell {\n border-color: '{{foreground-4}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-create-link {\n color: '{{primary-default}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label {\n color: '{{foreground-3}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-event-show-more-link {\n color: '{{primary-default}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.different-month {\n background: '{{background-hue-2}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today {\n box-shadow: inset 0px 0px 0px 1px '{{primary-default}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label {\n color: '{{primary-default}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell:last-child {\n border-color: '{{foreground-4}}'; }\n\nmd-event-calendar._md .md-event-calendar-cell-event {\n background: '{{foreground-4}}';\n color: '{{background-900}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-selected {\n color: #EEE;\n background: '{{primary-default}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-continue-left:after, md-event-calendar._md .md-event-calendar-cell-event.md-end-left:after {\n border-right-color: '{{foreground-4}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-continue-right:after, md-event-calendar._md .md-event-calendar-cell-event.md-start-right:after {\n border-left-color: '{{foreground-4}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-selected.md-continue-left:after, md-event-calendar._md .md-event-calendar-cell-event.md-selected.md-end-left:after {\n border-right-color: '{{primary-default}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-selected.md-continue-right:after, md-event-calendar._md .md-event-calendar-cell-event.md-selected.md-start-right:after {\n border-left-color: '{{primary-default}}'; }\n\nmd-event-calendar._md .md-event-calendar-show-more-container .md-event-calendar-show-more-date-label {\n color: '{{foreground-3}}'; }\n\nmd-event-calendar._md .md-event-calendar-show-more-container .md-event-calendar-show-more-close svg {\n fill: '{{foreground-1}}'; }\n\nmd-event-calendar._md.md-primary md-event-calendar-header {\n color: '{{background-100}}';\n background: '{{primary-default}}'; }\n md-event-calendar._md.md-primary md-event-calendar-header md-event-calendar-next .md-arrow svg, md-event-calendar._md.md-primary md-event-calendar-header md-event-calendar-prev .md-arrow svg {\n fill: '{{background-100}}'; }\n\nmd-event-calendar._md.md-primary md-event-calendar-month .md-event-calendar-month-row-header {\n color: '{{background-100}}';\n background: '{{primary-default}}'; }\n") 53 | 54 | ;}()); 55 | (function(){"use strict"; 56 | eventCalendarDirective.$inject = ["$injector", "$parse"];angular 57 | .module('material.components.eventCalendar') 58 | .directive('mdEventCalendar', eventCalendarDirective); 59 | 60 | 61 | /** 62 | * @ngdoc directive 63 | * @name mdEventCalendar 64 | * @module material.components.eventCalendar 65 | * 66 | * @restrict E 67 | **/ 68 | function eventCalendarDirective($injector, $parse) { 69 | controller.$inject = ["$$mdEventCalendarUtil", "$element", "$attrs"]; 70 | var $mdTheming = $injector.has('$mdTheming') ? $injector.get('$mdTheming') : undefined; 71 | var directive = { 72 | restrict: 'E', 73 | require: ['mdEventCalendar', '?ngModel'], 74 | scope: { 75 | events: '=mdEvents' 76 | }, 77 | compile: compile, 78 | controller: controller, 79 | controllerAs: 'mdEventCalendar', 80 | bindToController: true 81 | }; 82 | return directive; 83 | 84 | 85 | function compile(tElement, tAttr) { 86 | var eventClickFunc = tAttr.mdEventClick ? $parse(tAttr.mdEventClick, null, true) : undefined; 87 | var createEventClickFunc = tAttr.mdCreateEventClick ? $parse(tAttr.mdCreateEventClick, null, true) : undefined; 88 | var mdCreateDisabled = tAttr.mdCreateDisabled ? $parse(tAttr.mdCreateDisabled) : undefined; 89 | tElement.append(''); 90 | 91 | return function postLink(scope, element, attrs, ctrls) { 92 | var createDisabled = false; 93 | var mdEventCalendarCtrl = ctrls[0]; 94 | var ngModelCtrl = ctrls[1]; 95 | if ($mdTheming) { 96 | element.addClass('_md'); 97 | $mdTheming(element); 98 | } 99 | 100 | mdEventCalendarCtrl.isCreateDisabled = isCreateDisabled; 101 | mdEventCalendarCtrl.callEventClick = callEventClick; 102 | mdEventCalendarCtrl.createEventClick = createEventClick; 103 | 104 | if (ngModelCtrl) { 105 | ngModelCtrl.$render = render; 106 | mdEventCalendarCtrl.ngModelCtrl = ngModelCtrl; 107 | } 108 | 109 | 110 | function render() { 111 | var viewValue = ngModelCtrl.$viewValue || ngModelCtrl.$modelValue || []; 112 | mdEventCalendarCtrl.selectedEvents = [].concat(viewValue); 113 | } 114 | 115 | 116 | function callEventClick(e, eventItem) { 117 | if (!attrs.mdEventClick) { return; } 118 | eventClickFunc(scope.$parent, {$event: e, $selectedEvent: eventItem}); 119 | } 120 | 121 | function createEventClick(e, date) { 122 | if (!attrs.mdCreateEventClick) { return; } 123 | createEventClickFunc(scope.$parent, {$event: e, $date: date}); 124 | } 125 | 126 | function isCreateDisabled() { 127 | return createDisabled; 128 | } 129 | 130 | // watch for create being disabled 131 | if (mdCreateDisabled) { 132 | scope.$watch(function () { return mdCreateDisabled(scope.$parent); }, function (value) { 133 | createDisabled = value; 134 | element.toggleClass('md-create-disabled', value); 135 | }); 136 | 137 | // if no string was given check to see if the attr exists 138 | } else if (tAttr.mdCreateDisabled !== undefined) { 139 | createDisabled = true; 140 | element.addClass('md-create-disabled'); 141 | } 142 | }; 143 | } 144 | 145 | 146 | /*@ngInject*/ 147 | function controller($$mdEventCalendarUtil, $element, $attrs) { 148 | /*jshint validthis:true*/ 149 | var vm = this; 150 | 151 | vm.$element = $element; 152 | vm.labelProperty = $attrs.mdLabel || 'title'; 153 | vm.selectedEvents = []; 154 | vm.today = $$mdEventCalendarUtil.createDateAtMidnight(); 155 | vm.date = $$mdEventCalendarUtil.createDateAtMidnight(); 156 | vm.isToday = $$mdEventCalendarUtil.isSameDay(vm.date, new Date()); 157 | vm.monthDisplay = $$mdEventCalendarUtil.months[vm.date.getMonth()]; 158 | vm.yearDisplay = vm.date.getFullYear(); 159 | vm.isTodayDisabled = true; 160 | vm.showCreateLink = $attrs.mdShowCreateLink !== undefined && $attrs.mdShowCreateLink !== 'false'; 161 | vm.nextMonth = nextMonth; 162 | vm.previousMonth = previousMonth; 163 | vm.selectEvent = selectEvent; 164 | vm.setToday = setToday; 165 | vm.autoHeight = $attrs.autoHeight !== undefined; 166 | vm.fitted = $attrs.fitted !== undefined; 167 | vm.offset = vm.autoHeight === false || $attrs.autoHeight === '' || isNaN($attrs.autoHeight.replace('px', '')) ? 0 : parseInt($attrs.autoHeight.replace('px', '')); 168 | 169 | 170 | function nextMonth() { 171 | vm.date = $$mdEventCalendarUtil.getDateInNextMonth(vm.date); 172 | vm.monthDisplay = $$mdEventCalendarUtil.months[vm.date.getMonth()]; 173 | vm.yearDisplay = vm.date.getFullYear(); 174 | vm.isTodayDisabled = vm.date.getMonth() === (new Date()).getMonth(); 175 | } 176 | 177 | 178 | function previousMonth() { 179 | vm.date = $$mdEventCalendarUtil.getDateInPreviousMonth(vm.date); 180 | vm.monthDisplay = $$mdEventCalendarUtil.months[vm.date.getMonth()]; 181 | vm.yearDisplay = vm.date.getFullYear(); 182 | vm.isTodayDisabled = vm.date.getMonth() === (new Date()).getMonth(); 183 | } 184 | 185 | function setToday() { 186 | vm.date = new Date(); 187 | vm.monthDisplay = $$mdEventCalendarUtil.months[vm.date.getMonth()]; 188 | vm.yearDisplay = vm.date.getFullYear(); 189 | vm.isTodayDisabled = true; 190 | } 191 | 192 | 193 | function selectEvent(e, id) { 194 | // TODO create hashkeys for all events and store in reference object 195 | var value = vm.events.filter(function (item) { 196 | return item.$$mdEventId === id; 197 | }); 198 | 199 | if (vm.ngModelCtrl) { 200 | vm.ngModelCtrl.$setViewValue(value[0]); 201 | vm.ngModelCtrl.$render(); 202 | } 203 | vm.callEventClick(e, value[0]); 204 | 205 | return true; 206 | } 207 | } 208 | } 209 | }()); 210 | (function(){"use strict"; 211 | mdEventCalendarBuilderService.$inject = ["$$mdEventCalendarUtil", "$templateCache"];angular 212 | .module('material.components.eventCalendar') 213 | .factory('$$mdEventCalendarBuilder', mdEventCalendarBuilderService); 214 | 215 | 216 | var nextId = 0; 217 | 218 | /** 219 | * @ngdoc service 220 | * @name $$mdEventCalendarBuilder 221 | * @module material.components.eventCalendar 222 | **/ 223 | /*@ngInject*/ 224 | function mdEventCalendarBuilderService($$mdEventCalendarUtil, $templateCache) { 225 | var service = { 226 | month: month, 227 | showMore: showMore 228 | }; 229 | return service; 230 | 231 | 232 | 233 | function showMore(opts) { 234 | var date = opts.date; 235 | var selected = opts.selected || []; 236 | var events = opts.events ? filterEventsOnDay(date, opts.events) : []; 237 | var labelProperty = opts.labelProperty; 238 | var showMoreBody = document.createDocumentFragment(); 239 | var container = document.createElement('div'); 240 | container.classList.add('md-event-calendar-show-more-container'); 241 | var content = document.createElement('div'); 242 | content.classList.add('md-event-calendar-show-more-content'); 243 | var dateLabel = document.createElement('div'); 244 | dateLabel.classList.add('md-event-calendar-show-more-date-label'); 245 | dateLabel.textContent = $$mdEventCalendarUtil.dates[date.getDate()]; 246 | var closeButton = document.createElement('div'); 247 | closeButton.classList.add('md-event-calendar-show-more-close'); 248 | closeButton.innerHTML = $templateCache.get('icons/ic_close_black_24px.svg'); 249 | closeButton.setAttribute('md-show-more-close', 'true'); 250 | container.appendChild(dateLabel); 251 | container.appendChild(closeButton); 252 | container.appendChild(content); 253 | showMoreBody.appendChild(container); 254 | 255 | events.forEach(function (item) { 256 | var eventElement; 257 | var isStartThisDay = $$mdEventCalendarUtil.isSameDay(date, item.start); 258 | var isEndThisDay = $$mdEventCalendarUtil.isValidDate(item.end) ? $$mdEventCalendarUtil.isSameDay(date, item.end) : true; 259 | var eventOptions = { 260 | labelProperty: labelProperty, 261 | selected: selected 262 | }; 263 | 264 | if (isStartThisDay && isEndThisDay) { 265 | eventElement = createEventElement({className: 'single', hasLabel: true}, item, eventOptions); 266 | } else if (isStartThisDay) { 267 | eventElement = createEventElement({className: 'start-right', hasLabel: true}, item, eventOptions); 268 | } else if (isEndThisDay) { 269 | eventElement = createEventElement({className: 'end-left', hasLabel: true}, item, eventOptions); 270 | } else { 271 | eventElement = createEventElement({className: 'continue', hasLabel: true}, item, eventOptions); 272 | } 273 | 274 | content.appendChild(eventElement); 275 | }); 276 | 277 | 278 | var bounds = opts.cell.getBoundingClientRect(); 279 | var cellTop = opts.cell.offsetTop; 280 | var cellLeft = opts.cell.offsetLeft; 281 | container.style.top = cellTop+'px'; 282 | container.style.left = cellLeft+'px'; 283 | 284 | return showMoreBody; 285 | } 286 | 287 | 288 | function filterEventsOnDay(date, events) { 289 | return !events || !events.length ? [] : events.filter(function (item) { 290 | return $$mdEventCalendarUtil.isDateWithinRange(date, item.start, item.end || item.start); 291 | }).sort(function(a, b) { 292 | a = new Date(a.start); 293 | b = new Date(b.start); 294 | return a > b ? 1 : a < b ? -1 : 0; 295 | }); 296 | } 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | function month(options) { 310 | var calendarStartDate; 311 | var lastCalendarDayNum; 312 | var d = 0; 313 | var rowNumber = 1; 314 | var firstCalendarDay = true; 315 | var lastCalendarDay = false; 316 | var today = $$mdEventCalendarUtil.createDateAtMidnight(); 317 | var date = $$mdEventCalendarUtil.isValidDate(options.date) ? options.date : new Date(); 318 | var firstDayOfMonth = $$mdEventCalendarUtil.getFirstDateOfMonth(date); 319 | var firstDayOfTheWeek = (firstDayOfMonth.getDay() + 7) % 7; 320 | var numberOfDaysInMonth = $$mdEventCalendarUtil.getNumberOfDaysInMonth(date); 321 | var events = filterCurrentCalendar(date, options.events); 322 | events.forEach(cleanEvent); 323 | var selected = options.selected || []; 324 | var monthElement = createMonthElement(); 325 | var row = createRowElement(); 326 | monthElement.appendChild(row); 327 | var cellSize = options.cellHeight - 48; 328 | var maxEvents = Math.floor(cellSize / 24); 329 | 330 | 331 | // days from last month 332 | if (firstDayOfTheWeek > 0) { 333 | calendarStartDate = $$mdEventCalendarUtil.getFirstDateOfMonth(date); 334 | calendarStartDate.setDate(calendarStartDate.getDate() - firstDayOfTheWeek); 335 | while (d < firstDayOfTheWeek) { 336 | row.appendChild(createCellElement(getCellOptions(calendarStartDate, d, true))); 337 | firstCalendarDay = false; 338 | d += 1; 339 | calendarStartDate.setDate(calendarStartDate.getDate() + 1); 340 | } 341 | } 342 | 343 | 344 | 345 | // Add a cell for each day of the month, keeping track of the day of the week so that 346 | // we know when to start a new row. 347 | var dayOfWeek = firstDayOfTheWeek; 348 | var iterationDate = firstDayOfMonth; 349 | d = 1; 350 | while (d <= numberOfDaysInMonth) { 351 | // If we've reached the end of the week, start a new row. 352 | if (dayOfWeek === 7) { 353 | dayOfWeek = 0; 354 | row = createRowElement(); 355 | firstCalendarDay = false; 356 | monthElement.appendChild(row); 357 | } 358 | 359 | if (dayOfWeek === 6 && d === numberOfDaysInMonth) { 360 | lastCalendarDay = true; 361 | } 362 | 363 | iterationDate.setDate(d); 364 | row.appendChild(createCellElement(getCellOptions(iterationDate, dayOfWeek))); 365 | firstCalendarDay = false; 366 | dayOfWeek += 1; 367 | d += 1; 368 | } 369 | 370 | 371 | lastCalendarDayNum = d; 372 | // fill in the rest of the row with next month 373 | while (row.childNodes.length < 7) { 374 | if (dayOfWeek === 6) { 375 | lastCalendarDay = true; 376 | } 377 | iterationDate.setDate((d - lastCalendarDayNum) + 1); 378 | row.appendChild(createCellElement(getCellOptions(iterationDate, dayOfWeek, true))); 379 | dayOfWeek += 1; 380 | d += 1; 381 | } 382 | 383 | 384 | return monthElement; 385 | 386 | 387 | function getCellOptions(cellDate, dayOfWeek, differentMonth) { 388 | return { 389 | date: cellDate, // date for day on calendar 390 | today: today, // todays date at midnight 391 | dayOfWeek: dayOfWeek, // 0-6 (sun-sat) 392 | differentMonth: differentMonth || false, // previous or next month overflow days 393 | events: events, // events arr 394 | isFirstDay: firstCalendarDay, // is first day of current month view. not the first day of month(unless that is sunday) 395 | isLastDay: lastCalendarDay, // last day of calenday. not last day of month (unless sat) 396 | maxEvents: maxEvents, // max events that can be displayed in a day cell. based on cell size 397 | selected: selected, // array of selected events. from ngModel 398 | labelProperty: options.labelProperty, // name of the label property. default: title 399 | showCreateLink: options.showCreateLink // show create link on hover of day cell 400 | }; 401 | } 402 | } 403 | 404 | 405 | 406 | function createCellElement(options) { 407 | var cell = document.createElement('div'); 408 | cell.classList.add('md-event-calendar-month-cell'); 409 | cell.setAttribute('md-date', options.date); 410 | if (options.differentMonth === true) { cell.classList.add('different-month'); } 411 | if ($$mdEventCalendarUtil.isSameDay(options.date, options.today)) { cell.classList.add('today'); } 412 | 413 | var cellSpacer = document.createElement('div'); 414 | cellSpacer.classList.add('md-event-calendar-month-cell-spacer'); 415 | cell.appendChild(cellSpacer); 416 | 417 | var divider = document.createElement('div'); 418 | divider.classList.add('md-event-calendar-month-cell-divider'); 419 | cell.appendChild(divider); 420 | 421 | var cellContent = document.createElement('div'); 422 | cellContent.setAttribute('md-create-event', ''); 423 | cellContent.classList.add('md-event-calendar-month-cell-content'); 424 | cell.appendChild(cellContent); 425 | 426 | var cellHeader = document.createElement('div'); 427 | cellHeader.setAttribute('md-create-event', ''); 428 | cellHeader.classList.add('layout-row'); 429 | cellContent.appendChild(cellHeader); 430 | 431 | var dateLabel = document.createElement('div'); 432 | dateLabel.setAttribute('md-create-event', ''); 433 | dateLabel.classList.add('md-event-calendar-cell-data-label'); 434 | dateLabel.textContent = $$mdEventCalendarUtil.dates[options.date.getDate()]; 435 | cellHeader.appendChild(dateLabel); 436 | 437 | if (options.showCreateLink === true) { 438 | var createLink = document.createElement('div'); 439 | createLink.setAttribute('md-create-event', ''); 440 | createLink.classList.add('md-event-calendar-create-link'); 441 | createLink.textContent = 'Create'; 442 | cellHeader.appendChild(createLink); 443 | } 444 | 445 | createEventElements(cellContent, options); 446 | 447 | return cell; 448 | } 449 | 450 | 451 | 452 | function createEventElements(cellContent, options) { 453 | var i; 454 | var place = 0; 455 | var hasEvents = false; 456 | var matchingEvents = getEventsInRange(options.date, options.events); 457 | matchingEvents = setEventPlaces(matchingEvents, options.dayOfWeek); 458 | matchingEvents.every(function (eventItem, pos) { 459 | var type = getEventDisplayType(eventItem, options); 460 | var placeDiff = eventItem.$$place - place; 461 | hasEvents = true; 462 | place = eventItem.$$place + 1; 463 | i = 0; 464 | // add spacer items for overflow events from last day 465 | while (i < placeDiff) { 466 | if (place >= options.maxEvents) { 467 | cellContent.appendChild(createShowMore(matchingEvents.length - pos, options.date)); 468 | return false; 469 | } 470 | cellContent.appendChild(createEventSpacerElement()); 471 | i += 1; 472 | } 473 | 474 | if (place >= options.maxEvents) { 475 | cellContent.appendChild(createShowMore(matchingEvents.length - pos, options.date)); 476 | return false; 477 | } 478 | cellContent.appendChild(createEventElement(type, eventItem, options)); 479 | return true; 480 | }); 481 | 482 | if (hasEvents === true) { 483 | cellContent.classList.add('md-has-events'); 484 | } 485 | } 486 | 487 | 488 | function createShowMore(num, date) { 489 | var showMoreElement = document.createElement('div'); 490 | showMoreElement.classList.add('md-event-calendar-cell-event-show-more-link'); 491 | showMoreElement.textContent = num+' more'; 492 | showMoreElement.setAttribute('md-show-more', date.toISOString()); 493 | return showMoreElement; 494 | } 495 | 496 | 497 | function createEventSpacerElement() { 498 | var spacer = document.createElement('div'); 499 | spacer.classList.add('md-event-calendar-cell-event-spacer'); 500 | return spacer; 501 | } 502 | 503 | function createEventElement(type, eventItem, options) { 504 | var hash = getHashValue(eventItem); 505 | var eventElement = document.createElement('div'); 506 | eventElement.setAttribute('md-event-id', hash); 507 | eventElement.classList.add('md-event-calendar-cell-event'); 508 | eventElement.classList.add('md-'+type.className); 509 | if (eventItem.customClass) { eventElement.classList.add(eventItem.customClass); } 510 | 511 | if (type.hasLabel === true) { 512 | // do not show time for allDay events 513 | if (type.allDay !== true) { 514 | var dateLabelTime = document.createElement('span'); 515 | dateLabelTime.classList.add('md-event-calendar-cell-event-time'); 516 | dateLabelTime.textContent = $$mdEventCalendarUtil.formatEventTime(eventItem.start); 517 | eventElement.appendChild(dateLabelTime); 518 | } 519 | 520 | var dateLabelText = document.createElement('span'); 521 | dateLabelText.textContent = eventItem[options.labelProperty]; 522 | eventElement.appendChild(dateLabelText); 523 | } 524 | 525 | options.selected.every(function (sel) { 526 | if (sel.$$mdEventId !== undefined && sel.$$mdEventId === eventItem.$$mdEventId) { 527 | eventElement.classList.add('md-selected'); 528 | return false; 529 | } 530 | return true; 531 | }); 532 | 533 | return eventElement; 534 | } 535 | 536 | 537 | 538 | function getHashValue(value) { 539 | if (angular.isObject(value)) { 540 | return 'object_' + (value.$$mdEventId || (value.$$mdEventId = ++nextId)); 541 | } 542 | return 'id_' + (++nextId); 543 | } 544 | 545 | 546 | function getEventDisplayType(item, options) { 547 | var className; 548 | var hasLabel; 549 | 550 | var isStartThisDay = $$mdEventCalendarUtil.isSameDay(options.date, item.start); 551 | var isEndThisDay = $$mdEventCalendarUtil.isValidDate(item.end) ? $$mdEventCalendarUtil.isSameDay(options.date, item.end) : true; 552 | 553 | // single day event 554 | if (isStartThisDay && (options.allDay || isEndThisDay)) { 555 | className = 'single'; 556 | hasLabel = true; 557 | 558 | // starts today on last day of week 559 | } else if (isStartThisDay && options.dayOfWeek === 6) { 560 | className = 'start-right'; 561 | hasLabel = true; 562 | 563 | // starts today 564 | } else if (isStartThisDay) { 565 | className = 'start'; 566 | hasLabel = true; 567 | 568 | // ends on sunday 569 | } else if (isEndThisDay && options.dayOfWeek === 0) { 570 | className = 'end-left'; 571 | hasLabel = true; 572 | 573 | // last day of event 574 | } else if (isEndThisDay) { 575 | className = 'end'; 576 | hasLabel = options.isFirstDay; // add label if event is continuing from last month 577 | 578 | // continuation on sunday 579 | } else if (options.dayOfWeek === 0) { 580 | className = 'continue-left'; 581 | hasLabel = true; 582 | 583 | // continue on sat 584 | } else if (options.dayOfWeek === 6) { 585 | className = 'continue-right'; 586 | hasLabel = false; 587 | 588 | // continuation 589 | } else { 590 | className = 'continue'; 591 | hasLabel = false; 592 | } 593 | 594 | return { 595 | className: className, 596 | hasLabel: hasLabel, 597 | allDay: item.allDay || false 598 | }; 599 | } 600 | 601 | function getEventsInRange(date, events) { 602 | return events.filter(function (item) { 603 | return $$mdEventCalendarUtil.isDateWithinRange(date, item.start, item.end || item.start); 604 | }); 605 | } 606 | 607 | function setEventPlaces(events, dayOfWeek) { 608 | var takenPlaces = []; 609 | var sorted = events.sort(function (a, b) { 610 | if (a.end > b.end) { return -1; } 611 | if (a.end < b.end) { return 1; } 612 | return 0; 613 | }); 614 | 615 | // if not first day of week then get event palces. this is for dates that come from previous days 616 | // otherwise reset places 617 | sorted.forEach(function (item) { 618 | if (dayOfWeek === 0) { item.$$place = undefined; } 619 | else if (item.$$place !== undefined) { takenPlaces.push(item.$$place); } 620 | }); 621 | 622 | // fill in places that have not been set 623 | sorted.forEach(function(item) { 624 | if (item.$$place === undefined) { item.$$place = getPlace(); } 625 | }); 626 | 627 | // sort on places 628 | return sorted.sort(function(a, b) { 629 | if (a.$$place > b.$$place) { return 1; } 630 | if (a.$$place < b.$$place) { return -1; } 631 | return 0; 632 | }); 633 | 634 | 635 | // find lowest place not taken 636 | function getPlace() { 637 | var place = 0; 638 | while (takenPlaces.indexOf(place) !== -1) { 639 | place++; 640 | } 641 | takenPlaces.push(place); 642 | return place; 643 | } 644 | } 645 | 646 | 647 | function createMonthElement() { 648 | var monthBody = document.createDocumentFragment(); 649 | var headerRow = document.createElement('div'); 650 | headerRow.classList.add('md-event-calendar-month-row-header'); 651 | monthBody.appendChild(headerRow); 652 | 653 | // add header day labels 654 | $$mdEventCalendarUtil.days.forEach(function (name) { 655 | var dayHeader = document.createElement('div'); 656 | dayHeader.classList.add('md-event-calendar-month-cell-header'); 657 | dayHeader.textContent = name.slice(0,3).toLowerCase(); 658 | headerRow.appendChild(dayHeader); 659 | }); 660 | 661 | return monthBody; 662 | } 663 | 664 | function createRowElement() { 665 | var row = document.createElement('div'); 666 | row.classList.add("md-event-calendar-month-row"); 667 | return row; 668 | } 669 | 670 | 671 | function filterCurrentCalendar(date, events) { 672 | if (!events || !events.length) { return []; } 673 | // back fill 6 days for posibility of last month days showing up 674 | var start = $$mdEventCalendarUtil.getFirstDateOfMonth(date).getDate(-6); 675 | // front fill 6 days for posibility of next month days showing up 676 | var end = $$mdEventCalendarUtil.getFirstDateOfMonth(date).getDate(37); 677 | 678 | return events.filter(function (item) { 679 | if (!$$mdEventCalendarUtil.isValidDate(item.start)) { return false; } 680 | if ($$mdEventCalendarUtil.isDateWithinRange(item.start, start, end)) { return true; } 681 | if (!$$mdEventCalendarUtil.isValidDate(item.end)) { return false; } 682 | if ($$mdEventCalendarUtil.isDateWithinRange(item.end, start, end)) { return true; } 683 | return false; 684 | }).sort(function(a, b) { 685 | a = new Date(a.start); 686 | b = new Date(b.start); 687 | return a > b ? 1 : a < b ? -1 : 0; 688 | }); 689 | } 690 | 691 | 692 | function cleanEvent(item) { 693 | item.$$hide = undefined; 694 | item.$$place = undefined; 695 | } 696 | } 697 | }()); 698 | (function(){"use strict"; 699 | eventCalendarMonthDirective.$inject = ["$$mdEventCalendarBuilder", "$window", "$$rAF", "$timeout"];angular 700 | .module('material.components.eventCalendar') 701 | .directive('mdEventCalendarMonth', eventCalendarMonthDirective); 702 | 703 | 704 | /** 705 | * @ngdoc directive 706 | * @name eventCalendarMonthDirective 707 | * @module material.components.eventCalendar 708 | * 709 | * @restrict E 710 | **/ 711 | /*@ngInject*/ 712 | function eventCalendarMonthDirective($$mdEventCalendarBuilder, $window, $$rAF, $timeout) { 713 | var directive = { 714 | restrict: 'E', 715 | require: '^mdEventCalendar', 716 | link: link 717 | }; 718 | return directive; 719 | 720 | 721 | function link(scope, element, attrs, ctrl) { 722 | var showMoreData; 723 | 724 | var mdEventCalendarCtrl = ctrl; 725 | var rebuildThrottle = $$rAF.throttle(function () { 726 | scope.$evalAsync(function () { 727 | setAutoHeight(); 728 | element.toggleClass('fitted', mdEventCalendarCtrl.fitted); 729 | buildView(); 730 | }); 731 | }); 732 | 733 | scope.$watch(function () { return mdEventCalendarCtrl.date; }, buildView); 734 | scope.$watch(function () { return mdEventCalendarCtrl.events; }, function (newValue, oldValue) { 735 | if (newValue === oldValue) { return; } 736 | buildView(); 737 | }, true); 738 | scope.$watch(function () { return mdEventCalendarCtrl.selectedEvents; }, function (newValue, oldValue) { 739 | if (newValue === oldValue) { return; } 740 | buildView(); 741 | }, true); 742 | 743 | angular.element($window).on('resize', rebuildThrottle); 744 | scope.$on('$destroy', function () { 745 | angular.element($window).off('resize', rebuildThrottle); 746 | }); 747 | 748 | $$rAF(function () { 749 | setAutoHeight(); 750 | element.toggleClass('fitted', mdEventCalendarCtrl.fitted); 751 | }); 752 | 753 | function setAutoHeight() { 754 | if (!mdEventCalendarCtrl.autoHeight) { return; } 755 | mdEventCalendarCtrl.fitted = true; 756 | var top = element[0].getBoundingClientRect().top; 757 | var height = $window.innerHeight - top - mdEventCalendarCtrl.offset; 758 | element.css('height', height+'px'); 759 | } 760 | 761 | hideCreateLinkOnEventItemHover(); 762 | 763 | // When user mouses over an existing event, add a class of md-event-hover to 764 | // the month element, so that the create link is hidden from view. 765 | function hideCreateLinkOnEventItemHover() { 766 | element.on('mouseenter', function () { 767 | element.on('mousemove', checkForEventItemRAF); 768 | }); 769 | 770 | element.on('mouseleave', function () { 771 | element.off('mousemove', checkForEventItemRAF); 772 | element.removeClass('md-event-hover'); 773 | }); 774 | 775 | var lastHoverItem; 776 | var checkForEventItemRAF = $$rAF.throttle(checkForEventItem); 777 | function checkForEventItem(e) { 778 | if (mdEventCalendarCtrl.isCreateDisabled() === true) { return; } 779 | if (lastHoverItem === e.target) { return; } 780 | lastHoverItem = e.target; 781 | 782 | var targetIsEvent = !!e.target.getAttribute('md-event-id'); 783 | 784 | element.toggleClass('md-event-hover', targetIsEvent); 785 | } 786 | } 787 | 788 | element.on('click', function (e) { 789 | if (mdEventCalendarCtrl.isCreateDisabled() === true) { return; } 790 | 791 | var eventId = e.target.getAttribute('md-event-id'); 792 | var showMore = e.target.getAttribute('md-show-more'); 793 | var showMoreClose = e.target.getAttribute('md-show-more-close'); 794 | var createEvent = e.target.getAttribute('md-create-event') !== null; 795 | 796 | if (eventId) { 797 | var eventItem = getIdFromHash(eventId); 798 | scope.$apply(function () { 799 | mdEventCalendarCtrl.selectEvent(e, getIdFromHash(eventId)); 800 | }); 801 | return; 802 | } 803 | 804 | removeShowMore(true); 805 | if (showMore) { 806 | addShowMore(new Date(showMore)); 807 | } 808 | 809 | if (createEvent) { 810 | var cellDate = getDateFromCreate(e.target); 811 | if (cellDate !== undefined) { 812 | scope.$apply(function () { 813 | mdEventCalendarCtrl.createEventClick(e, cellDate); 814 | }); 815 | } 816 | } 817 | }); 818 | 819 | function getDateFromCreate(el) { 820 | var dateString = el.getAttribute('md-date'); 821 | while (dateString === null && el.nodeName !== 'MD-EVENT-CALENDAR-MONTH') { 822 | el = el.parentNode; 823 | dateString = el.getAttribute('md-date'); 824 | } 825 | 826 | return dateString === null ? undefined : new Date(dateString); 827 | } 828 | 829 | 830 | function buildView() { 831 | var cellHeight; 832 | if (mdEventCalendarCtrl.fitted) { 833 | cellHeight = element[0].offsetHeight / 5; 834 | } else { 835 | cellHeight = mdEventCalendarCtrl.$element[0].offsetWidth / 7; 836 | } 837 | 838 | var monthElement = $$mdEventCalendarBuilder.month({ 839 | date: mdEventCalendarCtrl.date, 840 | events: mdEventCalendarCtrl.events, 841 | selected: mdEventCalendarCtrl.selectedEvents, 842 | labelProperty: mdEventCalendarCtrl.labelProperty, 843 | showCreateLink: mdEventCalendarCtrl.showCreateLink, 844 | cellHeight: cellHeight 845 | }); 846 | element.empty(); 847 | element.append(monthElement); 848 | 849 | buildShowMore(); 850 | } 851 | 852 | 853 | function addShowMore(date) { 854 | showMoreData = { 855 | date: date 856 | }; 857 | buildShowMore(true); 858 | } 859 | 860 | function buildShowMore(animate) { 861 | if (showMoreData === undefined) { return; } 862 | if (showMoreData.element) { 863 | angular.element(showMoreData.element).remove(); 864 | showMoreData.element = undefined; 865 | } 866 | 867 | var cell = getCellFromDate(showMoreData.date); 868 | var showMoreFragment = $$mdEventCalendarBuilder.showMore({ 869 | date: showMoreData.date, 870 | selected: mdEventCalendarCtrl.selectedEvents, 871 | events: mdEventCalendarCtrl.events, 872 | labelProperty: mdEventCalendarCtrl.labelProperty, 873 | cell: cell 874 | }); 875 | 876 | element.append(showMoreFragment); 877 | showMoreData.element = element[0].lastChild; 878 | positonShowMore(); 879 | 880 | if (animate) { 881 | // add class after element added so animation 882 | $timeout(function () { 883 | angular.element(showMoreData.element).addClass('show'); 884 | }, 0); 885 | } else { 886 | angular.element(showMoreData.element).addClass('no-transition'); 887 | angular.element(showMoreData.element).addClass('show'); 888 | } 889 | } 890 | 891 | function positonShowMore() { 892 | var showMoreBounds = showMoreData.element.getBoundingClientRect(); 893 | var minTop = $window.innerHeight - showMoreBounds.height; 894 | var minLeft = $window.innerWidth - showMoreBounds.width; 895 | var leftDiff = showMoreBounds.left - minLeft; 896 | 897 | if (showMoreBounds.top > minTop) { 898 | showMoreData.element.style.top = minTop+'px'; 899 | } 900 | 901 | if (leftDiff > 0) { 902 | showMoreData.element.style.left = minLeft+'px'; 903 | // offset date 904 | 905 | leftDiff -= 10; 906 | if (leftDiff > 0) { 907 | showMoreData.element.querySelector('.md-event-calendar-show-more-date-label').style.marginLeft = leftDiff+'px'; 908 | } 909 | } 910 | } 911 | 912 | function getCellFromDate(date) { 913 | return element[0].querySelector('[md-date="'+date+'"]'); 914 | } 915 | 916 | function removeShowMore(animate) { 917 | if (!showMoreData) { return; } 918 | 919 | var el = showMoreData.element; 920 | showMoreData = undefined; 921 | 922 | if (animate) { 923 | angular.element(el).removeClass('no-transition'); 924 | $timeout(function () { 925 | angular.element(el).removeClass('show'); 926 | }, 0); 927 | $timeout(function () { 928 | el.remove(); 929 | el = undefined; 930 | }, 400); 931 | } else { 932 | el.remove(); 933 | el = undefined; 934 | } 935 | } 936 | 937 | 938 | function getClosestCell(el) { 939 | var target = angular.element(el).parent(); 940 | while (target.hasClass('md-event-calendar-month-cell') === false) { 941 | target = target.parent(); 942 | } 943 | return target; 944 | } 945 | 946 | function getIdFromHash(id) { 947 | return parseInt(id.replace('object_', '')); 948 | } 949 | } 950 | } 951 | }()); 952 | (function(){"use strict";angular 953 | .module('material.components.eventCalendar') 954 | .directive('mdEventCalendarNext', mdEventCalendarNextDirective); 955 | 956 | /** 957 | * @ngdoc directive 958 | * @name mdEventCalendarNextDirective 959 | * @module material.components.eventCalendar 960 | * 961 | * @restrict E 962 | **/ 963 | function mdEventCalendarNextDirective() { 964 | var directive = { 965 | restrict: 'E', 966 | require: '^mdEventCalendar', 967 | template: ''+ 968 | '
'+ 969 | '
' 970 | }; 971 | return directive; 972 | } 973 | }()); 974 | (function(){"use strict";angular 975 | .module('material.components.eventCalendar') 976 | .directive('mdEventCalendarPrev', mdEventCalendarPrevDirective); 977 | 978 | /** 979 | * @ngdoc directive 980 | * @name mdEventCalendarPrevDirective 981 | * @module material.components.eventCalendar 982 | * 983 | * @restrict E 984 | **/ 985 | function mdEventCalendarPrevDirective() { 986 | var directive = { 987 | restrict: 'E', 988 | require: '^mdEventCalendar', 989 | template: ''+ 990 | '
'+ 991 | '
' 992 | }; 993 | return directive; 994 | } 995 | }()); 996 | (function(){"use strict";angular 997 | .module('material.components.eventCalendar') 998 | .directive('mdEventCalendarTitle', mdEventCalendarTitleDirective); 999 | 1000 | /** 1001 | * @ngdoc directive 1002 | * @name mdEventCalendarTitleDirective 1003 | * @module material.components.eventCalendar 1004 | * 1005 | * @restrict E 1006 | **/ 1007 | function mdEventCalendarTitleDirective() { 1008 | var directive = { 1009 | restrict: 'E', 1010 | require: '^mdEventCalendar', 1011 | template: '
{{mdEventCalendar.monthDisplay + " " + mdEventCalendar.yearDisplay}}
', 1012 | link: link 1013 | }; 1014 | return directive; 1015 | 1016 | function link(scope, element, attrs, ctrl) { 1017 | scope.mdEventCalendar = ctrl; 1018 | } 1019 | } 1020 | }()); 1021 | (function(){"use strict";angular 1022 | .module('material.components.eventCalendar') 1023 | .directive('mdEventCalendarToday', mdEventCalendarTodayDirective); 1024 | 1025 | /** 1026 | * @ngdoc directive 1027 | * @name mdEventCalendarTodayDirective 1028 | * @module material.components.eventCalendar 1029 | * 1030 | * @restrict E 1031 | **/ 1032 | function mdEventCalendarTodayDirective() { 1033 | var directive = { 1034 | restrict: 'E', 1035 | require: '^mdEventCalendar', 1036 | template: 'Today' 1037 | }; 1038 | return directive; 1039 | } 1040 | }()); 1041 | (function(){"use strict"; 1042 | utilService.$inject = ["$injector", "$locale", "$filter"];angular 1043 | .module('material.components.eventCalendar') 1044 | .factory('$$mdEventCalendarUtil', utilService); 1045 | 1046 | 1047 | function utilService($injector, $locale, $filter) { 1048 | var $mdDateLocale = $injector.has('$mdDateLocale') ? $injector.get('$mdDateLocale') : undefined; 1049 | var dateFilter = $filter('date'); 1050 | var months = $mdDateLocale ? $mdDateLocale.months : $locale.DATETIME_FORMATS.MONTH; 1051 | var shortMonths = $mdDateLocale ? $mdDateLocale.shortMonths : $locale.DATETIME_FORMATS.SHORTMONTH; 1052 | var days = $mdDateLocale ? $mdDateLocale.days : $locale.DATETIME_FORMATS.DAY; 1053 | var shortDays = $mdDateLocale ? $mdDateLocale.shortDays : $locale.DATETIME_FORMATS.SHORTDAY.map(function(day) { 1054 | return day.substring(0, 1); 1055 | }); 1056 | // The default dates are simply the numbers 1 through 31. 1057 | var defaultDates = Array(32); 1058 | for (var i = 1; i <= 31; i++) { 1059 | defaultDates[i] = i; 1060 | } 1061 | 1062 | 1063 | var service = { 1064 | months: months, 1065 | shortMonths: shortMonths, 1066 | days: days, 1067 | dates: $mdDateLocale ? $mdDateLocale.dates : defaultDates, 1068 | shortDays: shortDays, 1069 | isValidDate: isValidDate, 1070 | createDateAtMidnight: createDateAtMidnight, 1071 | getDateInNextMonth: getDateInNextMonth, 1072 | getDateInPreviousMonth: getDateInPreviousMonth, 1073 | getFirstDateOfMonth: getFirstDateOfMonth, 1074 | getNumberOfDaysInMonth: getNumberOfDaysInMonth, 1075 | weekNumberFormatter: $mdDateLocale ? $mdDateLocale.weekNumberFormatter : weekNumberFormatter, 1076 | isSameMonthAndYear: isSameMonthAndYear, 1077 | isSameDay: isSameDay, 1078 | isDateWithinRange: isDateWithinRange, 1079 | formatEventTime: formatEventTime 1080 | }; 1081 | return service; 1082 | 1083 | 1084 | 1085 | 1086 | function formatEventTime(date) { 1087 | return dateFilter(date, 'h:mm a'); 1088 | } 1089 | 1090 | 1091 | 1092 | /** 1093 | * Checks if a date is within a min and max range, ignoring the time component. 1094 | * If minDate or maxDate are not dates, they are ignored. 1095 | * @param {Date} date 1096 | * @param {Date} minDate 1097 | * @param {Date} maxDate 1098 | */ 1099 | function isDateWithinRange(date, minDate, maxDate) { 1100 | var dateAtMidnight = createDateAtMidnight(date); 1101 | var minDateAtMidnight = isValidDate(minDate) ? createDateAtMidnight(minDate) : null; 1102 | var maxDateAtMidnight = isValidDate(maxDate) ? createDateAtMidnight(maxDate) : null; 1103 | return (!minDateAtMidnight || minDateAtMidnight <= dateAtMidnight) && 1104 | (!maxDateAtMidnight || maxDateAtMidnight >= dateAtMidnight); 1105 | } 1106 | 1107 | 1108 | 1109 | /** 1110 | * Default week number formatter. 1111 | * @param number 1112 | * @returns {string} 1113 | */ 1114 | function weekNumberFormatter(number) { 1115 | return 'Week ' + number; 1116 | } 1117 | 1118 | 1119 | /** 1120 | * Sets a date's time to midnight. 1121 | * @param {Date} date 1122 | */ 1123 | function setDateTimeToMidnight(date) { 1124 | if (isValidDate(date)) { 1125 | date.setHours(0, 0, 0, 0); 1126 | } 1127 | } 1128 | 1129 | /** 1130 | * Checks whether a date is valid. 1131 | * @param {Date} date 1132 | * @return {boolean} Whether the date is a valid Date. 1133 | */ 1134 | function isValidDate(date) { 1135 | return date && date.getTime && !isNaN(date.getTime()); 1136 | } 1137 | 1138 | 1139 | /** 1140 | * Creates a date with the time set to midnight. 1141 | * Drop-in replacement for two forms of the Date constructor: 1142 | * 1. No argument for Date representing now. 1143 | * 2. Single-argument value representing number of seconds since Unix Epoch 1144 | * or a Date object. 1145 | * @param {number|Date=} opt_value 1146 | * @return {Date} New date with time set to midnight. 1147 | */ 1148 | function createDateAtMidnight(opt_value) { 1149 | var date; 1150 | if (opt_value === undefined) { 1151 | date = new Date(); 1152 | } else { 1153 | date = new Date(opt_value); 1154 | } 1155 | setDateTimeToMidnight(date); 1156 | return date; 1157 | } 1158 | 1159 | 1160 | 1161 | /** 1162 | * Get an arbitrary date in the month after the given date's month. 1163 | * @param date 1164 | * @returns {Date} 1165 | */ 1166 | function getDateInNextMonth(date) { 1167 | return new Date(date.getFullYear(), date.getMonth() + 1, 1); 1168 | } 1169 | 1170 | 1171 | 1172 | /** 1173 | * Get an arbitrary date in the month before the given date's month. 1174 | * @param date 1175 | * @returns {Date} 1176 | */ 1177 | function getDateInPreviousMonth(date) { 1178 | return new Date(date.getFullYear(), date.getMonth() - 1, 1); 1179 | } 1180 | 1181 | 1182 | 1183 | 1184 | /** 1185 | * Gets the first day of the month for the given date's month. 1186 | * @param {Date} date 1187 | * @returns {Date} 1188 | */ 1189 | function getFirstDateOfMonth(date) { 1190 | return new Date(date.getFullYear(), date.getMonth(), 1); 1191 | } 1192 | 1193 | 1194 | /** 1195 | * Gets the number of days in the month for the given date's month. 1196 | * @param date 1197 | * @returns {number} 1198 | */ 1199 | function getNumberOfDaysInMonth(date) { 1200 | return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); 1201 | } 1202 | 1203 | 1204 | /** 1205 | * Gets whether two dates have the same month and year. 1206 | * @param {Date} d1 1207 | * @param {Date} d2 1208 | * @returns {boolean} 1209 | */ 1210 | function isSameMonthAndYear(d1, d2) { 1211 | return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth(); 1212 | } 1213 | 1214 | 1215 | /** 1216 | * Gets whether two dates are the same day (not not necesarily the same time). 1217 | * @param {Date} d1 1218 | * @param {Date} d2 1219 | * @returns {boolean} 1220 | */ 1221 | function isSameDay(d1, d2) { 1222 | return d1.getDate() == d2.getDate() && isSameMonthAndYear(d1, d2); 1223 | } 1224 | } 1225 | }()); -------------------------------------------------------------------------------- /dist/angular-material-event-calendar.min.css: -------------------------------------------------------------------------------- 1 | md-event-calendar:not(._md) md-event-calendar-header{color:#666;background:#fff;border-color:#ddd}md-event-calendar:not(._md) md-event-calendar-header md-event-calendar-next .md-arrow svg,md-event-calendar:not(._md) md-event-calendar-header md-event-calendar-prev .md-arrow svg{fill:#666}md-event-calendar:not(._md) .md-button:not([disabled]){color:#333}md-event-calendar:not(._md) .md-button:not([disabled]):hover{background:hsla(0,0%,62%,.2)}md-event-calendar:not(._md) .md-button[disabled]{color:#ccc}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row-header{color:#999;background:#fff;border-color:#ddd}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row{background:#fff;border-color:#ddd}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell,md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell-divider{border-color:#ddd}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-create-link{color:#4189b8}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label{color:#999}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-event-show-more-link{color:#4189b8}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.different-month{background:#f5f5f5}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today{box-shadow:inset 0 0 0 1px #aaa}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label{color:#666}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today .md-event-calendar-month-cell-divider{border-color:#aaa}md-event-calendar:not(._md) md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell:last-child{border-color:#ddd}md-event-calendar:not(._md) .md-event-calendar-cell-event{background:#ddd;color:#666}md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected{color:#eee;background:#888}md-event-calendar:not(._md) .md-event-calendar-cell-event.md-continue-left:after,md-event-calendar:not(._md) .md-event-calendar-cell-event.md-end-left:after{border-right-color:#ddd}md-event-calendar:not(._md) .md-event-calendar-cell-event.md-continue-right:after,md-event-calendar:not(._md) .md-event-calendar-cell-event.md-start-right:after{border-left-color:#ddd}md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected.md-continue-left:after,md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected.md-end-left:after{border-right-color:#888}md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected.md-continue-right:after,md-event-calendar:not(._md) .md-event-calendar-cell-event.md-selected.md-start-right:after{border-left-color:#888}md-event-calendar:not(._md) .md-event-calendar-show-more-container .md-event-calendar-show-more-date-label{color:#999}md-event-calendar:not(._md) .md-event-calendar-show-more-container .md-event-calendar-show-more-close svg{fill:#999}md-event-calendar{display:block}md-event-calendar md-event-calendar-header{-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;display:-webkit-box;display:-ms-flexbox;display:flex;line-height:64px;-webkit-box-align:center;-ms-flex-align:center;align-items:center;border-style:solid;border-width:1px 1px 0}md-event-calendar md-event-calendar-header.md-center{-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center}md-event-calendar md-event-calendar-header md-event-calendar-title{display:block;min-width:170px;text-align:center;font-size:20px}md-event-calendar md-event-calendar-header md-event-calendar-next,md-event-calendar md-event-calendar-header md-event-calendar-prev{display:block}md-event-calendar md-event-calendar-header md-event-calendar-next .md-arrow,md-event-calendar md-event-calendar-header md-event-calendar-prev .md-arrow{cursor:pointer;height:24px;width:24px}md-event-calendar md-event-calendar-header md-event-calendar-next .md-arrow.md-left-arrow,md-event-calendar md-event-calendar-header md-event-calendar-prev .md-arrow.md-left-arrow{-webkit-transform:rotate(180deg);transform:rotate(180deg)}md-event-calendar .md-event-calendar-month-cell-content .md-event-calendar-create-link{opacity:0}md-event-calendar .md-event-calendar-month-cell-content:hover .md-event-calendar-create-link{opacity:1}md-event-calendar md-event-calendar-month.md-event-hover .md-event-calendar-month-cell-content .md-event-calendar-create-link{opacity:0}md-event-calendar md-event-calendar-month.fitted{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:vertical;-webkit-box-direction:normal;-ms-flex-direction:column;flex-direction:column}md-event-calendar md-event-calendar-month .md-event-calendar-month-row-header{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;min-height:36px;height:36px;-webkit-box-align:end;-ms-flex-align:end;align-items:flex-end;font-size:12px;font-weight:500;padding-bottom:12px;border-style:solid;border-width:0 1px 1px}md-event-calendar md-event-calendar-month .md-event-calendar-month-row-header .md-event-calendar-month-cell-header{-webkit-box-flex:1;-ms-flex:1;flex:1;padding-left:6px}md-event-calendar md-event-calendar-month .md-event-calendar-month-row{-webkit-box-flex:1;-ms-flex:1;flex:1;display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-orient:horizontal;-webkit-box-direction:normal;-ms-flex-direction:row;flex-direction:row;border-style:solid;border-width:0 0 1px}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell{position:relative;-webkit-box-flex:1;-ms-flex:1;flex:1}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-spacer{margin-top:100%}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-divider{position:absolute;top:0;bottom:0;left:0;border-style:solid;border-width:0 1px 0 0}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content{position:absolute;top:0;bottom:0;left:0;right:0}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-create-link{-ms-flex-item-align:center;align-self:center;text-transform:uppercase;font-size:14px;font-weight:500;padding-right:12px;cursor:pointer;-webkit-transition:opacity .4s cubic-bezier(.25,.8,.25,1);transition:opacity .4s cubic-bezier(.25,.8,.25,1)}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label{font-size:13px;padding:8px;-webkit-box-flex:1;-ms-flex:1;flex:1}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-event-spacer{margin:4px 0;height:23px}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-event-show-more-link{font-size:13px;padding:4px;padding-left:8px;cursor:pointer}md-event-calendar md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell:last-child{border-style:solid;border-width:0 1px 0 0}md-event-calendar .md-event-calendar-cell-event{font-size:12px;min-height:15px;padding:4px;cursor:pointer}md-event-calendar .md-event-calendar-cell-event.md-single{margin:4px;border-radius:2px;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}md-event-calendar .md-event-calendar-cell-event.md-start{margin:4px 0 4px 4px;border-radius:2px 0 0 2px;white-space:nowrap;z-index:1;position:relative}md-event-calendar .md-event-calendar-cell-event.md-start-right{margin:4px 13px 4px 4px;border-radius:2px 0 0 2px}md-event-calendar .md-event-calendar-cell-event.md-end{margin:4px 4px 4px 0;border-radius:0 2px 2px 0}md-event-calendar .md-event-calendar-cell-event.md-end-left{margin:4px 4px 4px 13px;border-radius:0 2px 2px 0}md-event-calendar .md-event-calendar-cell-event.md-continue,md-event-calendar .md-event-calendar-cell-event.md-continue-both{margin:4px 0;border-radius:0}md-event-calendar .md-event-calendar-cell-event.md-continue-right{margin:4px 13px 4px 0;border-radius:0;white-space:nowrap}md-event-calendar .md-event-calendar-cell-event.md-continue-left{margin:4px 0 4px 13px;border-radius:0;white-space:nowrap}md-event-calendar .md-event-calendar-cell-event.md-continue-right:after,md-event-calendar .md-event-calendar-cell-event.md-start-right:after{content:'';position:absolute;height:0;width:0;right:0;margin-top:-4px;border-top:12px solid transparent;border-bottom:11.5px solid transparent;border-left:13px solid #eee}md-event-calendar .md-event-calendar-cell-event.md-continue-left:after,md-event-calendar .md-event-calendar-cell-event.md-end-left:after{content:'';position:absolute;height:0;width:0;left:0;margin-top:-4px;border-top:12px solid transparent;border-bottom:11.5px solid transparent;border-right:13px solid #eee}md-event-calendar .md-event-calendar-cell-event .md-event-calendar-cell-event-time{font-weight:500;padding-right:6px;pointer-events:none}md-event-calendar .md-event-calendar-cell-event span{pointer-events:none}md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-end,md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-single,md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-start-right{padding-left:16px;margin-left:0}md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-end,md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-end-left,md-event-calendar .md-event-calendar-show-more-content .md-event-calendar-cell-event.md-single{margin-right:0}md-event-calendar .md-event-calendar-show-more-container{opacity:0;position:absolute;top:0;left:1px;width:180px;background:#fff;padding:12px;padding-top:7px;border-radius:2px;z-index:2;box-shadow:0 7px 8px -4px rgba(0,0,0,.2),0 13px 19px 2px rgba(0,0,0,.14),0 5px 24px 4px rgba(0,0,0,.12)}md-event-calendar .md-event-calendar-show-more-container .md-event-calendar-show-more-content{position:relative}md-event-calendar .md-event-calendar-show-more-container.show{opacity:1}md-event-calendar .md-event-calendar-show-more-container.show:not(.no-transition){-webkit-transition:opacity .4s cubic-bezier(.25,.8,.25,1);transition:opacity .4s cubic-bezier(.25,.8,.25,1)}md-event-calendar .md-event-calendar-show-more-container .md-event-calendar-show-more-close{display:-webkit-box;display:-ms-flexbox;display:flex;position:absolute;top:6px;right:7px;width:16px;height:16px;cursor:pointer}md-event-calendar .md-event-calendar-show-more-container .md-event-calendar-show-more-close svg{pointer-events:none}md-event-calendar .md-event-calendar-show-more-container .md-event-calendar-show-more-date-label{font-size:13px;padding:6px;margin-left:-11px;margin-top:-5px}md-event-calendar.md-create-disabled md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-create-link{display:none} -------------------------------------------------------------------------------- /dist/angular-material-event-calendar.min.js: -------------------------------------------------------------------------------- 1 | !function(){"use strict";function e(e,n,a){var r;e.has("$mdThemingProvider")?(r=e.get("$mdThemingProvider"),r.registerStyles(a)):n.decorator("$$rAF",["$delegate",t])}function t(e){return e.throttle=function(t){var n,a,r,d;return function(){n=arguments,d=this,r=t,a||(a=!0,e(function(){r.apply(d,Array.prototype.slice.call(n)),a=!1}))}},e}e.$inject=["$injector","$provide","EVENT_CALENDAR_THEME"],angular.module("material.components.eventCalendar",[]).config(e)}(),function(){"use strict";angular.module("material.components.eventCalendar").run(["$templateCache",function(e){e.put("icons/ic_close_black_24px.svg",'\n \n \n'),e.put("icons/ic_keyboard_arrow_right_black_24px.svg",'\n \n \n\n')}])}(),function(){"use strict";angular.module("material.components.eventCalendar").constant("EVENT_CALENDAR_THEME","md-event-calendar._md md-event-calendar-header {\n color: '{{foreground-1}}';\n background: '{{background-hue-1}}';\n border-color: '{{foreground-4}}'; }\n md-event-calendar._md md-event-calendar-header md-event-calendar-next .md-arrow svg, md-event-calendar._md md-event-calendar-header md-event-calendar-prev .md-arrow svg {\n fill: '{{foreground-2}}'; }\n\nmd-event-calendar._md md-event-calendar-month .md-event-calendar-month-row-header {\n color: '{{foreground-3}}';\n background: '{{background-hue-1}}';\n border-color: '{{foreground-4}}'; }\n\nmd-event-calendar._md md-event-calendar-month .md-event-calendar-month-row {\n background: '{{background-hue-1}}';\n border-color: '{{foreground-4}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell-divider {\n border-color: '{{foreground-4}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell {\n border-color: '{{foreground-4}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-create-link {\n color: '{{primary-default}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label {\n color: '{{foreground-3}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell .md-event-calendar-month-cell-content .md-event-calendar-cell-event-show-more-link {\n color: '{{primary-default}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.different-month {\n background: '{{background-hue-2}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today {\n box-shadow: inset 0px 0px 0px 1px '{{primary-default}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell.today .md-event-calendar-month-cell-content .md-event-calendar-cell-data-label {\n color: '{{primary-default}}'; }\n md-event-calendar._md md-event-calendar-month .md-event-calendar-month-row .md-event-calendar-month-cell:last-child {\n border-color: '{{foreground-4}}'; }\n\nmd-event-calendar._md .md-event-calendar-cell-event {\n background: '{{foreground-4}}';\n color: '{{background-900}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-selected {\n color: #EEE;\n background: '{{primary-default}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-continue-left:after, md-event-calendar._md .md-event-calendar-cell-event.md-end-left:after {\n border-right-color: '{{foreground-4}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-continue-right:after, md-event-calendar._md .md-event-calendar-cell-event.md-start-right:after {\n border-left-color: '{{foreground-4}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-selected.md-continue-left:after, md-event-calendar._md .md-event-calendar-cell-event.md-selected.md-end-left:after {\n border-right-color: '{{primary-default}}'; }\n md-event-calendar._md .md-event-calendar-cell-event.md-selected.md-continue-right:after, md-event-calendar._md .md-event-calendar-cell-event.md-selected.md-start-right:after {\n border-left-color: '{{primary-default}}'; }\n\nmd-event-calendar._md .md-event-calendar-show-more-container .md-event-calendar-show-more-date-label {\n color: '{{foreground-3}}'; }\n\nmd-event-calendar._md .md-event-calendar-show-more-container .md-event-calendar-show-more-close svg {\n fill: '{{foreground-1}}'; }\n\nmd-event-calendar._md.md-primary md-event-calendar-header {\n color: '{{background-100}}';\n background: '{{primary-default}}'; }\n md-event-calendar._md.md-primary md-event-calendar-header md-event-calendar-next .md-arrow svg, md-event-calendar._md.md-primary md-event-calendar-header md-event-calendar-prev .md-arrow svg {\n fill: '{{background-100}}'; }\n\nmd-event-calendar._md.md-primary md-event-calendar-month .md-event-calendar-month-row-header {\n color: '{{background-100}}';\n background: '{{primary-default}}'; }\n")}(),function(){"use strict";function e(e,t){function n(e,n){var a=n.mdEventClick?t(n.mdEventClick,null,!0):void 0,d=n.mdCreateEventClick?t(n.mdCreateEventClick,null,!0):void 0,l=n.mdCreateDisabled?t(n.mdCreateDisabled):void 0;return e.append(""),function(e,t,o,c){function i(){var e=f.$viewValue||f.$modelValue||[];h.selectedEvents=[].concat(e)}function m(t,n){o.mdEventClick&&a(e.$parent,{$event:t,$selectedEvent:n})}function s(t,n){o.mdCreateEventClick&&d(e.$parent,{$event:t,$date:n})}function v(){return u}var u=!1,h=c[0],f=c[1];r&&(t.addClass("_md"),r(t)),h.isCreateDisabled=v,h.callEventClick=m,h.createEventClick=s,f&&(f.$render=i,h.ngModelCtrl=f),l?e.$watch(function(){return l(e.$parent)},function(e){u=e,t.toggleClass("md-create-disabled",e)}):void 0!==n.mdCreateDisabled&&(u=!0,t.addClass("md-create-disabled"))}}function a(e,t,n){function a(){o.date=e.getDateInNextMonth(o.date),o.monthDisplay=e.months[o.date.getMonth()],o.yearDisplay=o.date.getFullYear(),o.isTodayDisabled=o.date.getMonth()===(new Date).getMonth()}function r(){o.date=e.getDateInPreviousMonth(o.date),o.monthDisplay=e.months[o.date.getMonth()],o.yearDisplay=o.date.getFullYear(),o.isTodayDisabled=o.date.getMonth()===(new Date).getMonth()}function d(){o.date=new Date,o.monthDisplay=e.months[o.date.getMonth()],o.yearDisplay=o.date.getFullYear(),o.isTodayDisabled=!0}function l(e,t){var n=o.events.filter(function(e){return e.$$mdEventId===t});return o.ngModelCtrl&&(o.ngModelCtrl.$setViewValue(n[0]),o.ngModelCtrl.$render()),o.callEventClick(e,n[0]),!0}var o=this;o.$element=t,o.labelProperty=n.mdLabel||"title",o.selectedEvents=[],o.today=e.createDateAtMidnight(),o.date=e.createDateAtMidnight(),o.isToday=e.isSameDay(o.date,new Date),o.monthDisplay=e.months[o.date.getMonth()],o.yearDisplay=o.date.getFullYear(),o.isTodayDisabled=!0,o.showCreateLink=void 0!==n.mdShowCreateLink&&"false"!==n.mdShowCreateLink,o.nextMonth=a,o.previousMonth=r,o.selectEvent=l,o.setToday=d,o.autoHeight=void 0!==n.autoHeight,o.fitted=void 0!==n.fitted,o.offset=o.autoHeight===!1||""===n.autoHeight||isNaN(n.autoHeight.replace("px",""))?0:parseInt(n.autoHeight.replace("px",""))}a.$inject=["$$mdEventCalendarUtil","$element","$attrs"];var r=e.has("$mdTheming")?e.get("$mdTheming"):void 0,d={restrict:"E",require:["mdEventCalendar","?ngModel"],scope:{events:"=mdEvents"},compile:n,controller:a,controllerAs:"mdEventCalendar",bindToController:!0};return d}e.$inject=["$injector","$parse"],angular.module("material.components.eventCalendar").directive("mdEventCalendar",e)}(),function(){"use strict";function e(e,n){function a(t){var a=t.date,d=t.selected||[],l=t.events?r(a,t.events):[],o=t.labelProperty,c=document.createDocumentFragment(),i=document.createElement("div");i.classList.add("md-event-calendar-show-more-container");var s=document.createElement("div");s.classList.add("md-event-calendar-show-more-content");var v=document.createElement("div");v.classList.add("md-event-calendar-show-more-date-label"),v.textContent=e.dates[a.getDate()];var u=document.createElement("div");u.classList.add("md-event-calendar-show-more-close"),u.innerHTML=n.get("icons/ic_close_black_24px.svg"),u.setAttribute("md-show-more-close","true"),i.appendChild(v),i.appendChild(u),i.appendChild(s),c.appendChild(i),l.forEach(function(t){var n,r=e.isSameDay(a,t.start),l=!e.isValidDate(t.end)||e.isSameDay(a,t.end),c={labelProperty:o,selected:d};n=r&&l?m({className:"single",hasLabel:!0},t,c):r?m({className:"start-right",hasLabel:!0},t,c):l?m({className:"end-left",hasLabel:!0},t,c):m({className:"continue",hasLabel:!0},t,c),s.appendChild(n)});var h=(t.cell.getBoundingClientRect(),t.cell.offsetTop),f=t.cell.offsetLeft;return i.style.top=h+"px",i.style.left=f+"px",c}function r(t,n){return n&&n.length?n.filter(function(n){return e.isDateWithinRange(t,n.start,n.end||n.start)}).sort(function(e,t){return e=new Date(e.start),t=new Date(t.start),e>t?1:e0)for(a=e.getFirstDateOfMonth(m),a.setDate(a.getDate()-v);d=t.maxEvents)return e.appendChild(c(d.length-o,t.date)),!1;e.appendChild(i()),n+=1}return a>=t.maxEvents?(e.appendChild(c(d.length-o,t.date)),!1):(e.appendChild(m(s,l,t)),!0)}),r===!0&&e.classList.add("md-has-events")}function c(e,t){var n=document.createElement("div");return n.classList.add("md-event-calendar-cell-event-show-more-link"),n.textContent=e+" more",n.setAttribute("md-show-more",t.toISOString()),n}function i(){var e=document.createElement("div");return e.classList.add("md-event-calendar-cell-event-spacer"),e}function m(t,n,a){var r=s(n),d=document.createElement("div");if(d.setAttribute("md-event-id",r),d.classList.add("md-event-calendar-cell-event"),d.classList.add("md-"+t.className),n.customClass&&d.classList.add(n.customClass),t.hasLabel===!0){if(t.allDay!==!0){var l=document.createElement("span");l.classList.add("md-event-calendar-cell-event-time"),l.textContent=e.formatEventTime(n.start),d.appendChild(l)}var o=document.createElement("span");o.textContent=n[a.labelProperty],d.appendChild(o)}return a.selected.every(function(e){return void 0===e.$$mdEventId||e.$$mdEventId!==n.$$mdEventId||(d.classList.add("md-selected"),!1)}),d}function s(e){return angular.isObject(e)?"object_"+(e.$$mdEventId||(e.$$mdEventId=++t)):"id_"+ ++t}function v(t,n){var a,r,d=e.isSameDay(n.date,t.start),l=!e.isValidDate(t.end)||e.isSameDay(n.date,t.end);return d&&(n.allDay||l)?(a="single",r=!0):d&&6===n.dayOfWeek?(a="start-right",r=!0):d?(a="start",r=!0):l&&0===n.dayOfWeek?(a="end-left",r=!0):l?(a="end",r=n.isFirstDay):0===n.dayOfWeek?(a="continue-left",r=!0):6===n.dayOfWeek?(a="continue-right",r=!1):(a="continue",r=!1),{className:a,hasLabel:r,allDay:t.allDay||!1}}function u(t,n){return n.filter(function(n){return e.isDateWithinRange(t,n.start,n.end||n.start)})}function h(e,t){function n(){for(var e=0;a.indexOf(e)!==-1;)e++;return a.push(e),e}var a=[],r=e.sort(function(e,t){return e.end>t.end?-1:e.endt.$$place?1:e.$$placet?1:en&&(C.element.style.top=n+"px"),r>0&&(C.element.style.left=a+"px",r-=10,r>0&&(C.element.querySelector(".md-event-calendar-show-more-date-label").style.marginLeft=r+"px"))}function f(e){return d[0].querySelector('[md-date="'+e+'"]')}function g(e){if(C){var t=C.element;C=void 0,e?(angular.element(t).removeClass("no-transition"),a(function(){angular.element(t).removeClass("show")},0),a(function(){t.remove(),t=void 0},400)):(t.remove(),t=void 0)}}function p(e){return parseInt(e.replace("object_",""))}var C,E=o,b=n.throttle(function(){r.$evalAsync(function(){c(),d.toggleClass("fitted",E.fitted),s()})});r.$watch(function(){return E.date},s),r.$watch(function(){return E.events},function(e,t){e!==t&&s()},!0),r.$watch(function(){return E.selectedEvents},function(e,t){e!==t&&s()},!0),angular.element(t).on("resize",b),r.$on("$destroy",function(){angular.element(t).off("resize",b)}),n(function(){c(),d.toggleClass("fitted",E.fitted)}),i(),d.on("click",function(e){if(E.isCreateDisabled()!==!0){var t=e.target.getAttribute("md-event-id"),n=e.target.getAttribute("md-show-more"),a=(e.target.getAttribute("md-show-more-close"),null!==e.target.getAttribute("md-create-event"));if(t){p(t);return void r.$apply(function(){E.selectEvent(e,p(t))})}if(g(!0),n&&v(new Date(n)),a){var d=m(e.target);void 0!==d&&r.$apply(function(){E.createEventClick(e,d)})}}})}var d={restrict:"E",require:"^mdEventCalendar",link:r};return d}e.$inject=["$$mdEventCalendarBuilder","$window","$$rAF","$timeout"],angular.module("material.components.eventCalendar").directive("mdEventCalendarMonth",e)}(),function(){"use strict";function e(){var e={restrict:"E",require:"^mdEventCalendar",template:'
'};return e}angular.module("material.components.eventCalendar").directive("mdEventCalendarNext",e)}(),function(){"use strict";function e(){var e={restrict:"E",require:"^mdEventCalendar",template:'
'};return e}angular.module("material.components.eventCalendar").directive("mdEventCalendarPrev",e)}(),function(){"use strict";function e(){function e(e,t,n,a){e.mdEventCalendar=a}var t={restrict:"E",require:"^mdEventCalendar",template:'
{{mdEventCalendar.monthDisplay + " " + mdEventCalendar.yearDisplay}}
',link:e};return t}angular.module("material.components.eventCalendar").directive("mdEventCalendarTitle",e)}(),function(){"use strict";function e(){var e={restrict:"E",require:"^mdEventCalendar",template:'Today'};return e}angular.module("material.components.eventCalendar").directive("mdEventCalendarToday",e)}(),function(){"use strict";function e(e,t,n){function a(e){return g(e,"h:mm a")}function r(e,t,n){var a=c(e),r=o(t)?c(t):null,d=o(n)?c(n):null;return(!r||r<=a)&&(!d||d>=a)}function d(e){return"Week "+e}function l(e){o(e)&&e.setHours(0,0,0,0)}function o(e){return e&&e.getTime&&!isNaN(e.getTime())}function c(e){var t;return t=void 0===e?new Date:new Date(e),l(t),t}function i(e){return new Date(e.getFullYear(),e.getMonth()+1,1)}function m(e){return new Date(e.getFullYear(),e.getMonth()-1,1)}function s(e){return new Date(e.getFullYear(),e.getMonth(),1)}function v(e){return new Date(e.getFullYear(),e.getMonth()+1,0).getDate()}function u(e,t){return e.getFullYear()===t.getFullYear()&&e.getMonth()===t.getMonth()}function h(e,t){return e.getDate()==t.getDate()&&u(e,t)}for(var f=e.has("$mdDateLocale")?e.get("$mdDateLocale"):void 0,g=n("date"),p=f?f.months:t.DATETIME_FORMATS.MONTH,C=f?f.shortMonths:t.DATETIME_FORMATS.SHORTMONTH,E=f?f.days:t.DATETIME_FORMATS.DAY,b=f?f.shortDays:t.DATETIME_FORMATS.SHORTDAY.map(function(e){return e.substring(0,1)}),y=Array(32),D=1;D<=31;D++)y[D]=D;var $={months:p,shortMonths:C,days:E,dates:f?f.dates:y,shortDays:b,isValidDate:o,createDateAtMidnight:c,getDateInNextMonth:i,getDateInPreviousMonth:m,getFirstDateOfMonth:s,getNumberOfDaysInMonth:v,weekNumberFormatter:f?f.weekNumberFormatter:d,isSameMonthAndYear:u,isSameDay:h,isDateWithinRange:r,formatEventTime:a};return $}e.$inject=["$injector","$locale","$filter"],angular.module("material.components.eventCalendar").factory("$$mdEventCalendarUtil",e)}(); -------------------------------------------------------------------------------- /gulp/config.js: -------------------------------------------------------------------------------- 1 | exports.paths = { 2 | src: 'src/', 3 | app: 'app/', 4 | dest: 'public/', 5 | build: 'dist/', 6 | scripts: ['src/mdCalendar.js', 'src/*.js', 'src/**/*.js', '!src/*spec.js', '!src/**/*spec.js'], 7 | appScripts: ['app/app.js', 'app/*.js', 'app/**/*.js'], 8 | css: ['src/*.scss', 'src/*.css', '!src/*spec.css', '!src/*-theme.scss'], 9 | appCss: ['app/style.css', 'app/**/*.css'], 10 | injectCss: ['public/*.css', 'public/**/*.css'], 11 | partials: ['app/**/*.html'], 12 | icons: ['src/**/*.svg'], 13 | bower: './bower.json' 14 | }; 15 | -------------------------------------------------------------------------------- /gulp/cssBuild.js: -------------------------------------------------------------------------------- 1 | var paths = require('./config').paths; 2 | 3 | var gulp = require('gulp'); 4 | var gutil = require('gulp-util'); 5 | var autoprefixer = require('gulp-autoprefixer'); 6 | var gulpFilter = require('gulp-filter'); 7 | var concat = require('gulp-concat'); 8 | var cssnano = require('gulp-cssnano'); 9 | var sass = require('gulp-sass'); 10 | var rename = require('gulp-rename'); 11 | 12 | exports.getDev = function (srcs) { 13 | srcs = srcs || paths.css.concat(paths.appCss); 14 | 15 | return function dev() { 16 | return gulp.src(srcs) 17 | .pipe(sass()) 18 | .pipe(autoprefixer()) 19 | .pipe(gulp.dest(paths.dest)) 20 | .on('end', function(){ 21 | gutil.log(gutil.colors.green('✔ CSS dev'), 'Finished'); 22 | }); 23 | }; 24 | }; 25 | 26 | 27 | exports.release = function () { 28 | return gulp.src(paths.css) 29 | .pipe(sass()) 30 | .pipe(concat('angular-material-event-calendar.css')) 31 | .pipe(autoprefixer()) 32 | .pipe(gulp.dest(paths.build)) 33 | .pipe(cssnano()) 34 | .pipe(rename('angular-material-event-calendar.min.css')) 35 | .pipe(gulp.dest(paths.build)) 36 | .on('end', function(){ 37 | gutil.log(gutil.colors.green('✔ CSS Build'), 'Finished'); 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /gulp/indexBuild.js: -------------------------------------------------------------------------------- 1 | var paths = require('./config').paths; 2 | 3 | var gulp = require('gulp'); 4 | var inject = require('gulp-inject'); 5 | var mainBowerFiles = require('gulp-main-bower-files'); 6 | 7 | 8 | exports.inject = function () { 9 | var scripts = gulp.src(paths.scripts, {read: false}); 10 | var appScripts = gulp.src(paths.appScripts, {read: false}); 11 | var appCss = gulp.src(paths.appCss, {read: false}); 12 | var bower = gulp.src(paths.bower).pipe(mainBowerFiles({includeDev: true})); 13 | var css = gulp.src(paths.injectCss, {read: false}); 14 | var themeConstant = gulp.src(['public/js/_theme.js', 'public/templates.js'], {read: false}); 15 | 16 | return gulp.src(paths.app + 'index.html') 17 | .pipe(inject(css, { 18 | name: 'css', 19 | relative: true, 20 | ignorePath: '../public' 21 | })) 22 | .pipe(inject(scripts, { 23 | name: 'scripts', 24 | relative: true, 25 | ignorePath: '../src' 26 | })) 27 | .pipe(inject(themeConstant, { 28 | name: 'theme', 29 | relative: true, 30 | ignorePath: '../public' 31 | })) 32 | .pipe(inject(appScripts, { 33 | name: 'appscripts', 34 | relative: true, 35 | ignorePath: '../' 36 | })) 37 | .pipe(inject(bower, { 38 | name: 'bower', 39 | relative: true, 40 | ignorePath: '../bower_components/' 41 | })) 42 | .pipe(gulp.dest(paths.dest)); 43 | }; 44 | -------------------------------------------------------------------------------- /gulp/jsBuild.js: -------------------------------------------------------------------------------- 1 | var paths = require('./config').paths; 2 | 3 | var gulp = require('gulp'); 4 | var jshint = require('gulp-jshint'); 5 | var wrap = require("gulp-wrap"); 6 | var concat = require('gulp-concat'); 7 | var uglify = require('gulp-uglify'); 8 | var stripDebug = require('gulp-strip-debug'); 9 | var rename = require("gulp-rename"); 10 | var filter = require('gulp-filter'); 11 | var gutil = require('gulp-util'); 12 | var ngAnnotate = require('gulp-ng-annotate'); 13 | 14 | 15 | 16 | exports.getDevSrc = function (srcs) { 17 | srcs = srcs || paths.scripts; 18 | 19 | return function dev() { 20 | return gulp.src(srcs, {base: paths.src}) 21 | .pipe(wrap('(function(){"use strict";<%= contents %>}());')) 22 | .pipe(jshint()) 23 | .pipe(jshint.reporter('default')) 24 | .pipe(gulp.dest(paths.dest)) 25 | .on('end', function() { 26 | gutil.log(gutil.colors.green('✔ JS Dev'), 'Finished'); 27 | }); 28 | }; 29 | } 30 | 31 | 32 | exports.getDevApp = function (srcs) { 33 | srcs = srcs || paths.appScripts; 34 | 35 | return function dev() { 36 | return gulp.src(srcs, {base: paths.app}) 37 | .pipe(wrap('(function(){"use strict";<%= contents %>}());')) 38 | .pipe(jshint()) 39 | .pipe(jshint.reporter('default')) 40 | .pipe(gulp.dest(paths.dest)) 41 | .on('end', function() { 42 | gutil.log(gutil.colors.green('✔ JS Dev'), 'Finished'); 43 | }); 44 | }; 45 | } 46 | 47 | 48 | exports.release = function () { 49 | return gulp.src(paths.scripts) 50 | .pipe(wrap('(function(){"use strict";<%= contents %>}());')) 51 | .pipe(jshint()) 52 | .pipe(jshint.reporter('default')) 53 | .pipe(concat('angular-material-event-calendar.js')) 54 | .pipe(stripDebug()) 55 | .pipe(ngAnnotate()) 56 | .pipe(gulp.dest(paths.build)) 57 | .pipe(uglify()) 58 | .pipe(rename('angular-material-event-calendar.min.js')) 59 | .pipe(gulp.dest(paths.build)) 60 | .on('end', function() { 61 | gutil.log(gutil.colors.green('✔ JS build'), 'Finished'); 62 | }); 63 | } 64 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | var paths = require('./gulp/config').paths; 2 | 3 | var gulp = require('gulp'); 4 | var serve = require('gulp-serve'); 5 | var gulpSequence = require('gulp-sequence'); 6 | var del = require('del'); 7 | var bump = require('gulp-bump'); 8 | var ngConstant = require('gulp-ng-constant'); 9 | var sass = require('gulp-sass'); 10 | var through2 = require('through2'); 11 | var rename = require('gulp-rename'); 12 | var wrap = require('gulp-wrap'); 13 | var templateCache = require('gulp-angular-templatecache'); 14 | var KarmaServer = require('karma').Server; 15 | 16 | 17 | var jsBuild = require('./gulp/jsBuild'); 18 | var cssBuild = require('./gulp/cssBuild'); 19 | var indexBuild = require('./gulp/indexBuild'); 20 | 21 | 22 | 23 | gulp.task('jsSrcBuild', jsBuild.getDevSrc()); 24 | gulp.task('jsAppBuild', jsBuild.getDevApp()); 25 | gulp.task('jsReleaseBuild', jsBuild.release); 26 | gulp.task('cssBuild', cssBuild.getDev()); 27 | gulp.task('cssReleaseBuild', cssBuild.release); 28 | gulp.task('indexBuild', indexBuild.inject); 29 | 30 | 31 | 32 | // -- main tasks. use these to watch and build and release 33 | 34 | gulp.task('default', gulpSequence('buildLocal', ['serve', 'watch'])); 35 | gulp.task('buildLocal', gulpSequence( 36 | 'clean', 37 | [ 38 | 'jsSrcBuild', 39 | 'jsAppBuild', 40 | 'cssBuild', 41 | 'copyPartials', 42 | 'copyIcons', 43 | 'themeToConstant', 44 | 'buildIconCacheDev' 45 | ], 46 | 'indexBuild' 47 | )); 48 | 49 | gulp.task('build', gulpSequence('buildIconCache', 'themeToConstantBuild', ['jsReleaseBuild', 'cssReleaseBuild'], 'cleanIconCache', 'cleanThemeConstant')); 50 | 51 | 52 | 53 | gulp.task('themeToConstant', function () { 54 | return gulp.src(paths.src+'eventCalendar-theme.scss') 55 | .pipe(sass()) 56 | .pipe(through2.obj(function (file, enc, cb) { 57 | var config = { 58 | name: 'material.components.eventCalendar', 59 | deps: false, 60 | constants: { 61 | EVENT_CALENDAR_THEME: file.contents.toString() 62 | } 63 | }; 64 | file.contents = new Buffer(JSON.stringify(config), 'utf-8'); 65 | this.push(file); 66 | cb(); 67 | })) 68 | .pipe(ngConstant()) 69 | .pipe(wrap('(function(){"use strict";<%= contents %>}());')) 70 | .pipe(rename('_theme.js')) 71 | .pipe(gulp.dest(paths.dest+'/js')) 72 | }); 73 | gulp.task('themeToConstantBuild', function () { 74 | return gulp.src(paths.src+'eventCalendar-theme.scss') 75 | .pipe(sass()) 76 | .pipe(through2.obj(function (file, enc, cb) { 77 | var config = { 78 | name: 'material.components.eventCalendar', 79 | deps: false, 80 | constants: { 81 | EVENT_CALENDAR_THEME: file.contents.toString() 82 | } 83 | }; 84 | file.contents = new Buffer(JSON.stringify(config), 'utf-8'); 85 | this.push(file); 86 | cb(); 87 | })) 88 | .pipe(ngConstant()) 89 | .pipe(rename('_theme.js')) 90 | .pipe(gulp.dest(paths.src+'/js')) 91 | }); 92 | gulp.task('cleanThemeConstant', function () { 93 | return del(paths.src+'/js/_theme.js'); 94 | }); 95 | 96 | 97 | 98 | gulp.task('clean', function () { 99 | return del(paths.dest); 100 | }); 101 | 102 | 103 | gulp.task('copyPartials', function () { 104 | return gulp.src(paths.partials, {base: paths.app}) 105 | .pipe(gulp.dest(paths.dest)); 106 | }); 107 | 108 | gulp.task('copyIcons', function () { 109 | return gulp.src(paths.icons, {base: paths.src}) 110 | .pipe(gulp.dest(paths.dest)); 111 | }); 112 | 113 | gulp.task('buildIconCacheDev', function () { 114 | return gulp.src(paths.icons) 115 | .pipe(templateCache({module: 'material.components.eventCalendar'})) 116 | .pipe(gulp.dest(paths.dest)); 117 | }); 118 | gulp.task('buildIconCache', function () { 119 | return gulp.src(paths.icons) 120 | .pipe(templateCache({module: 'material.components.eventCalendar'})) 121 | .pipe(gulp.dest(paths.src)); 122 | }); 123 | 124 | gulp.task('cleanIconCache', function () { 125 | return del('src/templates.js'); 126 | }); 127 | 128 | gulp.task('serve', serve({ 129 | root: ['public', 'bower_components'], 130 | port: 8080 131 | })); 132 | 133 | 134 | 135 | gulp.task('test-karma', function (done) { 136 | new KarmaServer({ 137 | configFile: __dirname + '/karma.conf.js', 138 | singleRun: true 139 | }, function (errorCode) { 140 | if (errorCode !== 0) { 141 | console.log('Karma exited with error code ' + errorCode); 142 | done(); 143 | return process.exit(errorCode); 144 | } 145 | done(); 146 | }).start(); 147 | }); 148 | 149 | gulp.task('test', gulpSequence('build', 'test-karma')); 150 | 151 | 152 | 153 | 154 | gulp.task('watch', function () { 155 | gulp.watch(paths.scripts, function (event) { 156 | jsBuild.getDevSrc(event.path)() 157 | .on('end', function () { 158 | if (event.type !== 'changed') { indexBuild.inject(); } 159 | }); 160 | }); 161 | 162 | gulp.watch(paths.appScripts, function (event) { 163 | jsBuild.getDevApp(event.path)() 164 | .on('end', function () { 165 | if (event.type !== 'changed') { indexBuild.inject(); } 166 | }); 167 | }); 168 | 169 | 170 | gulp.watch(paths.css.concat(paths.appCss), function (event) { 171 | cssBuild.getDev(event.path)() 172 | .on('end', function () { 173 | if (event.type !== 'changed') { indexBuild.inject(); } 174 | }); 175 | }); 176 | 177 | gulp.watch('src/eventCalendar-theme.scss', ['themeToConstant']); 178 | 179 | 180 | gulp.watch(paths.partials, function (event) { 181 | return gulp.src(event.path, {base: paths.app}) 182 | .pipe(gulp.dest(paths.dest)); 183 | }); 184 | }); 185 | 186 | 187 | 188 | 189 | 190 | 191 | gulp.task('major', function(){ 192 | gulp.src(['./bower.json', './package.json']) 193 | .pipe(bump({type:'major'})) 194 | .pipe(gulp.dest('./')); 195 | }); 196 | 197 | gulp.task('minor', function(){ 198 | gulp.src(['./bower.json', './package.json']) 199 | .pipe(bump({type:'minor'})) 200 | .pipe(gulp.dest('./')); 201 | }); 202 | 203 | gulp.task('patch', function(){ 204 | gulp.src(['./bower.json', './package.json']) 205 | .pipe(bump({type:'patch'})) 206 | .pipe(gulp.dest('./')); 207 | }); 208 | 209 | gulp.task('prerelease', function(){ 210 | gulp.src(['./bower.json', './package.json']) 211 | .pipe(bump({type:'prerelease'})) 212 | .pipe(gulp.dest('./')); 213 | }); 214 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./dist/angular-material-event-calendar'); 2 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Wed May 11 2016 11:02:59 GMT-0500 (CDT) 3 | module.exports = function(config) { 4 | config.set({ 5 | 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '', 8 | 9 | 10 | // frameworks to use 11 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 12 | frameworks: ['jasmine'], 13 | 14 | 15 | // list of files / patterns to load in the browser 16 | files: [ 17 | 'dist/md-calendar.css', 18 | 'bower_components/angular-material/angular-material.css', 19 | 20 | 'bower_components/angular/angular.js', 21 | 'bower_components/angular-animate/angular-animate.js', 22 | 'bower_components/angular-material/angular-material.js', 23 | 'bower_components/angular-mocks/angular-mocks.js', 24 | 'src/mdCalendar.js', 25 | 'src/**/*.js' 26 | ], 27 | 28 | 29 | // test results reporter to use 30 | // possible values: 'dots', 'progress' 31 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 32 | reporters: [ 'spec'], 33 | 34 | 35 | // web server port 36 | port: 9876, 37 | 38 | 39 | // enable / disable colors in the output (reporters and logs) 40 | colors: true, 41 | 42 | 43 | // level of logging 44 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 45 | logLevel: config.LOG_INFO, 46 | 47 | 48 | // enable / disable watching file and executing tests whenever any file changes 49 | autoWatch: true, 50 | 51 | 52 | // start these browsers 53 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 54 | // browsers: ['Chrome'], 55 | browsers: ['PhantomJS'], 56 | 57 | 58 | // Continuous Integration mode 59 | // if true, Karma captures browsers, runs the tests and exits 60 | singleRun: false, 61 | 62 | // Concurrency level 63 | // how many browser should be started simultaneous 64 | concurrency: Infinity 65 | }) 66 | } 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-material-event-calendar", 3 | "version": "0.1.4", 4 | "author": "Ben Rubin", 5 | "description": "Angular material event calander component", 6 | "keywords": "material, material-design, design, angular, component, calendar, event, md", 7 | "license": "MIT", 8 | "repository": { 9 | "type": "git", 10 | "url": "git@github.com/B-3PO/angular-material-event-calendar.git" 11 | }, 12 | "main": "index.js", 13 | "devDependencies": { 14 | "bump": "^0.2.5", 15 | "del": "^2.2.0", 16 | "gulp": "^3.9.1", 17 | "gulp-angular-templatecache": "^1.9.1", 18 | "gulp-autoprefixer": "^3.1.0", 19 | "gulp-bump": "^1.0.0", 20 | "gulp-concat": "^2.6.0", 21 | "gulp-cssnano": "^2.1.0", 22 | "gulp-filter": "^4.0.0", 23 | "gulp-flatten": "^0.3.0", 24 | "gulp-if": "^2.0.1", 25 | "gulp-inject": "^4.0.0", 26 | "gulp-jasmine": "^2.4.0", 27 | "gulp-jshint": "^2.0.0", 28 | "gulp-main-bower-files": "^1.5.1", 29 | "gulp-ng-annotate": "^2.0.0", 30 | "gulp-ng-constant": "^1.1.0", 31 | "gulp-preprocess": "^2.0.0", 32 | "gulp-rename": "^1.2.2", 33 | "gulp-sass": "^2.3.2", 34 | "gulp-sequence": "^0.4.5", 35 | "gulp-serve": "^1.2.0", 36 | "gulp-strip-debug": "^1.1.0", 37 | "gulp-uglify": "^1.5.3", 38 | "gulp-util": "^3.0.7", 39 | "gulp-wrap": "^0.11.0", 40 | "jshint": "^2.8.0", 41 | "karma": "^1.1.0", 42 | "karma-jasmine": "^1.0.2", 43 | "karma-phantomjs-launcher": "^1.0.1", 44 | "karma-spec-reporter": "0.0.26", 45 | "through2": "^2.0.1" 46 | }, 47 | "scripts": { 48 | "local": "gulp", 49 | "build": "gulp build" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/eventCalendar-colors.scss: -------------------------------------------------------------------------------- 1 | // Borders 2 | $border: #DDD; 3 | $todayBorder: #AAA; 4 | 5 | // Backgrounds 6 | $headerBackground: #FFF; 7 | $eventBackground: #DDD; 8 | $eventBackgroundSelected: #EEE; 9 | $rowBackground: #FFF; 10 | $previousMonthDayBackground: #F5F5F5; 11 | $buttonHover: rgba(158,158,158,0.2); 12 | 13 | // Fonts 14 | $eventFont: #666; 15 | $headerFont: #666; 16 | $monthDayfont: #999; 17 | $monthDayHeaderFont: #999; 18 | $showMoreLink: #4189b8; 19 | $eventSelectedFont: #888; 20 | $button: #333; 21 | $buttonDisabled: #CCC; 22 | 23 | 24 | 25 | 26 | md-event-calendar:not(._md) { 27 | md-event-calendar-header { 28 | color: $headerFont; 29 | background: $headerBackground; 30 | border-color: $border; 31 | 32 | md-event-calendar-next, md-event-calendar-prev { 33 | .md-arrow svg { 34 | fill: $headerFont; 35 | } 36 | } 37 | } 38 | 39 | .md-button:not([disabled]) { 40 | color: $button; 41 | &:hover { 42 | background: $buttonHover; 43 | } 44 | } 45 | 46 | .md-button[disabled] { 47 | color: $buttonDisabled; 48 | } 49 | 50 | 51 | md-event-calendar-month { 52 | .md-event-calendar-month-row-header { 53 | color: $monthDayHeaderFont; 54 | background: $headerBackground; 55 | border-color: $border; 56 | } 57 | 58 | .md-event-calendar-month-row { 59 | background: $rowBackground; 60 | border-color: $border; 61 | 62 | .md-event-calendar-month-cell-divider { 63 | border-color: $border; 64 | } 65 | 66 | .md-event-calendar-month-cell { 67 | border-color: $border; 68 | 69 | .md-event-calendar-month-cell-content { 70 | .md-event-calendar-create-link { 71 | color: $showMoreLink; 72 | } 73 | 74 | .md-event-calendar-cell-data-label { 75 | color: $monthDayfont; 76 | } 77 | 78 | .md-event-calendar-cell-event-show-more-link { 79 | color: $showMoreLink; 80 | } 81 | } 82 | 83 | 84 | 85 | &.different-month { 86 | background: $previousMonthDayBackground; 87 | } 88 | 89 | &.today { 90 | box-shadow: inset 0px 0px 0px 1px $todayBorder; 91 | 92 | .md-event-calendar-month-cell-content { 93 | .md-event-calendar-cell-data-label { 94 | color: $headerFont; 95 | } 96 | } 97 | 98 | .md-event-calendar-month-cell-divider { 99 | border-color: $todayBorder; 100 | } 101 | } 102 | 103 | &:last-child { 104 | border-color: $border; 105 | } 106 | } 107 | } 108 | } 109 | 110 | .md-event-calendar-cell-event { 111 | background: $eventBackground; 112 | color: $eventFont; 113 | 114 | &.md-selected { 115 | color: $eventBackgroundSelected; 116 | background: $eventSelectedFont; 117 | } 118 | 119 | &.md-continue-left, &.md-end-left { 120 | &:after { 121 | border-right-color: $eventBackground; 122 | } 123 | } 124 | 125 | &.md-continue-right, &.md-start-right { 126 | &:after { 127 | border-left-color: $eventBackground; 128 | } 129 | } 130 | 131 | 132 | &.md-selected { 133 | &.md-continue-left, &.md-end-left { 134 | &:after { 135 | border-right-color: $eventSelectedFont; 136 | } 137 | } 138 | 139 | &.md-continue-right, &.md-start-right { 140 | &:after { 141 | border-left-color: $eventSelectedFont; 142 | } 143 | } 144 | } 145 | } 146 | 147 | .md-event-calendar-show-more-container { 148 | .md-event-calendar-show-more-date-label { 149 | color: $monthDayfont; 150 | } 151 | 152 | .md-event-calendar-show-more-close svg { 153 | fill: $monthDayfont; 154 | } 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/eventCalendar-theme.scss: -------------------------------------------------------------------------------- 1 | md-event-calendar._md { 2 | md-event-calendar-header { 3 | color: '{{foreground-1}}'; 4 | background: '{{background-hue-1}}'; 5 | border-color: '{{foreground-4}}'; 6 | 7 | md-event-calendar-next, md-event-calendar-prev { 8 | .md-arrow svg { 9 | fill: '{{foreground-2}}'; 10 | } 11 | } 12 | } 13 | 14 | 15 | md-event-calendar-month { 16 | .md-event-calendar-month-row-header { 17 | color: '{{foreground-3}}'; 18 | background: '{{background-hue-1}}'; 19 | border-color: '{{foreground-4}}'; 20 | } 21 | 22 | .md-event-calendar-month-row { 23 | background: '{{background-hue-1}}'; 24 | border-color: '{{foreground-4}}'; 25 | 26 | 27 | .md-event-calendar-month-cell-divider { 28 | border-color: '{{foreground-4}}'; 29 | } 30 | 31 | .md-event-calendar-month-cell { 32 | border-color: '{{foreground-4}}'; 33 | 34 | .md-event-calendar-month-cell-content { 35 | .md-event-calendar-create-link { 36 | color: '{{primary-default}}'; 37 | } 38 | 39 | .md-event-calendar-cell-data-label { 40 | color: '{{foreground-3}}'; 41 | } 42 | 43 | .md-event-calendar-cell-event-show-more-link { 44 | color: '{{primary-default}}'; 45 | } 46 | } 47 | 48 | 49 | 50 | &.different-month { 51 | background: '{{background-hue-2}}'; 52 | } 53 | 54 | &.today { 55 | box-shadow: inset 0px 0px 0px 1px '{{primary-default}}'; 56 | 57 | .md-event-calendar-month-cell-content { 58 | .md-event-calendar-cell-data-label { 59 | color: '{{primary-default}}'; 60 | } 61 | } 62 | } 63 | 64 | &:last-child { 65 | border-color: '{{foreground-4}}'; 66 | } 67 | } 68 | } 69 | } 70 | 71 | .md-event-calendar-cell-event { 72 | background: '{{foreground-4}}'; 73 | color: '{{background-900}}'; 74 | 75 | &.md-selected { 76 | color: #EEE; 77 | background: '{{primary-default}}'; 78 | } 79 | 80 | &.md-continue-left, &.md-end-left { 81 | &:after { 82 | border-right-color:'{{foreground-4}}'; 83 | } 84 | } 85 | 86 | &.md-continue-right, &.md-start-right { 87 | &:after { 88 | border-left-color: '{{foreground-4}}'; 89 | } 90 | } 91 | 92 | 93 | &.md-selected { 94 | &.md-continue-left, &.md-end-left { 95 | &:after { 96 | border-right-color: '{{primary-default}}'; 97 | } 98 | } 99 | 100 | &.md-continue-right, &.md-start-right { 101 | &:after { 102 | border-left-color: '{{primary-default}}'; 103 | } 104 | } 105 | } 106 | } 107 | 108 | 109 | .md-event-calendar-show-more-container { 110 | .md-event-calendar-show-more-date-label { 111 | color: '{{foreground-3}}'; 112 | } 113 | .md-event-calendar-show-more-close svg { 114 | fill: '{{foreground-1}}'; 115 | } 116 | } 117 | } 118 | 119 | 120 | 121 | md-event-calendar._md.md-primary { 122 | md-event-calendar-header { 123 | color: '{{background-100}}'; 124 | background: '{{primary-default}}'; 125 | 126 | md-event-calendar-next, md-event-calendar-prev { 127 | .md-arrow svg { 128 | fill: '{{background-100}}'; 129 | } 130 | } 131 | } 132 | 133 | md-event-calendar-month { 134 | .md-event-calendar-month-row-header { 135 | color: '{{background-100}}'; 136 | background: '{{primary-default}}'; 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /src/eventCalendar.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc module 3 | * @name material.components.eventCalendar 4 | * 5 | * @description 6 | * Calendar Component 7 | */ 8 | angular 9 | .module('material.components.eventCalendar', []) 10 | .config(addEventCalendarTheme); 11 | 12 | 13 | /*@ngInject*/ 14 | function addEventCalendarTheme($injector, $provide, EVENT_CALENDAR_THEME) { 15 | var $mdThemingProvider; 16 | 17 | // if using angular material, then register the event theme css 18 | if ($injector.has('$mdThemingProvider')) { 19 | $mdThemingProvider = $injector.get('$mdThemingProvider'); 20 | $mdThemingProvider.registerStyles(EVENT_CALENDAR_THEME); 21 | } else { 22 | $provide.decorator('$$rAF', ["$delegate", rAFDecorator]); 23 | } 24 | } 25 | 26 | 27 | // polly fill rAF throttle if not using angular material 28 | function rAFDecorator($delegate) { 29 | $delegate.throttle = function(cb) { 30 | var queuedArgs, alreadyQueued, queueCb, context; 31 | return function debounced() { 32 | queuedArgs = arguments; 33 | context = this; 34 | queueCb = cb; 35 | if (!alreadyQueued) { 36 | alreadyQueued = true; 37 | $delegate(function() { 38 | queueCb.apply(context, Array.prototype.slice.call(queuedArgs)); 39 | alreadyQueued = false; 40 | }); 41 | } 42 | }; 43 | }; 44 | return $delegate; 45 | } 46 | -------------------------------------------------------------------------------- /src/eventCalendar.scss: -------------------------------------------------------------------------------- 1 | md-event-calendar { 2 | display: block; 3 | 4 | md-event-calendar-header { 5 | flex-direction: row; 6 | display: flex; 7 | line-height: 64px; 8 | align-items: center; 9 | border-style: solid; 10 | border-width: 1px 1px 0 1px; 11 | 12 | &.md-center { 13 | justify-content: center; 14 | } 15 | 16 | 17 | md-event-calendar-title { 18 | display: block; 19 | min-width: 170px; 20 | text-align: center; 21 | font-size: 20px; 22 | } 23 | 24 | md-event-calendar-next, md-event-calendar-prev { 25 | display: block; 26 | 27 | .md-arrow { 28 | cursor: pointer; 29 | height: 24px; 30 | width: 24px; 31 | &.md-left-arrow { 32 | transform: rotate(180deg); 33 | } 34 | } 35 | } 36 | } 37 | } 38 | 39 | 40 | 41 | 42 | 43 | md-event-calendar { 44 | // hide create link until hover over the day view. Hide it again if hovering 45 | // over an existing event. This specific structure is necessary in order to 46 | // preserve the correct precedence. 47 | 48 | .md-event-calendar-month-cell-content { 49 | .md-event-calendar-create-link { 50 | opacity: 0; 51 | } 52 | } 53 | 54 | .md-event-calendar-month-cell-content:hover { 55 | .md-event-calendar-create-link { 56 | opacity: 1; 57 | } 58 | } 59 | 60 | md-event-calendar-month { 61 | &.md-event-hover { 62 | .md-event-calendar-month-cell-content .md-event-calendar-create-link { 63 | opacity: 0; 64 | } 65 | } 66 | 67 | &.fitted { 68 | display: flex; 69 | flex-direction: column; 70 | } 71 | 72 | .md-event-calendar-month-row-header { 73 | display: flex; 74 | flex-direction: row; 75 | min-height: 36px; 76 | height: 36px; 77 | align-items: flex-end; 78 | font-size: 12px; 79 | font-weight: 500; 80 | padding-bottom: 12px; 81 | border-style: solid; 82 | border-width: 0 1px 1px 1px; 83 | 84 | .md-event-calendar-month-cell-header { 85 | flex: 1; 86 | padding-left: 6px; 87 | } 88 | } 89 | 90 | 91 | 92 | .md-event-calendar-month-row { 93 | flex: 1; 94 | display: flex; 95 | flex-direction: row; 96 | border-style: solid; 97 | border-width: 0 0 1px 0; 98 | 99 | .md-event-calendar-month-cell { 100 | position: relative; 101 | flex: 1; 102 | 103 | .md-event-calendar-month-cell-spacer { 104 | margin-top: 100% 105 | } 106 | 107 | .md-event-calendar-month-cell-divider { 108 | position: absolute; 109 | top: 0; 110 | bottom: 0; 111 | left: 0; 112 | border-style: solid; 113 | border-width: 0 1px 0 0; 114 | } 115 | 116 | .md-event-calendar-month-cell-content { 117 | position: absolute; 118 | top: 0; 119 | bottom: 0; 120 | left: 0; 121 | right: 0; 122 | 123 | .md-event-calendar-create-link { 124 | align-self: center; 125 | text-transform: uppercase; 126 | font-size: 14px; 127 | font-weight: 500; 128 | padding-right: 12px; 129 | cursor: pointer; 130 | transition: opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); 131 | } 132 | 133 | .md-event-calendar-cell-data-label { 134 | font-size: 13px; 135 | padding: 8px; 136 | flex: 1; 137 | } 138 | 139 | .md-event-calendar-cell-event-spacer { 140 | margin: 4px 0 4px 0; 141 | height: 23px; 142 | } 143 | 144 | .md-event-calendar-cell-event-show-more-link { 145 | font-size: 13px; 146 | padding: 4px; 147 | padding-left: 8px; 148 | cursor: pointer; 149 | } 150 | } 151 | 152 | 153 | &:last-child { 154 | border-style: solid; 155 | border-width: 0 1px 0 0; 156 | } 157 | } 158 | } 159 | } 160 | 161 | 162 | .md-event-calendar-cell-event { 163 | font-size: 12px; 164 | min-height: 15px; 165 | padding: 4px; 166 | cursor: pointer; 167 | 168 | &.md-single { 169 | margin: 4px; 170 | border-radius: 2px; 171 | text-overflow: ellipsis; 172 | white-space: nowrap; 173 | overflow: hidden; 174 | } 175 | 176 | &.md-start { 177 | margin: 4px 0 4px 4px; 178 | border-radius: 2px 0 0 2px; 179 | white-space: nowrap; 180 | z-index: 1; 181 | position: relative; 182 | } 183 | 184 | &.md-start-right { 185 | margin: 4px 13px 4px 4px; 186 | border-radius: 2px 0 0 2px; 187 | } 188 | 189 | &.md-end { 190 | margin: 4px 4px 4px 0; 191 | border-radius: 0 2px 2px 0; 192 | } 193 | 194 | &.md-end-left { 195 | margin: 4px 4px 4px 13px; 196 | border-radius: 0 2px 2px 0; 197 | } 198 | 199 | &.md-continue, &.md-continue-both { 200 | margin: 4px 0 4px 0; 201 | border-radius: 0; 202 | } 203 | 204 | &.md-continue-right { 205 | margin: 4px 13px 4px 0; 206 | border-radius: 0; 207 | white-space: nowrap; 208 | } 209 | 210 | &.md-continue-left { 211 | margin: 4px 0 4px 13px; 212 | border-radius: 0; 213 | white-space: nowrap; 214 | } 215 | 216 | &.md-continue-right, &.md-start-right { 217 | &:after { 218 | content: ''; 219 | position:absolute; 220 | height: 0; 221 | width: 0; 222 | right: 0; 223 | margin-top: -4px; 224 | border-top: 12px solid transparent; 225 | border-bottom: 11.5px solid transparent; 226 | border-left:13px solid #EEE; 227 | } 228 | } 229 | 230 | &.md-continue-left, &.md-end-left { 231 | &:after { 232 | content: ''; 233 | position:absolute; 234 | height: 0; 235 | width: 0; 236 | left: 0; 237 | margin-top: -4px; 238 | border-top: 12px solid transparent; 239 | border-bottom: 11.5px solid transparent; 240 | border-right:13px solid #EEE; 241 | } 242 | } 243 | 244 | .md-event-calendar-cell-event-time { 245 | font-weight: 500; 246 | padding-right: 6px; 247 | pointer-events: none; 248 | } 249 | 250 | span { 251 | pointer-events: none; 252 | } 253 | } 254 | 255 | // show more popup 256 | .md-event-calendar-show-more-content .md-event-calendar-cell-event { 257 | &.md-single, 258 | &.md-end, 259 | &.md-start-right 260 | { 261 | padding-left: 16px; 262 | } 263 | 264 | &.md-single, 265 | &.md-end, 266 | &.md-start-right 267 | { 268 | margin-left: 0; 269 | } 270 | 271 | &.md-single, 272 | &.md-end, 273 | &.md-end-left 274 | { 275 | margin-right: 0; 276 | } 277 | } 278 | 279 | 280 | 281 | .md-event-calendar-show-more-container { 282 | opacity: 0; 283 | // transition: opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); 284 | 285 | position: absolute; 286 | top: 0; 287 | left: 1px; 288 | width: 180px; 289 | background: #FFF; 290 | padding: 12px; 291 | padding-top: 7px; 292 | border-radius: 2px; 293 | z-index: 9; 294 | box-shadow: 0 7px 8px -4px rgba(0,0,0,.2), 295 | 0 13px 19px 2px rgba(0,0,0,.14), 296 | 0 5px 24px 4px rgba(0,0,0,.12); 297 | 298 | .md-event-calendar-show-more-content { 299 | position: relative; 300 | } 301 | 302 | &.show { 303 | opacity: 1; 304 | 305 | &:not(.no-transition) { 306 | transition: opacity 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); 307 | } 308 | } 309 | 310 | .md-event-calendar-show-more-close { 311 | display: flex; 312 | position: absolute; 313 | top: 6px; 314 | right: 7px; 315 | width: 16px; 316 | height: 16px; 317 | cursor: pointer; 318 | 319 | svg { 320 | pointer-events: none; 321 | } 322 | } 323 | 324 | .md-event-calendar-show-more-date-label { 325 | font-size: 13px; 326 | padding: 6px; 327 | margin-left: -11px; 328 | margin-top: -5px; 329 | } 330 | } 331 | } 332 | 333 | 334 | 335 | md-event-calendar.md-create-disabled { 336 | md-event-calendar-month { 337 | .md-event-calendar-month-row { 338 | .md-event-calendar-month-cell { 339 | .md-event-calendar-month-cell-content { 340 | .md-event-calendar-create-link { 341 | display: none; 342 | } 343 | } 344 | } 345 | } 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/icons/ic_close_black_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/icons/ic_keyboard_arrow_right_black_24px.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/js/eventCalendar.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.eventCalendar') 3 | .directive('mdEventCalendar', eventCalendarDirective); 4 | 5 | 6 | /** 7 | * @ngdoc directive 8 | * @name mdEventCalendar 9 | * @module material.components.eventCalendar 10 | * 11 | * @restrict E 12 | **/ 13 | function eventCalendarDirective($injector, $parse) { 14 | var $mdTheming = $injector.has('$mdTheming') ? $injector.get('$mdTheming') : undefined; 15 | var directive = { 16 | restrict: 'E', 17 | require: ['mdEventCalendar', '?ngModel'], 18 | scope: { 19 | events: '=mdEvents' 20 | }, 21 | compile: compile, 22 | controller: controller, 23 | controllerAs: 'mdEventCalendar', 24 | bindToController: true 25 | }; 26 | return directive; 27 | 28 | 29 | function compile(tElement, tAttr) { 30 | var eventClickFunc = tAttr.mdEventClick ? $parse(tAttr.mdEventClick, null, true) : undefined; 31 | var createEventClickFunc = tAttr.mdCreateEventClick ? $parse(tAttr.mdCreateEventClick, null, true) : undefined; 32 | var mdCreateDisabled = tAttr.mdCreateDisabled ? $parse(tAttr.mdCreateDisabled) : undefined; 33 | tElement.append(''); 34 | 35 | return function postLink(scope, element, attrs, ctrls) { 36 | var createDisabled = false; 37 | var mdEventCalendarCtrl = ctrls[0]; 38 | var ngModelCtrl = ctrls[1]; 39 | if ($mdTheming) { 40 | element.addClass('_md'); 41 | $mdTheming(element); 42 | } 43 | 44 | mdEventCalendarCtrl.isCreateDisabled = isCreateDisabled; 45 | mdEventCalendarCtrl.callEventClick = callEventClick; 46 | mdEventCalendarCtrl.createEventClick = createEventClick; 47 | 48 | if (ngModelCtrl) { 49 | ngModelCtrl.$render = render; 50 | mdEventCalendarCtrl.ngModelCtrl = ngModelCtrl; 51 | } 52 | 53 | 54 | function render() { 55 | var viewValue = ngModelCtrl.$viewValue || ngModelCtrl.$modelValue || []; 56 | mdEventCalendarCtrl.selectedEvents = [].concat(viewValue); 57 | } 58 | 59 | 60 | function callEventClick(e, eventItem) { 61 | if (!attrs.mdEventClick) { return; } 62 | eventClickFunc(scope.$parent, {$event: e, $selectedEvent: eventItem}); 63 | } 64 | 65 | function createEventClick(e, date) { 66 | if (!attrs.mdCreateEventClick) { return; } 67 | createEventClickFunc(scope.$parent, {$event: e, $date: date}); 68 | } 69 | 70 | function isCreateDisabled() { 71 | return createDisabled; 72 | } 73 | 74 | // watch for create being disabled 75 | if (mdCreateDisabled) { 76 | scope.$watch(function () { return mdCreateDisabled(scope.$parent); }, function (value) { 77 | createDisabled = value; 78 | element.toggleClass('md-create-disabled', value); 79 | }); 80 | 81 | // if no string was given check to see if the attr exists 82 | } else if (tAttr.mdCreateDisabled !== undefined) { 83 | createDisabled = true; 84 | element.addClass('md-create-disabled'); 85 | } 86 | }; 87 | } 88 | 89 | 90 | /*@ngInject*/ 91 | function controller($$mdEventCalendarUtil, $element, $attrs) { 92 | /*jshint validthis:true*/ 93 | var vm = this; 94 | 95 | vm.$element = $element; 96 | vm.labelProperty = $attrs.mdLabel || 'title'; 97 | vm.selectedEvents = []; 98 | vm.today = $$mdEventCalendarUtil.createDateAtMidnight(); 99 | vm.date = $$mdEventCalendarUtil.createDateAtMidnight(); 100 | vm.isToday = $$mdEventCalendarUtil.isSameDay(vm.date, new Date()); 101 | vm.monthDisplay = $$mdEventCalendarUtil.months[vm.date.getMonth()]; 102 | vm.yearDisplay = vm.date.getFullYear(); 103 | vm.isTodayDisabled = true; 104 | vm.showCreateLink = $attrs.mdShowCreateLink !== undefined && $attrs.mdShowCreateLink !== 'false'; 105 | vm.nextMonth = nextMonth; 106 | vm.previousMonth = previousMonth; 107 | vm.selectEvent = selectEvent; 108 | vm.setToday = setToday; 109 | vm.autoHeight = $attrs.autoHeight !== undefined; 110 | vm.fitted = $attrs.fitted !== undefined; 111 | vm.offset = vm.autoHeight === false || $attrs.autoHeight === '' || isNaN($attrs.autoHeight.replace('px', '')) ? 0 : parseInt($attrs.autoHeight.replace('px', '')); 112 | 113 | 114 | function nextMonth() { 115 | vm.date = $$mdEventCalendarUtil.getDateInNextMonth(vm.date); 116 | vm.monthDisplay = $$mdEventCalendarUtil.months[vm.date.getMonth()]; 117 | vm.yearDisplay = vm.date.getFullYear(); 118 | vm.isTodayDisabled = vm.date.getMonth() === (new Date()).getMonth(); 119 | } 120 | 121 | 122 | function previousMonth() { 123 | vm.date = $$mdEventCalendarUtil.getDateInPreviousMonth(vm.date); 124 | vm.monthDisplay = $$mdEventCalendarUtil.months[vm.date.getMonth()]; 125 | vm.yearDisplay = vm.date.getFullYear(); 126 | vm.isTodayDisabled = vm.date.getMonth() === (new Date()).getMonth(); 127 | } 128 | 129 | function setToday() { 130 | vm.date = new Date(); 131 | vm.monthDisplay = $$mdEventCalendarUtil.months[vm.date.getMonth()]; 132 | vm.yearDisplay = vm.date.getFullYear(); 133 | vm.isTodayDisabled = true; 134 | } 135 | 136 | 137 | function selectEvent(e, id) { 138 | // TODO create hashkeys for all events and store in reference object 139 | var value = vm.events.filter(function (item) { 140 | return item.$$mdEventId === id; 141 | }); 142 | 143 | if (vm.ngModelCtrl) { 144 | vm.ngModelCtrl.$setViewValue(value[0]); 145 | vm.ngModelCtrl.$render(); 146 | } 147 | vm.callEventClick(e, value[0]); 148 | 149 | return true; 150 | } 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/js/eventCalendarBuilder.service.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.eventCalendar') 3 | .factory('$$mdEventCalendarBuilder', mdEventCalendarBuilderService); 4 | 5 | 6 | var nextId = 0; 7 | 8 | /** 9 | * @ngdoc service 10 | * @name $$mdEventCalendarBuilder 11 | * @module material.components.eventCalendar 12 | **/ 13 | /*@ngInject*/ 14 | function mdEventCalendarBuilderService($$mdEventCalendarUtil, $templateCache) { 15 | var service = { 16 | month: month, 17 | showMore: showMore 18 | }; 19 | return service; 20 | 21 | 22 | 23 | function showMore(opts) { 24 | var date = opts.date; 25 | var selected = opts.selected || []; 26 | var events = opts.events ? filterEventsOnDay(date, opts.events) : []; 27 | var labelProperty = opts.labelProperty; 28 | var showMoreBody = document.createDocumentFragment(); 29 | var container = document.createElement('div'); 30 | container.classList.add('md-event-calendar-show-more-container'); 31 | var content = document.createElement('div'); 32 | content.classList.add('md-event-calendar-show-more-content'); 33 | var dateLabel = document.createElement('div'); 34 | dateLabel.classList.add('md-event-calendar-show-more-date-label'); 35 | dateLabel.textContent = $$mdEventCalendarUtil.dates[date.getDate()]; 36 | var closeButton = document.createElement('div'); 37 | closeButton.classList.add('md-event-calendar-show-more-close'); 38 | closeButton.innerHTML = $templateCache.get('icons/ic_close_black_24px.svg'); 39 | closeButton.setAttribute('md-show-more-close', 'true'); 40 | container.appendChild(dateLabel); 41 | container.appendChild(closeButton); 42 | container.appendChild(content); 43 | showMoreBody.appendChild(container); 44 | 45 | events.forEach(function (item) { 46 | var eventElement; 47 | var isStartThisDay = $$mdEventCalendarUtil.isSameDay(date, item.start); 48 | var isEndThisDay = $$mdEventCalendarUtil.isValidDate(item.end) ? $$mdEventCalendarUtil.isSameDay(date, item.end) : true; 49 | var eventOptions = { 50 | labelProperty: labelProperty, 51 | selected: selected 52 | }; 53 | 54 | if (isStartThisDay && isEndThisDay) { 55 | eventElement = createEventElement({className: 'single', hasLabel: true}, item, eventOptions); 56 | } else if (isStartThisDay) { 57 | eventElement = createEventElement({className: 'start-right', hasLabel: true}, item, eventOptions); 58 | } else if (isEndThisDay) { 59 | eventElement = createEventElement({className: 'end-left', hasLabel: true}, item, eventOptions); 60 | } else { 61 | eventElement = createEventElement({className: 'continue', hasLabel: true}, item, eventOptions); 62 | } 63 | 64 | content.appendChild(eventElement); 65 | }); 66 | 67 | 68 | var bounds = opts.cell.getBoundingClientRect(); 69 | var cellTop = opts.cell.offsetTop; 70 | var cellLeft = opts.cell.offsetLeft; 71 | container.style.top = cellTop+'px'; 72 | container.style.left = cellLeft+'px'; 73 | 74 | return showMoreBody; 75 | } 76 | 77 | 78 | function filterEventsOnDay(date, events) { 79 | return !events || !events.length ? [] : events.filter(function (item) { 80 | return $$mdEventCalendarUtil.isDateWithinRange(date, item.start, item.end || item.start); 81 | }).sort(function(a, b) { 82 | a = new Date(a.start); 83 | b = new Date(b.start); 84 | return a > b ? 1 : a < b ? -1 : 0; 85 | }); 86 | } 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | function month(options) { 100 | var calendarStartDate; 101 | var lastCalendarDayNum; 102 | var d = 0; 103 | var rowNumber = 1; 104 | var firstCalendarDay = true; 105 | var lastCalendarDay = false; 106 | var today = $$mdEventCalendarUtil.createDateAtMidnight(); 107 | var date = $$mdEventCalendarUtil.isValidDate(options.date) ? options.date : new Date(); 108 | var firstDayOfMonth = $$mdEventCalendarUtil.getFirstDateOfMonth(date); 109 | var firstDayOfTheWeek = (firstDayOfMonth.getDay() + 7) % 7; 110 | var numberOfDaysInMonth = $$mdEventCalendarUtil.getNumberOfDaysInMonth(date); 111 | var events = filterCurrentCalendar(date, options.events); 112 | events.forEach(cleanEvent); 113 | var selected = options.selected || []; 114 | var monthElement = createMonthElement(); 115 | var row = createRowElement(); 116 | monthElement.appendChild(row); 117 | var cellSize = options.cellHeight - 48; 118 | var maxEvents = Math.floor(cellSize / 24); 119 | 120 | 121 | // days from last month 122 | if (firstDayOfTheWeek > 0) { 123 | calendarStartDate = $$mdEventCalendarUtil.getFirstDateOfMonth(date); 124 | calendarStartDate.setDate(calendarStartDate.getDate() - firstDayOfTheWeek); 125 | while (d < firstDayOfTheWeek) { 126 | row.appendChild(createCellElement(getCellOptions(calendarStartDate, d, true))); 127 | firstCalendarDay = false; 128 | d += 1; 129 | calendarStartDate.setDate(calendarStartDate.getDate() + 1); 130 | } 131 | } 132 | 133 | 134 | 135 | // Add a cell for each day of the month, keeping track of the day of the week so that 136 | // we know when to start a new row. 137 | var dayOfWeek = firstDayOfTheWeek; 138 | var iterationDate = firstDayOfMonth; 139 | d = 1; 140 | while (d <= numberOfDaysInMonth) { 141 | // If we've reached the end of the week, start a new row. 142 | if (dayOfWeek === 7) { 143 | dayOfWeek = 0; 144 | row = createRowElement(); 145 | firstCalendarDay = false; 146 | monthElement.appendChild(row); 147 | } 148 | 149 | if (dayOfWeek === 6 && d === numberOfDaysInMonth) { 150 | lastCalendarDay = true; 151 | } 152 | 153 | iterationDate.setDate(d); 154 | row.appendChild(createCellElement(getCellOptions(iterationDate, dayOfWeek))); 155 | firstCalendarDay = false; 156 | dayOfWeek += 1; 157 | d += 1; 158 | } 159 | 160 | 161 | lastCalendarDayNum = d; 162 | // fill in the rest of the row with next month 163 | while (row.childNodes.length < 7) { 164 | if (dayOfWeek === 6) { 165 | lastCalendarDay = true; 166 | } 167 | iterationDate.setDate((d - lastCalendarDayNum) + 1); 168 | row.appendChild(createCellElement(getCellOptions(iterationDate, dayOfWeek, true))); 169 | dayOfWeek += 1; 170 | d += 1; 171 | } 172 | 173 | 174 | return monthElement; 175 | 176 | 177 | function getCellOptions(cellDate, dayOfWeek, differentMonth) { 178 | return { 179 | date: cellDate, // date for day on calendar 180 | today: today, // todays date at midnight 181 | dayOfWeek: dayOfWeek, // 0-6 (sun-sat) 182 | differentMonth: differentMonth || false, // previous or next month overflow days 183 | events: events, // events arr 184 | isFirstDay: firstCalendarDay, // is first day of current month view. not the first day of month(unless that is sunday) 185 | isLastDay: lastCalendarDay, // last day of calenday. not last day of month (unless sat) 186 | maxEvents: maxEvents, // max events that can be displayed in a day cell. based on cell size 187 | selected: selected, // array of selected events. from ngModel 188 | labelProperty: options.labelProperty, // name of the label property. default: title 189 | showCreateLink: options.showCreateLink // show create link on hover of day cell 190 | }; 191 | } 192 | } 193 | 194 | 195 | 196 | function createCellElement(options) { 197 | var cell = document.createElement('div'); 198 | cell.classList.add('md-event-calendar-month-cell'); 199 | cell.setAttribute('md-date', options.date); 200 | if (options.differentMonth === true) { cell.classList.add('different-month'); } 201 | if ($$mdEventCalendarUtil.isSameDay(options.date, options.today)) { cell.classList.add('today'); } 202 | 203 | var cellSpacer = document.createElement('div'); 204 | cellSpacer.classList.add('md-event-calendar-month-cell-spacer'); 205 | cell.appendChild(cellSpacer); 206 | 207 | var divider = document.createElement('div'); 208 | divider.classList.add('md-event-calendar-month-cell-divider'); 209 | cell.appendChild(divider); 210 | 211 | var cellContent = document.createElement('div'); 212 | cellContent.setAttribute('md-create-event', ''); 213 | cellContent.classList.add('md-event-calendar-month-cell-content'); 214 | cell.appendChild(cellContent); 215 | 216 | var cellHeader = document.createElement('div'); 217 | cellHeader.setAttribute('md-create-event', ''); 218 | cellHeader.classList.add('layout-row'); 219 | cellContent.appendChild(cellHeader); 220 | 221 | var dateLabel = document.createElement('div'); 222 | dateLabel.setAttribute('md-create-event', ''); 223 | dateLabel.classList.add('md-event-calendar-cell-data-label'); 224 | dateLabel.textContent = $$mdEventCalendarUtil.dates[options.date.getDate()]; 225 | cellHeader.appendChild(dateLabel); 226 | 227 | if (options.showCreateLink === true) { 228 | var createLink = document.createElement('div'); 229 | createLink.setAttribute('md-create-event', ''); 230 | createLink.classList.add('md-event-calendar-create-link'); 231 | createLink.textContent = 'Create'; 232 | cellHeader.appendChild(createLink); 233 | } 234 | 235 | createEventElements(cellContent, options); 236 | 237 | return cell; 238 | } 239 | 240 | 241 | 242 | function createEventElements(cellContent, options) { 243 | var i; 244 | var place = 0; 245 | var hasEvents = false; 246 | var matchingEvents = getEventsInRange(options.date, options.events); 247 | matchingEvents = setEventPlaces(matchingEvents, options.dayOfWeek); 248 | matchingEvents.every(function (eventItem, pos) { 249 | var type = getEventDisplayType(eventItem, options); 250 | var placeDiff = eventItem.$$place - place; 251 | hasEvents = true; 252 | place = eventItem.$$place + 1; 253 | i = 0; 254 | // add spacer items for overflow events from last day 255 | while (i < placeDiff) { 256 | if (place >= options.maxEvents) { 257 | cellContent.appendChild(createShowMore(matchingEvents.length - pos, options.date)); 258 | return false; 259 | } 260 | cellContent.appendChild(createEventSpacerElement()); 261 | i += 1; 262 | } 263 | 264 | if (place >= options.maxEvents) { 265 | cellContent.appendChild(createShowMore(matchingEvents.length - pos, options.date)); 266 | return false; 267 | } 268 | cellContent.appendChild(createEventElement(type, eventItem, options)); 269 | return true; 270 | }); 271 | 272 | if (hasEvents === true) { 273 | cellContent.classList.add('md-has-events'); 274 | } 275 | } 276 | 277 | 278 | function createShowMore(num, date) { 279 | var showMoreElement = document.createElement('div'); 280 | showMoreElement.classList.add('md-event-calendar-cell-event-show-more-link'); 281 | showMoreElement.textContent = num+' more'; 282 | showMoreElement.setAttribute('md-show-more', date.toISOString()); 283 | return showMoreElement; 284 | } 285 | 286 | 287 | function createEventSpacerElement() { 288 | var spacer = document.createElement('div'); 289 | spacer.classList.add('md-event-calendar-cell-event-spacer'); 290 | return spacer; 291 | } 292 | 293 | function createEventElement(type, eventItem, options) { 294 | var hash = getHashValue(eventItem); 295 | var eventElement = document.createElement('div'); 296 | eventElement.setAttribute('md-event-id', hash); 297 | eventElement.classList.add('md-event-calendar-cell-event'); 298 | eventElement.classList.add('md-'+type.className); 299 | if (eventItem.customClass) { eventElement.classList.add(eventItem.customClass); } 300 | 301 | if (type.hasLabel === true) { 302 | // do not show time for allDay events 303 | if (type.allDay !== true) { 304 | var dateLabelTime = document.createElement('span'); 305 | dateLabelTime.classList.add('md-event-calendar-cell-event-time'); 306 | dateLabelTime.textContent = $$mdEventCalendarUtil.formatEventTime(eventItem.start); 307 | eventElement.appendChild(dateLabelTime); 308 | } 309 | 310 | var dateLabelText = document.createElement('span'); 311 | dateLabelText.textContent = eventItem[options.labelProperty]; 312 | eventElement.appendChild(dateLabelText); 313 | } 314 | 315 | options.selected.every(function (sel) { 316 | if (sel.$$mdEventId !== undefined && sel.$$mdEventId === eventItem.$$mdEventId) { 317 | eventElement.classList.add('md-selected'); 318 | return false; 319 | } 320 | return true; 321 | }); 322 | 323 | return eventElement; 324 | } 325 | 326 | 327 | 328 | function getHashValue(value) { 329 | if (angular.isObject(value)) { 330 | return 'object_' + (value.$$mdEventId || (value.$$mdEventId = ++nextId)); 331 | } 332 | return 'id_' + (++nextId); 333 | } 334 | 335 | 336 | function getEventDisplayType(item, options) { 337 | var className; 338 | var hasLabel; 339 | 340 | var isStartThisDay = $$mdEventCalendarUtil.isSameDay(options.date, item.start); 341 | var isEndThisDay = $$mdEventCalendarUtil.isValidDate(item.end) ? $$mdEventCalendarUtil.isSameDay(options.date, item.end) : true; 342 | 343 | // single day event 344 | if (isStartThisDay && (options.allDay || isEndThisDay)) { 345 | className = 'single'; 346 | hasLabel = true; 347 | 348 | // starts today on last day of week 349 | } else if (isStartThisDay && options.dayOfWeek === 6) { 350 | className = 'start-right'; 351 | hasLabel = true; 352 | 353 | // starts today 354 | } else if (isStartThisDay) { 355 | className = 'start'; 356 | hasLabel = true; 357 | 358 | // ends on sunday 359 | } else if (isEndThisDay && options.dayOfWeek === 0) { 360 | className = 'end-left'; 361 | hasLabel = true; 362 | 363 | // last day of event 364 | } else if (isEndThisDay) { 365 | className = 'end'; 366 | hasLabel = options.isFirstDay; // add label if event is continuing from last month 367 | 368 | // continuation on sunday 369 | } else if (options.dayOfWeek === 0) { 370 | className = 'continue-left'; 371 | hasLabel = true; 372 | 373 | // continue on sat 374 | } else if (options.dayOfWeek === 6) { 375 | className = 'continue-right'; 376 | hasLabel = false; 377 | 378 | // continuation 379 | } else { 380 | className = 'continue'; 381 | hasLabel = false; 382 | } 383 | 384 | return { 385 | className: className, 386 | hasLabel: hasLabel, 387 | allDay: item.allDay || false 388 | }; 389 | } 390 | 391 | function getEventsInRange(date, events) { 392 | return events.filter(function (item) { 393 | return $$mdEventCalendarUtil.isDateWithinRange(date, item.start, item.end || item.start); 394 | }); 395 | } 396 | 397 | function setEventPlaces(events, dayOfWeek) { 398 | var takenPlaces = []; 399 | var sorted = events.sort(function (a, b) { 400 | if (a.end > b.end) { return -1; } 401 | if (a.end < b.end) { return 1; } 402 | return 0; 403 | }); 404 | 405 | // if not first day of week then get event palces. this is for dates that come from previous days 406 | // otherwise reset places 407 | sorted.forEach(function (item) { 408 | if (dayOfWeek === 0) { item.$$place = undefined; } 409 | else if (item.$$place !== undefined) { takenPlaces.push(item.$$place); } 410 | }); 411 | 412 | // fill in places that have not been set 413 | sorted.forEach(function(item) { 414 | if (item.$$place === undefined) { item.$$place = getPlace(); } 415 | }); 416 | 417 | // sort on places 418 | return sorted.sort(function(a, b) { 419 | if (a.$$place > b.$$place) { return 1; } 420 | if (a.$$place < b.$$place) { return -1; } 421 | return 0; 422 | }); 423 | 424 | 425 | // find lowest place not taken 426 | function getPlace() { 427 | var place = 0; 428 | while (takenPlaces.indexOf(place) !== -1) { 429 | place++; 430 | } 431 | takenPlaces.push(place); 432 | return place; 433 | } 434 | } 435 | 436 | 437 | function createMonthElement() { 438 | var monthBody = document.createDocumentFragment(); 439 | var headerRow = document.createElement('div'); 440 | headerRow.classList.add('md-event-calendar-month-row-header'); 441 | monthBody.appendChild(headerRow); 442 | 443 | // add header day labels 444 | $$mdEventCalendarUtil.days.forEach(function (name) { 445 | var dayHeader = document.createElement('div'); 446 | dayHeader.classList.add('md-event-calendar-month-cell-header'); 447 | dayHeader.textContent = name.slice(0,3).toLowerCase(); 448 | headerRow.appendChild(dayHeader); 449 | }); 450 | 451 | return monthBody; 452 | } 453 | 454 | function createRowElement() { 455 | var row = document.createElement('div'); 456 | row.classList.add("md-event-calendar-month-row"); 457 | return row; 458 | } 459 | 460 | 461 | function filterCurrentCalendar(date, events) { 462 | if (!events || !events.length) { return []; } 463 | // back fill 6 days for posibility of last month days showing up 464 | var start = $$mdEventCalendarUtil.getFirstDateOfMonth(date).getDate(-6); 465 | // front fill 6 days for posibility of next month days showing up 466 | var end = $$mdEventCalendarUtil.getFirstDateOfMonth(date).getDate(37); 467 | 468 | return events.filter(function (item) { 469 | if (!$$mdEventCalendarUtil.isValidDate(item.start)) { return false; } 470 | if ($$mdEventCalendarUtil.isDateWithinRange(item.start, start, end)) { return true; } 471 | if (!$$mdEventCalendarUtil.isValidDate(item.end)) { return false; } 472 | if ($$mdEventCalendarUtil.isDateWithinRange(item.end, start, end)) { return true; } 473 | return false; 474 | }).sort(function(a, b) { 475 | a = new Date(a.start); 476 | b = new Date(b.start); 477 | return a > b ? 1 : a < b ? -1 : 0; 478 | }); 479 | } 480 | 481 | 482 | function cleanEvent(item) { 483 | item.$$hide = undefined; 484 | item.$$place = undefined; 485 | } 486 | } 487 | -------------------------------------------------------------------------------- /src/js/eventCalendarMonth.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.eventCalendar') 3 | .directive('mdEventCalendarMonth', eventCalendarMonthDirective); 4 | 5 | 6 | /** 7 | * @ngdoc directive 8 | * @name eventCalendarMonthDirective 9 | * @module material.components.eventCalendar 10 | * 11 | * @restrict E 12 | **/ 13 | /*@ngInject*/ 14 | function eventCalendarMonthDirective($$mdEventCalendarBuilder, $window, $$rAF, $timeout) { 15 | var directive = { 16 | restrict: 'E', 17 | require: '^mdEventCalendar', 18 | link: link 19 | }; 20 | return directive; 21 | 22 | 23 | function link(scope, element, attrs, ctrl) { 24 | var showMoreData; 25 | 26 | var mdEventCalendarCtrl = ctrl; 27 | var rebuildThrottle = $$rAF.throttle(function () { 28 | scope.$evalAsync(function () { 29 | setAutoHeight(); 30 | element.toggleClass('fitted', mdEventCalendarCtrl.fitted); 31 | buildView(); 32 | }); 33 | }); 34 | 35 | scope.$watch(function () { return mdEventCalendarCtrl.date; }, buildView); 36 | scope.$watch(function () { return mdEventCalendarCtrl.events; }, function (newValue, oldValue) { 37 | if (newValue === oldValue) { return; } 38 | buildView(); 39 | }, true); 40 | scope.$watch(function () { return mdEventCalendarCtrl.selectedEvents; }, function (newValue, oldValue) { 41 | if (newValue === oldValue) { return; } 42 | buildView(); 43 | }, true); 44 | 45 | angular.element($window).on('resize', rebuildThrottle); 46 | scope.$on('$destroy', function () { 47 | angular.element($window).off('resize', rebuildThrottle); 48 | }); 49 | 50 | $$rAF(function () { 51 | setAutoHeight(); 52 | element.toggleClass('fitted', mdEventCalendarCtrl.fitted); 53 | }); 54 | 55 | function setAutoHeight() { 56 | if (!mdEventCalendarCtrl.autoHeight) { return; } 57 | mdEventCalendarCtrl.fitted = true; 58 | var top = element[0].getBoundingClientRect().top; 59 | var height = $window.innerHeight - top - mdEventCalendarCtrl.offset; 60 | element.css('height', height+'px'); 61 | } 62 | 63 | hideCreateLinkOnEventItemHover(); 64 | 65 | // When user mouses over an existing event, add a class of md-event-hover to 66 | // the month element, so that the create link is hidden from view. 67 | function hideCreateLinkOnEventItemHover() { 68 | element.on('mouseenter', function () { 69 | element.on('mousemove', checkForEventItemRAF); 70 | }); 71 | 72 | element.on('mouseleave', function () { 73 | element.off('mousemove', checkForEventItemRAF); 74 | element.removeClass('md-event-hover'); 75 | }); 76 | 77 | var lastHoverItem; 78 | var checkForEventItemRAF = $$rAF.throttle(checkForEventItem); 79 | function checkForEventItem(e) { 80 | if (mdEventCalendarCtrl.isCreateDisabled() === true) { return; } 81 | if (lastHoverItem === e.target) { return; } 82 | lastHoverItem = e.target; 83 | 84 | var targetIsEvent = !!e.target.getAttribute('md-event-id'); 85 | 86 | element.toggleClass('md-event-hover', targetIsEvent); 87 | } 88 | } 89 | 90 | element.on('click', function (e) { 91 | if (mdEventCalendarCtrl.isCreateDisabled() === true) { return; } 92 | 93 | var eventId = e.target.getAttribute('md-event-id'); 94 | var showMore = e.target.getAttribute('md-show-more'); 95 | var showMoreClose = e.target.getAttribute('md-show-more-close'); 96 | var createEvent = e.target.getAttribute('md-create-event') !== null; 97 | 98 | if (eventId) { 99 | var eventItem = getIdFromHash(eventId); 100 | scope.$apply(function () { 101 | mdEventCalendarCtrl.selectEvent(e, getIdFromHash(eventId)); 102 | }); 103 | return; 104 | } 105 | 106 | removeShowMore(true); 107 | if (showMore) { 108 | addShowMore(new Date(showMore)); 109 | } 110 | 111 | if (createEvent) { 112 | var cellDate = getDateFromCreate(e.target); 113 | if (cellDate !== undefined) { 114 | scope.$apply(function () { 115 | mdEventCalendarCtrl.createEventClick(e, cellDate); 116 | }); 117 | } 118 | } 119 | }); 120 | 121 | function getDateFromCreate(el) { 122 | var dateString = el.getAttribute('md-date'); 123 | while (dateString === null && el.nodeName !== 'MD-EVENT-CALENDAR-MONTH') { 124 | el = el.parentNode; 125 | dateString = el.getAttribute('md-date'); 126 | } 127 | 128 | return dateString === null ? undefined : new Date(dateString); 129 | } 130 | 131 | 132 | function buildView() { 133 | var cellHeight; 134 | if (mdEventCalendarCtrl.fitted) { 135 | cellHeight = element[0].offsetHeight / 5; 136 | } else { 137 | cellHeight = mdEventCalendarCtrl.$element[0].offsetWidth / 7; 138 | } 139 | 140 | var monthElement = $$mdEventCalendarBuilder.month({ 141 | date: mdEventCalendarCtrl.date, 142 | events: mdEventCalendarCtrl.events, 143 | selected: mdEventCalendarCtrl.selectedEvents, 144 | labelProperty: mdEventCalendarCtrl.labelProperty, 145 | showCreateLink: mdEventCalendarCtrl.showCreateLink, 146 | cellHeight: cellHeight 147 | }); 148 | element.empty(); 149 | element.append(monthElement); 150 | 151 | buildShowMore(); 152 | } 153 | 154 | 155 | function addShowMore(date) { 156 | showMoreData = { 157 | date: date 158 | }; 159 | buildShowMore(true); 160 | } 161 | 162 | function buildShowMore(animate) { 163 | if (showMoreData === undefined) { return; } 164 | if (showMoreData.element) { 165 | angular.element(showMoreData.element).remove(); 166 | showMoreData.element = undefined; 167 | } 168 | 169 | var cell = getCellFromDate(showMoreData.date); 170 | var showMoreFragment = $$mdEventCalendarBuilder.showMore({ 171 | date: showMoreData.date, 172 | selected: mdEventCalendarCtrl.selectedEvents, 173 | events: mdEventCalendarCtrl.events, 174 | labelProperty: mdEventCalendarCtrl.labelProperty, 175 | cell: cell 176 | }); 177 | 178 | element.append(showMoreFragment); 179 | showMoreData.element = element[0].lastChild; 180 | positonShowMore(); 181 | 182 | if (animate) { 183 | // add class after element added so animation 184 | $timeout(function () { 185 | angular.element(showMoreData.element).addClass('show'); 186 | }, 0); 187 | } else { 188 | angular.element(showMoreData.element).addClass('no-transition'); 189 | angular.element(showMoreData.element).addClass('show'); 190 | } 191 | } 192 | 193 | function positonShowMore() { 194 | var showMoreBounds = showMoreData.element.getBoundingClientRect(); 195 | var minTop = $window.innerHeight - showMoreBounds.height; 196 | var minLeft = $window.innerWidth - showMoreBounds.width; 197 | var leftDiff = showMoreBounds.left - minLeft; 198 | 199 | if (showMoreBounds.top > minTop) { 200 | showMoreData.element.style.top = minTop+'px'; 201 | } 202 | 203 | if (leftDiff > 0) { 204 | showMoreData.element.style.left = minLeft+'px'; 205 | // offset date 206 | 207 | leftDiff -= 10; 208 | if (leftDiff > 0) { 209 | showMoreData.element.querySelector('.md-event-calendar-show-more-date-label').style.marginLeft = leftDiff+'px'; 210 | } 211 | } 212 | } 213 | 214 | function getCellFromDate(date) { 215 | return element[0].querySelector('[md-date="'+date+'"]'); 216 | } 217 | 218 | function removeShowMore(animate) { 219 | if (!showMoreData) { return; } 220 | 221 | var el = showMoreData.element; 222 | showMoreData = undefined; 223 | 224 | if (animate) { 225 | angular.element(el).removeClass('no-transition'); 226 | $timeout(function () { 227 | angular.element(el).removeClass('show'); 228 | }, 0); 229 | $timeout(function () { 230 | el.remove(); 231 | el = undefined; 232 | }, 400); 233 | } else { 234 | el.remove(); 235 | el = undefined; 236 | } 237 | } 238 | 239 | 240 | function getClosestCell(el) { 241 | var target = angular.element(el).parent(); 242 | while (target.hasClass('md-event-calendar-month-cell') === false) { 243 | target = target.parent(); 244 | } 245 | return target; 246 | } 247 | 248 | function getIdFromHash(id) { 249 | return parseInt(id.replace('object_', '')); 250 | } 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/js/eventCalendarNext.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.eventCalendar') 3 | .directive('mdEventCalendarNext', mdEventCalendarNextDirective); 4 | 5 | /** 6 | * @ngdoc directive 7 | * @name mdEventCalendarNextDirective 8 | * @module material.components.eventCalendar 9 | * 10 | * @restrict E 11 | **/ 12 | function mdEventCalendarNextDirective() { 13 | var directive = { 14 | restrict: 'E', 15 | require: '^mdEventCalendar', 16 | template: ''+ 17 | '
'+ 18 | '
' 19 | }; 20 | return directive; 21 | } 22 | -------------------------------------------------------------------------------- /src/js/eventCalendarPrev.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.eventCalendar') 3 | .directive('mdEventCalendarPrev', mdEventCalendarPrevDirective); 4 | 5 | /** 6 | * @ngdoc directive 7 | * @name mdEventCalendarPrevDirective 8 | * @module material.components.eventCalendar 9 | * 10 | * @restrict E 11 | **/ 12 | function mdEventCalendarPrevDirective() { 13 | var directive = { 14 | restrict: 'E', 15 | require: '^mdEventCalendar', 16 | template: ''+ 17 | '
'+ 18 | '
' 19 | }; 20 | return directive; 21 | } 22 | -------------------------------------------------------------------------------- /src/js/eventCalendarTitle.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.eventCalendar') 3 | .directive('mdEventCalendarTitle', mdEventCalendarTitleDirective); 4 | 5 | /** 6 | * @ngdoc directive 7 | * @name mdEventCalendarTitleDirective 8 | * @module material.components.eventCalendar 9 | * 10 | * @restrict E 11 | **/ 12 | function mdEventCalendarTitleDirective() { 13 | var directive = { 14 | restrict: 'E', 15 | require: '^mdEventCalendar', 16 | template: '
{{mdEventCalendar.monthDisplay + " " + mdEventCalendar.yearDisplay}}
', 17 | link: link 18 | }; 19 | return directive; 20 | 21 | function link(scope, element, attrs, ctrl) { 22 | scope.mdEventCalendar = ctrl; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/js/eventCalendarToday.directive.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.eventCalendar') 3 | .directive('mdEventCalendarToday', mdEventCalendarTodayDirective); 4 | 5 | /** 6 | * @ngdoc directive 7 | * @name mdEventCalendarTodayDirective 8 | * @module material.components.eventCalendar 9 | * 10 | * @restrict E 11 | **/ 12 | function mdEventCalendarTodayDirective() { 13 | var directive = { 14 | restrict: 'E', 15 | require: '^mdEventCalendar', 16 | template: 'Today' 17 | }; 18 | return directive; 19 | } 20 | -------------------------------------------------------------------------------- /src/js/utij.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('material.components.eventCalendar') 3 | .factory('$$mdEventCalendarUtil', utilService); 4 | 5 | 6 | function utilService($injector, $locale, $filter) { 7 | var $mdDateLocale = $injector.has('$mdDateLocale') ? $injector.get('$mdDateLocale') : undefined; 8 | var dateFilter = $filter('date'); 9 | var months = $mdDateLocale ? $mdDateLocale.months : $locale.DATETIME_FORMATS.MONTH; 10 | var shortMonths = $mdDateLocale ? $mdDateLocale.shortMonths : $locale.DATETIME_FORMATS.SHORTMONTH; 11 | var days = $mdDateLocale ? $mdDateLocale.days : $locale.DATETIME_FORMATS.DAY; 12 | var shortDays = $mdDateLocale ? $mdDateLocale.shortDays : $locale.DATETIME_FORMATS.SHORTDAY.map(function(day) { 13 | return day.substring(0, 1); 14 | }); 15 | // The default dates are simply the numbers 1 through 31. 16 | var defaultDates = Array(32); 17 | for (var i = 1; i <= 31; i++) { 18 | defaultDates[i] = i; 19 | } 20 | 21 | 22 | var service = { 23 | months: months, 24 | shortMonths: shortMonths, 25 | days: days, 26 | dates: $mdDateLocale ? $mdDateLocale.dates : defaultDates, 27 | shortDays: shortDays, 28 | isValidDate: isValidDate, 29 | createDateAtMidnight: createDateAtMidnight, 30 | getDateInNextMonth: getDateInNextMonth, 31 | getDateInPreviousMonth: getDateInPreviousMonth, 32 | getFirstDateOfMonth: getFirstDateOfMonth, 33 | getNumberOfDaysInMonth: getNumberOfDaysInMonth, 34 | weekNumberFormatter: $mdDateLocale ? $mdDateLocale.weekNumberFormatter : weekNumberFormatter, 35 | isSameMonthAndYear: isSameMonthAndYear, 36 | isSameDay: isSameDay, 37 | isDateWithinRange: isDateWithinRange, 38 | formatEventTime: formatEventTime 39 | }; 40 | return service; 41 | 42 | 43 | 44 | 45 | function formatEventTime(date) { 46 | return dateFilter(date, 'h:mm a'); 47 | } 48 | 49 | 50 | 51 | /** 52 | * Checks if a date is within a min and max range, ignoring the time component. 53 | * If minDate or maxDate are not dates, they are ignored. 54 | * @param {Date} date 55 | * @param {Date} minDate 56 | * @param {Date} maxDate 57 | */ 58 | function isDateWithinRange(date, minDate, maxDate) { 59 | var dateAtMidnight = createDateAtMidnight(date); 60 | var minDateAtMidnight = isValidDate(minDate) ? createDateAtMidnight(minDate) : null; 61 | var maxDateAtMidnight = isValidDate(maxDate) ? createDateAtMidnight(maxDate) : null; 62 | return (!minDateAtMidnight || minDateAtMidnight <= dateAtMidnight) && 63 | (!maxDateAtMidnight || maxDateAtMidnight >= dateAtMidnight); 64 | } 65 | 66 | 67 | 68 | /** 69 | * Default week number formatter. 70 | * @param number 71 | * @returns {string} 72 | */ 73 | function weekNumberFormatter(number) { 74 | return 'Week ' + number; 75 | } 76 | 77 | 78 | /** 79 | * Sets a date's time to midnight. 80 | * @param {Date} date 81 | */ 82 | function setDateTimeToMidnight(date) { 83 | if (isValidDate(date)) { 84 | date.setHours(0, 0, 0, 0); 85 | } 86 | } 87 | 88 | /** 89 | * Checks whether a date is valid. 90 | * @param {Date} date 91 | * @return {boolean} Whether the date is a valid Date. 92 | */ 93 | function isValidDate(date) { 94 | return date && date.getTime && !isNaN(date.getTime()); 95 | } 96 | 97 | 98 | /** 99 | * Creates a date with the time set to midnight. 100 | * Drop-in replacement for two forms of the Date constructor: 101 | * 1. No argument for Date representing now. 102 | * 2. Single-argument value representing number of seconds since Unix Epoch 103 | * or a Date object. 104 | * @param {number|Date=} opt_value 105 | * @return {Date} New date with time set to midnight. 106 | */ 107 | function createDateAtMidnight(opt_value) { 108 | var date; 109 | if (opt_value === undefined) { 110 | date = new Date(); 111 | } else { 112 | date = new Date(opt_value); 113 | } 114 | setDateTimeToMidnight(date); 115 | return date; 116 | } 117 | 118 | 119 | 120 | /** 121 | * Get an arbitrary date in the month after the given date's month. 122 | * @param date 123 | * @returns {Date} 124 | */ 125 | function getDateInNextMonth(date) { 126 | return new Date(date.getFullYear(), date.getMonth() + 1, 1); 127 | } 128 | 129 | 130 | 131 | /** 132 | * Get an arbitrary date in the month before the given date's month. 133 | * @param date 134 | * @returns {Date} 135 | */ 136 | function getDateInPreviousMonth(date) { 137 | return new Date(date.getFullYear(), date.getMonth() - 1, 1); 138 | } 139 | 140 | 141 | 142 | 143 | /** 144 | * Gets the first day of the month for the given date's month. 145 | * @param {Date} date 146 | * @returns {Date} 147 | */ 148 | function getFirstDateOfMonth(date) { 149 | return new Date(date.getFullYear(), date.getMonth(), 1); 150 | } 151 | 152 | 153 | /** 154 | * Gets the number of days in the month for the given date's month. 155 | * @param date 156 | * @returns {number} 157 | */ 158 | function getNumberOfDaysInMonth(date) { 159 | return new Date(date.getFullYear(), date.getMonth() + 1, 0).getDate(); 160 | } 161 | 162 | 163 | /** 164 | * Gets whether two dates have the same month and year. 165 | * @param {Date} d1 166 | * @param {Date} d2 167 | * @returns {boolean} 168 | */ 169 | function isSameMonthAndYear(d1, d2) { 170 | return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth(); 171 | } 172 | 173 | 174 | /** 175 | * Gets whether two dates are the same day (not not necesarily the same time). 176 | * @param {Date} d1 177 | * @param {Date} d2 178 | * @returns {boolean} 179 | */ 180 | function isSameDay(d1, d2) { 181 | return d1.getDate() == d2.getDate() && isSameMonthAndYear(d1, d2); 182 | } 183 | } 184 | --------------------------------------------------------------------------------