├── .agignore ├── .editorconfig ├── .eslintrc ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md └── stale.yml ├── .gitignore ├── .htmlhintrc ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── dist ├── css │ ├── angular-bootstrap-calendar.css │ ├── angular-bootstrap-calendar.min.css │ └── angular-bootstrap-calendar.min.css.map └── js │ ├── angular-bootstrap-calendar-tpls.js │ ├── angular-bootstrap-calendar-tpls.min.js │ ├── angular-bootstrap-calendar-tpls.min.js.map │ ├── angular-bootstrap-calendar.js │ ├── angular-bootstrap-calendar.min.js │ └── angular-bootstrap-calendar.min.js.map ├── docs ├── docs.css ├── docs.js └── examples │ ├── all-day-events │ ├── javascript.js │ └── markup.html │ ├── badge-total │ ├── javascript.js │ └── markup.html │ ├── cell-is-open │ ├── javascript.js │ └── markup.html │ ├── cell-modifier │ ├── javascript.js │ └── markup.html │ ├── clickable-events │ ├── javascript.js │ └── markup.html │ ├── custom-event-class │ ├── javascript.js │ └── markup.html │ ├── custom-templates │ ├── javascript.js │ └── markup.html │ ├── day-view-all-times │ ├── javascript.js │ └── markup.html │ ├── day-view-segment-size │ ├── javascript.js │ └── markup.html │ ├── day-view-split │ ├── javascript.js │ └── markup.html │ ├── day-view-start-end │ ├── javascript.js │ └── markup.html │ ├── disable-tooltips │ ├── javascript.js │ └── markup.html │ ├── disabling-views │ ├── javascript.js │ └── markup.html │ ├── draggable-events │ ├── javascript.js │ └── markup.html │ ├── draggable-external-events │ ├── javascript.js │ └── markup.html │ ├── editable-deletable-events │ ├── javascript.js │ └── markup.html │ ├── examples.json │ ├── exclude-weekdays │ ├── javascript.js │ └── markup.html │ ├── grouping-events │ ├── javascript.js │ └── markup.html │ ├── helpers.js │ ├── i18n │ ├── javascript.js │ └── markup.html │ ├── kitchen-sink │ ├── javascript.js │ └── markup.html │ ├── optional-event-end-dates │ ├── javascript.js │ └── markup.html │ ├── recurring-events │ ├── javascript.js │ └── markup.html │ ├── resizable-events │ ├── javascript.js │ └── markup.html │ ├── select-range │ ├── javascript.js │ └── markup.html │ ├── side-time-position │ ├── javascript.js │ └── markup.html │ ├── slide-box-disabled │ ├── javascript.js │ └── markup.html │ ├── timespan-click │ ├── javascript.js │ └── markup.html │ └── view-change-click │ ├── javascript.js │ └── markup.html ├── index.html ├── karma.conf.js ├── package-lock.json ├── package.json ├── src ├── directives │ ├── mwlCalendar.js │ ├── mwlCalendarDay.js │ ├── mwlCalendarHourList.js │ ├── mwlCalendarMonth.js │ ├── mwlCalendarSlideBox.js │ ├── mwlCalendarWeek.js │ ├── mwlCalendarYear.js │ ├── mwlCollapseFallback.js │ ├── mwlDateModifier.js │ ├── mwlDragSelect.js │ ├── mwlDraggable.js │ ├── mwlDroppable.js │ ├── mwlDynamicDirectiveTemplate.js │ ├── mwlElementDimensions.js │ └── mwlResizable.js ├── entry.js ├── filters │ ├── calendarDate.js │ ├── calendarLimitTo.js │ ├── calendarTruncateEventTitle.js │ └── calendarTrustAsHtml.js ├── less │ ├── calendar.less │ ├── day.less │ ├── events.less │ ├── grid-mixin.less │ ├── grid.less │ ├── month.less │ ├── theme.less │ ├── variables.less │ └── week.less ├── services │ ├── calendarConfig.js │ ├── calendarEventTitle.js │ ├── calendarHelper.js │ ├── calendarTitle.js │ ├── interact.js │ └── moment.js └── templates │ ├── calendar.html │ ├── calendarDayView.html │ ├── calendarHourList.html │ ├── calendarMonthCell.html │ ├── calendarMonthCellEvents.html │ ├── calendarMonthView.html │ ├── calendarSlideBox.html │ ├── calendarWeekView.html │ └── calendarYearView.html ├── test ├── .eslintrc └── unit │ ├── config.spec.js │ ├── directives │ ├── mwlCalendar.spec.js │ ├── mwlCalendarDay.spec.js │ ├── mwlCalendarHourList.spec.js │ ├── mwlCalendarMonth.spec.js │ ├── mwlCalendarSlideBox.spec.js │ ├── mwlCalendarWeek.spec.js │ ├── mwlCalendarYear.spec.js │ ├── mwlCollapseFallback.spec.js │ ├── mwlDateModifier.spec.js │ ├── mwlDragSelect.spec.js │ ├── mwlDraggable.spec.js │ ├── mwlDroppable.spec.js │ ├── mwlDynamicDirectiveTemplate.spec.js │ ├── mwlElementDimensions.spec.js │ └── mwlResizable.spec.js │ ├── entry.js │ ├── filters │ ├── calendarDate.spec.js │ ├── calendarLimitTo.spec.js │ ├── calendarTruncateEventTitle.spec.js │ └── calendarTrustAsHtml.spec.js │ └── services │ ├── calendarEventTitle.spec.js │ ├── calendarHelper.spec.js │ ├── calendarTitle.spec.js │ ├── interact.spec.js │ └── moment.spec.js ├── webpack.config.build.js └── webpack.config.js /.agignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | 8 | [*] 9 | 10 | # Change these settings to your own preference 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # We recommend you to keep these unchanged 15 | end_of_line = lf 16 | charset = utf-8 17 | trim_trailing_whitespace = true 18 | insert_final_newline = true 19 | 20 | [*.md] 21 | trim_trailing_whitespace = false 22 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "mwl", 3 | "rules": { 4 | "angular/controller-name": [2, "/[A-Z].*Ctrl/"], 5 | "angular/deferred": 2, 6 | "angular/directive-name": [2, "mwl"], 7 | "angular/empty-controller": 2, 8 | "angular/di-unused": 2 9 | }, 10 | "env": { 11 | "node": true 12 | }, 13 | "plugins": [ 14 | "angular" 15 | ], 16 | "globals": { 17 | "EXCLUDE_TEMPLATES": true 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Reporting issues 2 | Please read this guide before filing issues. Thanks! 3 | 4 | ### Support queries 5 | Please use the following sources for any questions on how to use the module: 6 | * The [examples list](http://mattlewis92.github.io/angular-bootstrap-calendar/) for demos of how to use all features of the directive. 7 | * Search the [existing issues list](https://github.com/mattlewis92/angular-bootstrap-calendar/issues?q=is%3Aissue+is%3Aclosed), most of the time there has already been a similar issue filed. 8 | * For everything else please use [stackoverflow](http://stackoverflow.com/questions/ask/advice) or [gitter](https://gitter.im/mattlewis92/angular-bootstrap-calendar). The github issue tracker is primarily for bug reports + feature requests. 9 | 10 | ### Bug reports 11 | Please include a plunker or something similar that clearly reproduces your problem or your issue may be closed. You can use this as a [starter template](http://plnkr.co/edit/LE4F4U7AnnD3tjM9ZH4G?p=preview). 12 | 13 | ### Feature requests 14 | Please clearly describe what you require and your use case. A lot of the time you can use the existing API hooks + custom templates to add your own features. Please check the [examples list](http://mattlewis92.github.io/angular-bootstrap-calendar/) first to see if the feature you've requested has already been implemented. Features that can easily be implemented in userland are unlikely to be accepted. 15 | 16 | ### Pull requests 17 | Follow the [dev guide](https://github.com/mattlewis92/angular-bootstrap-calendar#development) for getting up and running. Please ensure all tests pass first by running `npm test`. This project uses the [angular commit conventions](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit-message-format). You can use the commit wizard for easily generating commit messages by running `npm run commit`. 18 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 6 | ### Bug description / Feature request: 7 | 8 | 9 | ### Link to minimally-working plunker that reproduces the issue (starter template: http://plnkr.co/edit/LE4F4U7AnnD3tjM9ZH4G?p=preview) 10 | 11 | 12 | ### Versions 13 | 14 | Angular: 15 | 16 | Calendar library: 17 | 18 | Browser name and version: 19 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .tmp 3 | .idea 4 | build 5 | coverage 6 | *.log 7 | -------------------------------------------------------------------------------- /.htmlhintrc: -------------------------------------------------------------------------------- 1 | { 2 | "tagname-lowercase": true, 3 | "attr-lowercase": true, 4 | "attr-value-double-quotes": true, 5 | "attr-value-not-empty": false, 6 | "attr-no-duplication": true, 7 | "doctype-first": false, 8 | "tag-pair": true, 9 | "tag-self-close": false, 10 | "spec-char-escape": true, 11 | "id-unique": true, 12 | "src-not-empty": true, 13 | "doctype-html5": true, 14 | "space-tab-mixed-disabled": true 15 | } 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - '8' 5 | 6 | script: npm test 7 | 8 | notifications: 9 | email: false 10 | 11 | env: 12 | - CI=true 13 | 14 | after_success: 15 | - npm run codecov 16 | 17 | cache: 18 | directories: 19 | - node_modules -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bootstrap-calendar", 3 | "version": "1.0.0", 4 | "homepage": "https://github.com/mattlewis92/angular-bootstrap-calendar", 5 | "authors": [ 6 | "Matt Lewis" 7 | ], 8 | "description": "A pure AngularJS bootstrap themed responsive calendar that can display events and has views for year, month, week and day", 9 | "main": [ 10 | "dist/js/angular-bootstrap-calendar-tpls.js", 11 | "dist/css/angular-bootstrap-calendar.css" 12 | ], 13 | "keywords": [ 14 | "AngularJS", 15 | "Angular", 16 | "Bootstrap", 17 | "Responsive", 18 | "Calendar" 19 | ], 20 | "license": "MIT", 21 | "ignore": [ 22 | "**/.*", 23 | "node_modules", 24 | "test", 25 | "src/**/*.js", 26 | ".github", 27 | "docs", 28 | "index.html", 29 | "karma.conf.js", 30 | "webpack.config.build.js", 31 | "webpack.config.js" 32 | ], 33 | "dependencies": { 34 | "angular": ">=1.3.0", 35 | "moment": "^2.10.2", 36 | "bootstrap": "^3.3.4" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /docs/docs.css: -------------------------------------------------------------------------------- 1 | .content { padding-top: 70px; } 2 | 3 | /* Everything but the jumbotron gets side spacing for mobile first views */ 4 | .header, 5 | .marketing, 6 | .footer { 7 | padding-left: 15px; 8 | padding-right: 15px; 9 | } 10 | 11 | /* Custom page header */ 12 | .header { 13 | border-bottom: 1px solid #e5e5e5; 14 | } 15 | /* Make the masthead heading the same height as the navigation */ 16 | .header h3 { 17 | margin-top: 0; 18 | margin-bottom: 0; 19 | line-height: 40px; 20 | padding-bottom: 19px; 21 | } 22 | 23 | h3 { 24 | margin-top: 0; 25 | margin-bottom: 20px; 26 | } 27 | 28 | .tab-pane { 29 | margin-top: 10px; 30 | } 31 | 32 | .sidebar-nav li { 33 | margin-bottom: 5px; 34 | } 35 | 36 | .sidebar-nav a:not(.active) { 37 | color: #777; 38 | } 39 | 40 | /* Custom page footer */ 41 | .footer { 42 | padding-top: 19px; 43 | color: #777; 44 | border-top: 1px solid #e5e5e5; 45 | } 46 | 47 | /* Customize container */ 48 | @media (min-width: 768px) { 49 | .container { 50 | max-width: 730px; 51 | } 52 | } 53 | .container-narrow > hr { 54 | margin: 30px 0; 55 | } 56 | 57 | /* Main marketing message and sign up button */ 58 | .jumbotron { 59 | text-align: center; 60 | border-bottom: 1px solid #e5e5e5; 61 | } 62 | .jumbotron .btn { 63 | font-size: 21px; 64 | padding: 14px 24px; 65 | } 66 | 67 | /* Supporting marketing content */ 68 | .marketing { 69 | margin: 40px 0; 70 | } 71 | .marketing p + h4 { 72 | margin-top: 28px; 73 | } 74 | 75 | /* Responsive: Portrait tablets and up */ 76 | @media screen and (min-width: 768px) { 77 | /* Remove the padding we set earlier */ 78 | .header, 79 | .marketing, 80 | .footer { 81 | padding-left: 0; 82 | padding-right: 0; 83 | } 84 | /* Space out the masthead */ 85 | .header { 86 | margin-bottom: 30px; 87 | } 88 | /* Remove the bottom border on the jumbotron for visual effect */ 89 | .jumbotron { 90 | border-bottom: 0; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /docs/docs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | angular 4 | .module('mwl.calendar.docs', ['mwl.calendar', 'ui.bootstrap', 'ngTouch', 'ngAnimate', 'oc.lazyLoad', 'hljs', 'colorpicker.module']) 5 | .controller('ExamplesCtrl', function($http, $rootScope, $compile, $q, $location, $ocLazyLoad, plunkGenerator, moment) { 6 | 7 | var vm = this; 8 | 9 | function loadFile(path) { 10 | return $http.get(path, { 11 | transformResponse: function(data) { 12 | return data; 13 | } 14 | }); 15 | } 16 | 17 | var helpers = { 18 | templates: [ 19 | 'modalContent.html', 20 | 'calendarControls.html' 21 | ] 22 | }; 23 | 24 | loadFile('docs/examples/helpers.js').then(function(result) { 25 | helpers.scripts = result.data; 26 | }); 27 | 28 | var previousScope; 29 | 30 | vm.loadExample = function(demo) { 31 | vm.activeExample = angular.copy(demo); 32 | vm.showDemoTab = true; 33 | $location.search('example', demo.key); 34 | var scriptPath = 'docs/examples/' + demo.key + '/javascript.js'; 35 | var markupPath = 'docs/examples/' + demo.key + '/markup.html'; 36 | 37 | loadFile(scriptPath).then(function(result) { 38 | vm.activeExample.javascript = result.data; 39 | }); 40 | 41 | $q.all({ 42 | markup: loadFile(markupPath), 43 | script: $ocLazyLoad.load(scriptPath) 44 | }).then(function(result) { 45 | vm.activeExample.markup = result.markup.data; 46 | var demoContainer = angular.element(document.getElementById('demoContainer')); 47 | demoContainer.html(vm.activeExample.markup); 48 | var scope = $rootScope.$new(); 49 | $compile(demoContainer)(scope); 50 | if (previousScope) { 51 | previousScope.$destroy(); 52 | } 53 | previousScope = scope; 54 | }); 55 | 56 | }; 57 | 58 | vm.editActiveExample = function() { 59 | plunkGenerator(angular.version.full, '3', '2', moment.version, helpers, vm.activeExample); 60 | }; 61 | 62 | $http.get('docs/examples/examples.json').then(function(result) { 63 | vm.examples = result.data; 64 | if ($location.search().example) { 65 | var exampleToLoad = vm.examples.filter(function(example) { 66 | return example.key === $location.search().example; 67 | })[0]; 68 | vm.loadExample(exampleToLoad); 69 | } else { 70 | vm.loadExample(vm.examples[0]); 71 | } 72 | }); 73 | 74 | }) 75 | .factory('plunkGenerator', function($templateCache, $window) { 76 | 77 | return function(ngVersion, bsVersion, uibVersion, momentVersion, helpers, content) { 78 | 79 | var scriptContent = function(content) { 80 | return "angular.module('mwl.calendar.docs', ['mwl.calendar', 'ngAnimate', 'ui.bootstrap', 'colorpicker.module']);" + "\n" + content; 81 | }; 82 | 83 | $window.createPlunker.Plunker.create() 84 | .setDescription('http://mattlewis92.github.io/angular-bootstrap-calendar/') 85 | .addIndexHtmlAttribute('ng-app', 'mwl.calendar.docs') 86 | .addNpmPackage('moment', {version: momentVersion}) 87 | .addNpmPackage('interactjs', {version: 1}) 88 | .addNpmPackage('angular', {version: ngVersion, filename: 'angular.js'}) 89 | .addNpmPackage('angular-animate', {version: ngVersion, filename: 'angular-animate.js'}) 90 | .addNpmPackage('angular-ui-bootstrap', {version: uibVersion, filename: 'dist/ui-bootstrap-tpls.js'}) 91 | .addNpmPackage('rrule', {version: 2}) 92 | .addNpmPackage('angular-bootstrap-colorpicker', {version: 3}) 93 | .addNpmPackage('angular-bootstrap-calendar') 94 | .addNpmPackage('bootstrap', {filename: 'dist/css/bootstrap.css', version: bsVersion}) 95 | .addNpmPackage('angular-bootstrap-colorpicker', {version: 3, filename: 'css/colorpicker.min.css'}) 96 | .addNpmPackage('angular-bootstrap-calendar', {filename: 'dist/css/angular-bootstrap-calendar.min.css'}) 97 | .addFile({name: 'example.js', contents: scriptContent(content.javascript)}) 98 | .addFile({name: 'helpers.js', contents: helpers.scripts}) 99 | .setIndexBody(content.markup) 100 | .addFiles(helpers.templates.map(function(templateName) { 101 | return {name: templateName, contents: $templateCache.get(templateName)}; 102 | })) 103 | .save(); 104 | }; 105 | }) 106 | .config(function($touchProvider) { 107 | $touchProvider.ngClickOverrideEnabled(true); 108 | }); 109 | -------------------------------------------------------------------------------- /docs/examples/all-day-events/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('AllDayEventsCtrl', function(moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'An all day event', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('week').subtract(2, 'days').add(8, 'hours').toDate(), 12 | endsAt: moment().startOf('week').add(1, 'week').add(9, 'hours').toDate(), 13 | allDay: true 14 | }, { 15 | title: 'A non all day event', 16 | color: calendarConfig.colorTypes.important, 17 | startsAt: moment().startOf('day').add(7, 'hours').toDate(), 18 | endsAt: moment().startOf('day').add(19, 'hours').toDate(), 19 | draggable: true, 20 | resizable: true 21 | } 22 | ]; 23 | 24 | vm.calendarView = 'day'; 25 | vm.viewDate = moment().toDate(); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /docs/examples/all-day-events/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 6 | 7 |
8 | -------------------------------------------------------------------------------- /docs/examples/badge-total/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('BadgeTotalCtrl', function(moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'Increments the badge total on the day cell', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('month').toDate(), 12 | incrementsBadgeTotal: true 13 | }, 14 | { 15 | title: 'Does not increment the badge total ', 16 | color: calendarConfig.colorTypes.info, 17 | startsAt: moment().startOf('month').toDate(), 18 | incrementsBadgeTotal: false 19 | } 20 | ]; 21 | 22 | vm.calendarView = 'month'; 23 | vm.viewDate = moment().startOf('month').toDate(); 24 | vm.cellIsOpen = true; 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /docs/examples/badge-total/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /docs/examples/cell-is-open/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('CellIsOpenCtrl', function(moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'Draggable event', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('month').toDate() 12 | }, 13 | { 14 | title: 'Non-draggable event', 15 | color: calendarConfig.colorTypes.info, 16 | startsAt: moment().startOf('month').add(1, 'day').toDate() 17 | } 18 | ]; 19 | 20 | vm.calendarView = 'month'; 21 | vm.viewDate = moment().startOf('month').toDate(); 22 | vm.cellIsOpen = true; 23 | 24 | vm.timespanClicked = function(date, cell) { 25 | 26 | if (vm.calendarView === 'month') { 27 | if ((vm.cellIsOpen && moment(date).startOf('day').isSame(moment(vm.viewDate).startOf('day'))) || cell.events.length === 0 || !cell.inMonth) { 28 | vm.cellIsOpen = false; 29 | } else { 30 | vm.cellIsOpen = true; 31 | vm.viewDate = date; 32 | } 33 | } else if (vm.calendarView === 'year') { 34 | if ((vm.cellIsOpen && moment(date).startOf('month').isSame(moment(vm.viewDate).startOf('month'))) || cell.events.length === 0) { 35 | vm.cellIsOpen = false; 36 | } else { 37 | vm.cellIsOpen = true; 38 | vm.viewDate = date; 39 | } 40 | } 41 | 42 | }; 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /docs/examples/cell-is-open/markup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | The slidebox is openclosed. 4 |
5 | 6 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /docs/examples/cell-modifier/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('CellModifierCtrl', function(moment) { 4 | 5 | var vm = this; 6 | 7 | vm.events = []; 8 | vm.calendarView = 'month'; 9 | vm.viewDate = moment().startOf('month').toDate(); 10 | 11 | vm.cellModifier = function(cell) { 12 | console.log(cell); 13 | if (cell.label % 2 === 1 && cell.inMonth) { 14 | cell.cssClass = 'odd-cell'; 15 | } 16 | cell.label = '-' + cell.label + '-'; 17 | }; 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /docs/examples/cell-modifier/markup.html: -------------------------------------------------------------------------------- 1 | 6 | 7 |
8 | 9 | 14 | 15 |
16 | -------------------------------------------------------------------------------- /docs/examples/clickable-events/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('ClickableEventsCtrl', function(moment, alert, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'Click me', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('month').toDate() 12 | }, 13 | { 14 | title: 'Or click me', 15 | color: calendarConfig.colorTypes.info, 16 | startsAt: moment().startOf('month').toDate() 17 | } 18 | ]; 19 | 20 | vm.calendarView = 'month'; 21 | vm.viewDate = moment().startOf('month').toDate(); 22 | vm.cellIsOpen = true; 23 | 24 | vm.eventClicked = function(event) { 25 | alert.show('Clicked', event); 26 | }; 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /docs/examples/clickable-events/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 10 | 11 |
12 | -------------------------------------------------------------------------------- /docs/examples/custom-event-class/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('CustomEventClassCtrl', function(moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'Has custom class', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('month').toDate(), 12 | cssClass: 'my-custom-class' 13 | } 14 | ]; 15 | 16 | vm.calendarView = 'month'; 17 | vm.viewDate = moment().startOf('month').toDate(); 18 | vm.cellIsOpen = true; 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /docs/examples/custom-event-class/markup.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 11 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /docs/examples/custom-templates/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('CustomTemplatesCtrl', function($scope, moment) { 4 | 5 | var vm = this; 6 | vm.events = []; 7 | vm.calendarView = 'month'; 8 | vm.viewDate = moment().startOf('month').toDate(); 9 | vm.cellModifier = function(cell) { 10 | cell.cssClass = 'custom-template-cell'; 11 | }; 12 | }); 13 | -------------------------------------------------------------------------------- /docs/examples/custom-templates/markup.html: -------------------------------------------------------------------------------- 1 | 17 | 18 | 23 | 24 |
25 | 26 | 32 | 33 |
34 | -------------------------------------------------------------------------------- /docs/examples/day-view-all-times/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('DayViewAllTimesCtrl', function($scope, moment, calendarConfig) { 4 | 5 | var vm = this; 6 | vm.events = []; 7 | vm.calendarView = 'day'; 8 | vm.viewDate = moment().startOf('month').toDate(); 9 | var originalFormat = calendarConfig.dateFormats.hour; 10 | calendarConfig.dateFormats.hour = 'HH:mm'; 11 | 12 | $scope.$on('$destroy', function() { 13 | calendarConfig.dateFormats.hour = originalFormat; // reset for other demos 14 | }); 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /docs/examples/day-view-all-times/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 7 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /docs/examples/day-view-segment-size/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('DayViewStartEndCtrl', function(moment) { 4 | 5 | var vm = this; 6 | vm.events = []; 7 | vm.calendarView = 'day'; 8 | vm.viewDate = moment().startOf('month').toDate(); 9 | 10 | // note that this class is required to set the hour part height in the css 11 | vm.cellModifier = function(cell) { 12 | cell.cssClass = 'my-custom-class'; 13 | } 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /docs/examples/day-view-segment-size/markup.html: -------------------------------------------------------------------------------- 1 | 8 | 9 |
10 | 18 | 19 |
20 | -------------------------------------------------------------------------------- /docs/examples/day-view-split/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('DayViewSplitCtrl', function(moment) { 4 | 5 | var vm = this; 6 | vm.events = []; 7 | vm.calendarView = 'day'; 8 | vm.viewDate = moment().startOf('month').toDate(); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /docs/examples/day-view-split/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /docs/examples/day-view-start-end/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('DayViewStartEndCtrl', function(moment) { 4 | 5 | var vm = this; 6 | vm.events = []; 7 | vm.calendarView = 'day'; 8 | vm.viewDate = moment().startOf('month').toDate(); 9 | 10 | }); 11 | -------------------------------------------------------------------------------- /docs/examples/day-view-start-end/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /docs/examples/disable-tooltips/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('DisableTooltipsCtrl', function($scope, moment, calendarConfig, calendarEventTitle) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [{ 8 | title: 'An event', 9 | color: calendarConfig.colorTypes.warning, 10 | startsAt: moment().startOf('week').subtract(2, 'days').add(8, 'hours').toDate(), 11 | endsAt: moment().startOf('week').add(1, 'week').add(9, 'hours').toDate(), 12 | }]; 13 | vm.calendarView = 'month'; 14 | vm.viewDate = moment().startOf('month').toDate(); 15 | 16 | var originalEventTitle = angular.copy(calendarEventTitle); 17 | 18 | calendarEventTitle.monthViewTooltip = calendarEventTitle.weekViewTooltip = calendarEventTitle.dayViewTooltip = function() { 19 | return ''; 20 | }; 21 | 22 | // required so other demos work as before 23 | $scope.$on('$destroy', function() { 24 | angular.extend(calendarEventTitle, originalEventTitle); 25 | }); 26 | 27 | }); 28 | -------------------------------------------------------------------------------- /docs/examples/disable-tooltips/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ vm.viewTitle }}

3 | 4 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /docs/examples/disabling-views/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('DisableViewsCtrl', function(moment) { 4 | 5 | var vm = this; 6 | 7 | vm.events = []; 8 | vm.calendarView = 'year'; 9 | vm.viewDate = moment().startOf('month').toDate(); 10 | 11 | vm.viewChangeClicked = function(nextView) { 12 | if (nextView === 'month') { 13 | return false; 14 | } 15 | }; 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /docs/examples/disabling-views/markup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 | 6 | 7 |
8 |
9 |
10 | 15 | 16 |
17 | -------------------------------------------------------------------------------- /docs/examples/draggable-events/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('DraggableEventsCtrl', function(moment, alert, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'Draggable event', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('month').toDate(), 12 | draggable: true 13 | }, 14 | { 15 | title: 'Non-draggable event', 16 | color: calendarConfig.colorTypes.info, 17 | startsAt: moment().startOf('month').toDate(), 18 | draggable: false 19 | } 20 | ]; 21 | 22 | vm.calendarView = 'month'; 23 | vm.viewDate = moment().startOf('month').toDate(); 24 | vm.cellIsOpen = true; 25 | 26 | vm.eventTimesChanged = function(event) { 27 | vm.viewDate = event.startsAt; 28 | alert.show('Dragged and dropped', event); 29 | }; 30 | 31 | vm.timespanClicked = function(date, cell) { 32 | 33 | if (vm.calendarView === 'month') { 34 | if ((vm.cellIsOpen && moment(date).startOf('day').isSame(moment(vm.viewDate).startOf('day'))) || cell.events.length === 0 || !cell.inMonth) { 35 | vm.cellIsOpen = false; 36 | } else { 37 | vm.cellIsOpen = true; 38 | vm.viewDate = date; 39 | } 40 | } else if (vm.calendarView === 'year') { 41 | if ((vm.cellIsOpen && moment(date).startOf('month').isSame(moment(vm.viewDate).startOf('month'))) || cell.events.length === 0) { 42 | vm.cellIsOpen = false; 43 | } else { 44 | vm.cellIsOpen = true; 45 | vm.viewDate = date; 46 | } 47 | } 48 | 49 | }; 50 | 51 | }); 52 | -------------------------------------------------------------------------------- /docs/examples/draggable-events/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 11 | 12 |
13 | -------------------------------------------------------------------------------- /docs/examples/draggable-external-events/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('DraggableExternalEventsCtrl', function(moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = []; 8 | 9 | vm.externalEvents = [ 10 | { 11 | title: 'Event 1', 12 | type: 'warning', 13 | color: calendarConfig.colorTypes.warning, 14 | startsAt: moment().startOf('month').toDate(), 15 | draggable: true 16 | }, 17 | { 18 | title: 'Event 2', 19 | type: 'danger', 20 | color: calendarConfig.colorTypes.important, 21 | startsAt: moment().startOf('month').toDate(), 22 | draggable: true 23 | } 24 | ]; 25 | 26 | vm.calendarView = 'month'; 27 | vm.viewDate = moment().startOf('month').toDate(); 28 | vm.cellIsOpen = false; 29 | 30 | vm.eventDropped = function(event, start, end) { 31 | var externalIndex = vm.externalEvents.indexOf(event); 32 | if (externalIndex > -1) { 33 | vm.externalEvents.splice(externalIndex, 1); 34 | vm.events.push(event); 35 | } 36 | event.startsAt = start; 37 | if (end) { 38 | event.endsAt = end; 39 | } 40 | vm.viewDate = start; 41 | vm.cellIsOpen = true; 42 | }; 43 | 44 | }); 45 | -------------------------------------------------------------------------------- /docs/examples/draggable-external-events/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
5 |
6 | 18 |
19 |
20 |
21 | 22 | 29 | 30 |
31 |
32 |
33 | -------------------------------------------------------------------------------- /docs/examples/editable-deletable-events/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('EditableDeletableEventsCtrl', function(moment, alert, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'Editable event', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('month').toDate(), 12 | actions: [{ 13 | label: '', 14 | onClick: function(args) { 15 | alert.show('Edited', args.calendarEvent); 16 | } 17 | }] 18 | }, { 19 | title: 'Deletable event', 20 | color: calendarConfig.colorTypes.info, 21 | startsAt: moment().startOf('month').toDate(), 22 | actions: [{ 23 | label: '', 24 | onClick: function(args) { 25 | alert.show('Deleted', args.calendarEvent); 26 | } 27 | }] 28 | }, { 29 | title: 'Non editable and deletable event', 30 | color: calendarConfig.colorTypes.important, 31 | startsAt: moment().startOf('month').toDate() 32 | } 33 | ]; 34 | 35 | vm.calendarView = 'month'; 36 | vm.viewDate = moment().startOf('month').toDate(); 37 | vm.cellIsOpen = true; 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /docs/examples/editable-deletable-events/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 9 | 10 |
11 | -------------------------------------------------------------------------------- /docs/examples/examples.json: -------------------------------------------------------------------------------- 1 | [{ 2 | "key": "kitchen-sink", 3 | "label": "Kitchen sink" 4 | }, { 5 | "key": "optional-event-end-dates", 6 | "label": "Optional event end dates" 7 | }, { 8 | "key": "editable-deletable-events", 9 | "label": "Editable / deletable events" 10 | }, { 11 | "key": "draggable-events", 12 | "label": "Draggable events" 13 | }, { 14 | "key": "resizable-events", 15 | "label": "Resizable events" 16 | }, { 17 | "key": "badge-total", 18 | "label": "Badge total" 19 | }, { 20 | "key": "recurring-events", 21 | "label": "Recurring events" 22 | }, { 23 | "key": "custom-event-class", 24 | "label": "Custom event class" 25 | }, { 26 | "key": "clickable-events", 27 | "label": "Clickable events" 28 | }, { 29 | "key": "timespan-click", 30 | "label": "Timespan click" 31 | }, { 32 | "key": "select-range", 33 | "label": "Select range" 34 | }, { 35 | "key": "cell-is-open", 36 | "label": "Slidebox is open" 37 | }, { 38 | "key": "day-view-start-end", 39 | "label": "Day view start / end time" 40 | }, { 41 | "key": "exclude-weekdays", 42 | "label": "Exclude days" 43 | }, { 44 | "key": "day-view-split", 45 | "label": "Day view minute split" 46 | }, { 47 | "key": "view-change-click", 48 | "label": "Navigating between views" 49 | }, { 50 | "key": "cell-modifier", 51 | "label": "Cell modifier" 52 | }, { 53 | "key": "custom-templates", 54 | "label": "Custom templates" 55 | }, { 56 | "key": "i18n", 57 | "label": "Internationalization" 58 | }, { 59 | "key": "disabling-views", 60 | "label": "Disabling views" 61 | }, { 62 | "key": "draggable-external-events", 63 | "label": "Draggable external events" 64 | }, { 65 | "key": "slide-box-disabled", 66 | "label": "Disable the slidebox" 67 | }, { 68 | "key": "grouping-events", 69 | "label": "Group month view events" 70 | }, { 71 | "key": "all-day-events", 72 | "label": "All day events" 73 | }, { 74 | "key": "day-view-all-times", 75 | "label": "Show all times on day view" 76 | }, { 77 | "key": "side-time-position", 78 | "label": "Show time on the side of calendar" 79 | }, { 80 | "key": "disable-tooltips", 81 | "label": "Disable tooltips" 82 | }, { 83 | "key": "day-view-segment-size", 84 | "label": "Custom hour segment size in day view" 85 | }] 86 | -------------------------------------------------------------------------------- /docs/examples/exclude-weekdays/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('ExcludeWeekdays', function(moment, calendarConfig) { 4 | 5 | var vm = this; 6 | vm.events = [ 7 | { 8 | title: 'An event', 9 | color: calendarConfig.colorTypes.warning, 10 | startsAt: moment().startOf('week').subtract(2, 'days').add(8, 'hours').toDate(), 11 | endsAt: moment().startOf('week').add(1, 'week').add(9, 'hours').toDate() 12 | }, { 13 | title: ' Another event, with a html title', 14 | color: calendarConfig.colorTypes.info, 15 | startsAt: moment().subtract(1, 'day').toDate(), 16 | endsAt: moment().add(5, 'days').toDate() 17 | }, { 18 | title: 'This is a really long event title that occurs on every year', 19 | color: calendarConfig.colorTypes.important, 20 | startsAt: moment().startOf('day').add(7, 'hours').toDate(), 21 | endsAt: moment().startOf('day').add(19, 'hours').toDate() 22 | } 23 | ]; 24 | vm.calendarView = 'week'; 25 | vm.viewDate = new Date(); 26 | vm.excludedDays = [0, 6]; 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /docs/examples/exclude-weekdays/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ vm.calendarTitle }}

3 | 4 |
5 |
6 |
7 | 8 | 17 | 25 | 34 |
35 |
36 | 37 |
38 | 39 |
40 |
41 | 42 | 43 | 44 |
45 |
46 |
47 | 48 |
49 | 50 | 56 | 57 |
58 | -------------------------------------------------------------------------------- /docs/examples/grouping-events/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('GroupingEventsCtrl', function($scope, moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | calendarConfig.templates.calendarMonthCell = 'groupedMonthEvents.html'; 8 | 9 | $scope.$on('$destroy', function() { 10 | calendarConfig.templates.calendarMonthCell = 'mwl/calendarMonthCell.html'; 11 | }); 12 | 13 | vm.events = [ 14 | { 15 | title: 'Event 1', 16 | type: 'warning', 17 | color: calendarConfig.colorTypes.warning, 18 | startsAt: moment().startOf('month').toDate() 19 | }, { 20 | title: 'Event 2', 21 | type: 'info', 22 | color: calendarConfig.colorTypes.info, 23 | startsAt: moment().startOf('month').toDate() 24 | }, { 25 | title: 'Event 3', 26 | type: 'info', 27 | color: calendarConfig.colorTypes.info, 28 | startsAt: moment().startOf('month').toDate() 29 | }, { 30 | title: 'Event 4', 31 | type: 'danger', 32 | color: calendarConfig.colorTypes.important, 33 | startsAt: moment().startOf('month').toDate() 34 | }, { 35 | title: 'Event 5', 36 | type: 'success', 37 | color: calendarConfig.colorTypes.success, 38 | startsAt: moment().startOf('month').toDate() 39 | } 40 | ]; 41 | 42 | vm.calendarView = 'month'; 43 | vm.viewDate = moment().startOf('month').toDate(); 44 | vm.cellIsOpen = true; 45 | 46 | vm.groupEvents = function(cell) { 47 | cell.groups = {}; 48 | cell.events.forEach(function(event) { 49 | cell.groups[event.type] = cell.groups[event.type] || []; 50 | cell.groups[event.type].push(event); 51 | }); 52 | }; 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /docs/examples/grouping-events/markup.html: -------------------------------------------------------------------------------- 1 | 37 |
38 | 39 | 46 | 47 |
48 | -------------------------------------------------------------------------------- /docs/examples/helpers.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .factory('alert', function($uibModal) { 4 | 5 | function show(action, event) { 6 | return $uibModal.open({ 7 | templateUrl: 'modalContent.html', 8 | controller: function() { 9 | var vm = this; 10 | vm.action = action; 11 | vm.event = event; 12 | }, 13 | controllerAs: 'vm' 14 | }); 15 | } 16 | 17 | return { 18 | show: show 19 | }; 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /docs/examples/i18n/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('i18nCtrl', function($scope, $window, $ocLazyLoad, calendarConfig, moment) { 4 | 5 | var vm = this; 6 | 7 | vm.events = []; 8 | vm.calendarView = 'month'; 9 | vm.viewDate = moment().startOf('month').toDate(); 10 | 11 | calendarConfig.dateFormatter = 'moment'; // use moment instead of angular for formatting dates 12 | var originali18n = angular.copy(calendarConfig.i18nStrings); 13 | calendarConfig.i18nStrings.weekNumber = 'Semaine {week}'; 14 | 15 | $window.moment = $window.moment || moment; 16 | $ocLazyLoad.load('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.6/locale/fr.js').then(function() { 17 | moment.locale('fr', { 18 | week: { 19 | dow: 1 // Monday is the first day of the week 20 | } 21 | }); 22 | moment.locale('fr'); // change the locale to french 23 | }); 24 | 25 | $scope.$on('$destroy', function() { 26 | moment.locale('en'); 27 | calendarConfig.i18nStrings = originali18n; 28 | }); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /docs/examples/i18n/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /docs/examples/kitchen-sink/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') //you will need to declare your module with the dependencies ['mwl.calendar', 'ui.bootstrap', 'ngAnimate'] 3 | .controller('KitchenSinkCtrl', function(moment, alert, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | //These variables MUST be set as a minimum for the calendar to work 8 | vm.calendarView = 'month'; 9 | vm.viewDate = new Date(); 10 | var actions = [{ 11 | label: '', 12 | onClick: function(args) { 13 | alert.show('Edited', args.calendarEvent); 14 | } 15 | }, { 16 | label: '', 17 | onClick: function(args) { 18 | alert.show('Deleted', args.calendarEvent); 19 | } 20 | }]; 21 | vm.events = [ 22 | { 23 | title: 'An event', 24 | color: calendarConfig.colorTypes.warning, 25 | startsAt: moment().startOf('week').subtract(2, 'days').add(8, 'hours').toDate(), 26 | endsAt: moment().startOf('week').add(1, 'week').add(9, 'hours').toDate(), 27 | draggable: true, 28 | resizable: true, 29 | actions: actions 30 | }, { 31 | title: ' Another event, with a html title', 32 | color: calendarConfig.colorTypes.info, 33 | startsAt: moment().subtract(1, 'day').toDate(), 34 | endsAt: moment().add(5, 'days').toDate(), 35 | draggable: true, 36 | resizable: true, 37 | actions: actions 38 | }, { 39 | title: 'This is a really long event title that occurs on every year', 40 | color: calendarConfig.colorTypes.important, 41 | startsAt: moment().startOf('day').add(7, 'hours').toDate(), 42 | endsAt: moment().startOf('day').add(19, 'hours').toDate(), 43 | recursOn: 'year', 44 | draggable: true, 45 | resizable: true, 46 | actions: actions 47 | } 48 | ]; 49 | 50 | vm.cellIsOpen = true; 51 | 52 | vm.addEvent = function() { 53 | vm.events.push({ 54 | title: 'New event', 55 | startsAt: moment().startOf('day').toDate(), 56 | endsAt: moment().endOf('day').toDate(), 57 | color: calendarConfig.colorTypes.important, 58 | draggable: true, 59 | resizable: true 60 | }); 61 | }; 62 | 63 | vm.eventClicked = function(event) { 64 | alert.show('Clicked', event); 65 | }; 66 | 67 | vm.eventEdited = function(event) { 68 | alert.show('Edited', event); 69 | }; 70 | 71 | vm.eventDeleted = function(event) { 72 | alert.show('Deleted', event); 73 | }; 74 | 75 | vm.eventTimesChanged = function(event) { 76 | alert.show('Dropped or resized', event); 77 | }; 78 | 79 | vm.toggle = function($event, field, event) { 80 | $event.preventDefault(); 81 | $event.stopPropagation(); 82 | event[field] = !event[field]; 83 | }; 84 | 85 | vm.timespanClicked = function(date, cell) { 86 | 87 | if (vm.calendarView === 'month') { 88 | if ((vm.cellIsOpen && moment(date).startOf('day').isSame(moment(vm.viewDate).startOf('day'))) || cell.events.length === 0 || !cell.inMonth) { 89 | vm.cellIsOpen = false; 90 | } else { 91 | vm.cellIsOpen = true; 92 | vm.viewDate = date; 93 | } 94 | } else if (vm.calendarView === 'year') { 95 | if ((vm.cellIsOpen && moment(date).startOf('month').isSame(moment(vm.viewDate).startOf('month'))) || cell.events.length === 0) { 96 | vm.cellIsOpen = false; 97 | } else { 98 | vm.cellIsOpen = true; 99 | vm.viewDate = date; 100 | } 101 | } 102 | 103 | }; 104 | 105 | }); 106 | -------------------------------------------------------------------------------- /docs/examples/kitchen-sink/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ vm.calendarTitle }}

3 | 4 |
5 | 6 |
7 |
8 | 9 | 17 | 25 | 33 |
34 |
35 | 36 |
37 | 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | 47 |
48 | 49 |
50 | 51 | 65 | 66 | 67 |


68 | 69 |

70 | Edit events 71 | 76 |
77 |

78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 100 | 103 | 106 | 133 | 160 | 167 | 168 | 169 | 170 |
TitlePrimary colorSecondary colorStarts atEnds atRemove
95 | 99 | 101 | 102 | 104 | 105 | 107 |

108 | 116 | 117 | 123 | 124 |

125 |
131 |
132 |
134 |

135 | 143 | 144 | 150 | 151 |

152 |
158 |
159 |
161 | 166 |
171 |
172 | -------------------------------------------------------------------------------- /docs/examples/optional-event-end-dates/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('OptionalEventEndDatesCtrl', function(moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [{ 8 | title: 'No event end date', 9 | startsAt: moment().hours(3).minutes(0).toDate(), 10 | color: calendarConfig.colorTypes.info 11 | }, { 12 | title: 'No event end date', 13 | startsAt: moment().hours(5).minutes(0).toDate(), 14 | color: calendarConfig.colorTypes.warning 15 | }]; 16 | 17 | vm.calendarView = 'day'; 18 | vm.viewDate = new Date(); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /docs/examples/optional-event-end-dates/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 7 | 8 |
9 | -------------------------------------------------------------------------------- /docs/examples/recurring-events/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('RecurringEventsCtrl', function($scope, moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = []; 8 | vm.calendarView = 'month'; 9 | vm.viewDate = moment().toDate(); 10 | vm.cellIsOpen = true; 11 | 12 | var events = [{ 13 | title: 'Recurs on the 5th of each month', 14 | color: calendarConfig.colorTypes.warning, 15 | rrule: { 16 | freq: RRule.MONTHLY, 17 | bymonthday: 5 18 | } 19 | }, { 20 | title: 'Recurs yearly on the 10th of the current month', 21 | color: calendarConfig.colorTypes.info, 22 | rrule: { 23 | freq: RRule.YEARLY, 24 | bymonth: moment().month() + 1, 25 | bymonthday: 10 26 | } 27 | }, { 28 | title: 'Recurs weekly on mondays', 29 | color: calendarConfig.colorTypes.success, 30 | rrule: { 31 | freq: RRule.WEEKLY, 32 | byweekday: [RRule.MO], 33 | } 34 | }]; 35 | 36 | $scope.$watchGroup([ 37 | 'vm.calendarView', 38 | 'vm.viewDate' 39 | ], function() { 40 | 41 | vm.events = []; 42 | 43 | events.forEach(function(event) { 44 | 45 | // Use the rrule library to generate recurring events: https://github.com/jkbrzt/rrule 46 | var rule = new RRule(angular.extend({}, event.rrule, { 47 | dtstart: moment(vm.viewDate).startOf(vm.calendarView).toDate(), 48 | until: moment(vm.viewDate).endOf(vm.calendarView).toDate() 49 | })); 50 | 51 | rule.all().forEach(function(date) { 52 | vm.events.push(angular.extend({}, event, { 53 | startsAt: new Date(date) 54 | })); 55 | }); 56 | 57 | }); 58 | 59 | }); 60 | 61 | vm.timespanClicked = function(date, cell) { 62 | 63 | if (vm.calendarView === 'month') { 64 | if ((vm.cellIsOpen && moment(date).startOf('day').isSame(moment(vm.viewDate).startOf('day'))) || cell.events.length === 0 || !cell.inMonth) { 65 | vm.cellIsOpen = false; 66 | } else { 67 | vm.cellIsOpen = true; 68 | vm.viewDate = date; 69 | } 70 | } else if (vm.calendarView === 'year') { 71 | if ((vm.cellIsOpen && moment(date).startOf('month').isSame(moment(vm.viewDate).startOf('month'))) || cell.events.length === 0) { 72 | vm.cellIsOpen = false; 73 | } else { 74 | vm.cellIsOpen = true; 75 | vm.viewDate = date; 76 | } 77 | } 78 | 79 | }; 80 | 81 | }); 82 | -------------------------------------------------------------------------------- /docs/examples/recurring-events/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ vm.calendarTitle }}

3 | 4 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /docs/examples/resizable-events/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('ResizableEventsCtrl', function(moment, alert, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'Resizable event', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('month').toDate(), 12 | endsAt: moment().startOf('month').add(1, 'hour').toDate(), //ends at is required 13 | resizable: true 14 | }, 15 | { 16 | title: 'Non-resizeable event', 17 | color: calendarConfig.colorTypes.info, 18 | startsAt: moment().startOf('month').toDate(), 19 | endsAt: moment().startOf('month').add(1, 'hour').toDate(), //ends at is required 20 | resizable: false 21 | } 22 | ]; 23 | 24 | vm.calendarView = 'week'; 25 | vm.viewDate = moment().startOf('month').toDate(); 26 | 27 | vm.eventTimesChanged = function(event) { 28 | alert.show('Resized', event); 29 | }; 30 | 31 | }); 32 | -------------------------------------------------------------------------------- /docs/examples/resizable-events/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /docs/examples/select-range/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('SelectRangeCtrl', function(moment) { 4 | 5 | var vm = this; 6 | 7 | vm.events = []; 8 | vm.calendarView = 'day'; 9 | vm.viewDate = moment().startOf('month').toDate(); 10 | 11 | vm.rangeSelected = function(startDate, endDate) { 12 | vm.firstDateClicked = startDate; 13 | vm.lastDateClicked = endDate; 14 | }; 15 | 16 | }); 17 | -------------------------------------------------------------------------------- /docs/examples/select-range/markup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Select range on a day on the view. 4 | You selected on this day: from {{ vm.firstDateClicked | date:'medium' }} to {{ vm.lastDateClicked | date:'medium' }} 5 |
6 | 7 | 12 | 13 |
14 | -------------------------------------------------------------------------------- /docs/examples/side-time-position/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('SideTimePositionCtrl', function ($scope, moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | var actions = [{ 8 | label: '' 9 | }, { 10 | label: '' 11 | }]; 12 | 13 | vm.events = [{ 14 | title: 'An event', 15 | startsAt: moment().hours(3).minutes(0).toDate(), 16 | endsAt: moment().hours(7).minutes(0).toDate(), 17 | actions: actions, 18 | color: calendarConfig.colorTypes.warning 19 | }, { 20 | title: 'Another event', 21 | startsAt: moment().hours(5).minutes(0).toDate(), 22 | endsAt: moment().hours(12).minutes(0).toDate(), 23 | actions: actions, 24 | color: calendarConfig.colorTypes.important 25 | }]; 26 | 27 | vm.calendarView = 'day'; 28 | vm.viewDate = new Date(); 29 | 30 | }); 31 | -------------------------------------------------------------------------------- /docs/examples/side-time-position/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |

Side

5 | 10 | 11 |
12 | 13 |
14 |

Default

15 | 19 | 20 |
21 | 22 |
23 |

Hidden

24 | 29 | 30 |
31 | 32 |
33 | -------------------------------------------------------------------------------- /docs/examples/slide-box-disabled/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('SlideBoxDisabledCtrl', function(moment, calendarConfig) { 4 | 5 | var vm = this; 6 | 7 | vm.events = [ 8 | { 9 | title: 'Event 1', 10 | color: calendarConfig.colorTypes.warning, 11 | startsAt: moment().startOf('month').toDate() 12 | }, 13 | { 14 | title: 'Event 2', 15 | color: calendarConfig.colorTypes.info, 16 | startsAt: moment().startOf('month').toDate() 17 | } 18 | ]; 19 | 20 | vm.calendarView = 'month'; 21 | vm.viewDate = moment().startOf('month').toDate(); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /docs/examples/slide-box-disabled/markup.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 8 | 9 |
10 | -------------------------------------------------------------------------------- /docs/examples/timespan-click/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('TimespanClickCtrl', function(moment) { 4 | 5 | var vm = this; 6 | 7 | vm.events = []; 8 | vm.calendarView = 'month'; 9 | vm.viewDate = moment().startOf('month').toDate(); 10 | 11 | vm.timespanClicked = function(date) { 12 | vm.lastDateClicked = date; 13 | }; 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /docs/examples/timespan-click/markup.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Click on a day on the view. 4 | You clicked on this day: {{ vm.lastDateClicked | date:'medium' }} 5 |
6 | 7 | 13 | 14 |
15 | -------------------------------------------------------------------------------- /docs/examples/view-change-click/javascript.js: -------------------------------------------------------------------------------- 1 | angular 2 | .module('mwl.calendar.docs') 3 | .controller('ViewChangeClickCtrl', function(moment) { 4 | 5 | var vm = this; 6 | 7 | vm.events = []; 8 | vm.calendarView = 'year'; 9 | vm.viewDate = moment().startOf('month').toDate(); 10 | vm.viewChangeEnabled = true; 11 | 12 | vm.viewChangeClicked = function(date, nextView) { 13 | console.log(date, nextView); 14 | return vm.viewChangeEnabled; 15 | }; 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /docs/examples/view-change-click/markup.html: -------------------------------------------------------------------------------- 1 |
2 |

{{ vm.viewTitle }}

3 | 4 |
5 |
6 | Click on a month label to change the view to that month. 7 | Click on a day label to change the view to that day. 8 | There is no other view to navigate to. 9 |
10 |
11 |
12 | Enable the view change 13 |
14 | 20 | 21 |
22 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var webpack = require('webpack'); 4 | 5 | module.exports = function(config) { 6 | 7 | var webpackConfig = { 8 | cache: true, 9 | devtool: 'inline-source-map', 10 | module: { 11 | rules: [{ 12 | test: /\.js$/, 13 | loader: 'eslint-loader', 14 | exclude: /node_modules/, 15 | enforce: 'pre' 16 | }, { 17 | test: /\.html$/, 18 | loader: 'htmlhint-loader', 19 | exclude: /node_modules/, 20 | enforce: 'pre' 21 | }, { 22 | test: /\.html$/, 23 | loader: 'html-loader', 24 | exclude: /node_modules/ 25 | }, { 26 | test: /\.less/, 27 | loader: 'null-loader', 28 | exclude: /node_modules/ 29 | }, { 30 | test: /\.js$/, 31 | exclude: /(test|node_modules)/, 32 | loader: 'istanbul-instrumenter-loader', 33 | enforce: 'post' 34 | }] 35 | }, 36 | plugins: [ 37 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 38 | new webpack.DefinePlugin({ 39 | EXCLUDE_TEMPLATES: false 40 | }) 41 | ] 42 | }; 43 | 44 | if (config.singleRun) { 45 | webpackConfig.plugins.push(new webpack.NoEmitOnErrorsPlugin()); 46 | } 47 | 48 | config.set({ 49 | 50 | // base path that will be used to resolve all patterns (eg. files, exclude) 51 | basePath: './', 52 | 53 | // frameworks to use 54 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 55 | frameworks: ['mocha', 'chai', 'chai-as-promised', 'sinon-chai', 'chai-things'], 56 | 57 | // list of files / patterns to load in the browser 58 | files: [ 59 | 'test/unit/entry.js' 60 | ], 61 | 62 | // preprocess matching files before serving them to the browser 63 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 64 | preprocessors: { 65 | 'test/unit/entry.js': ['webpack', 'sourcemap'] 66 | }, 67 | 68 | coverageReporter: { 69 | dir: 'coverage', 70 | reporters: [{ 71 | type: 'text-summary' 72 | }, { 73 | type: 'html', 74 | subdir: 'html' 75 | }, { 76 | type: 'lcovonly', 77 | subdir: '.' 78 | }] 79 | }, 80 | 81 | webpack: webpackConfig, 82 | 83 | // test results reporter to use 84 | // possible values: 'dots', 'progress' 85 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 86 | reporters: ['progress', 'coverage'], 87 | 88 | // web server port 89 | port: 9876, 90 | 91 | // enable / disable colors in the output (reporters and logs) 92 | colors: true, 93 | 94 | // level of logging 95 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 96 | logLevel: config.LOG_INFO, 97 | 98 | // start these browsers 99 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 100 | browsers: ['PhantomJS'] 101 | }); 102 | }; 103 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-bootstrap-calendar", 3 | "description": "A pure AngularJS bootstrap themed responsive calendar that can display events and has views for year, month, week and day", 4 | "version": "1.0.0", 5 | "homepage": "https://github.com/mattlewis92/angular-bootstrap-calendar", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "https://github.com/mattlewis92/angular-bootstrap-calendar.git" 10 | }, 11 | "files": [ 12 | "dist", 13 | "src/less" 14 | ], 15 | "peerDependencies": { 16 | "angular": ">=1.3.0", 17 | "moment": "2.x.x" 18 | }, 19 | "devDependencies": { 20 | "angular": "^1.6.8", 21 | "angular-mocks": "^1.6.8", 22 | "bootstrap": "^3.3.6", 23 | "calendar-utils": "0.0.60", 24 | "codecov-lite": "^0.1.3", 25 | "commitizen": "^2.9.2", 26 | "concurrently": "^3.5.1", 27 | "conventional-changelog": "^1.1.7", 28 | "conventional-changelog-cli": "^1.3.5", 29 | "css-loader": "^0.28.7", 30 | "cz-conventional-changelog": "^2.1.0", 31 | "date-fns": "^1.29.0", 32 | "eslint": "^4.14.0", 33 | "eslint-config-mwl": "^0.5.1", 34 | "eslint-loader": "^1.9.0", 35 | "eslint-plugin-angular": "^3.1.1", 36 | "extract-text-webpack-plugin": "^3.0.2", 37 | "html-loader": "^0.5.1", 38 | "htmlhint-loader": "^1.3.0", 39 | "husky": "^0.14.3", 40 | "istanbul-instrumenter-loader": "^3.0.0", 41 | "karma": "^2.0.0", 42 | "karma-chai-plugins": "^0.9.0", 43 | "karma-coverage": "^1.1.0", 44 | "karma-mocha": "^1.3.0", 45 | "karma-phantomjs-launcher": "^1.0.3", 46 | "karma-sourcemap-loader": "^0.3.5", 47 | "karma-webpack": "^2.0.9", 48 | "less": "^2.7.3", 49 | "less-loader": "^4.0.5", 50 | "mocha": "^5.0.0", 51 | "moment": "^2.20.1", 52 | "ng-annotate-loader": "^0.6.0", 53 | "null-loader": "^0.1.1", 54 | "phantomjs-prebuilt": "^2.1.16", 55 | "style-loader": "^0.20.1", 56 | "validate-commit-msg": "^2.14.0", 57 | "webpack": "^3.10.0", 58 | "webpack-dev-server": "^2.9.7", 59 | "webpack-notifier": "^1.5.0" 60 | }, 61 | "engines": { 62 | "node": ">=4.0.0" 63 | }, 64 | "main": "dist/js/angular-bootstrap-calendar-tpls.js", 65 | "style": "dist/css/angular-bootstrap-calendar.css", 66 | "optionalDependencies": { 67 | "angular-touch": ">=1.3.0", 68 | "angular-ui-bootstrap": ">=0.14.0", 69 | "interactjs": "^1.2.0" 70 | }, 71 | "scripts": { 72 | "test": "karma start --single-run", 73 | "test:watch": "karma start --auto-watch", 74 | "build:unmin": "webpack --config webpack.config.build.js", 75 | "build:min": "webpack -p --config webpack.config.build.js", 76 | "build:unmin:exclude-templates": "webpack --config webpack.config.build.js --env.excludeTemplates", 77 | "build:min:exclude-templates": "webpack -p --config webpack.config.build.js --env.excludeTemplates", 78 | "build": "concurrently --raw \"npm run build:unmin\" \"npm run build:min\" \"npm run build:unmin:exclude-templates\" \"npm run build:min:exclude-templates\"", 79 | "start": "concurrently --raw \"webpack-dev-server --open\" \"npm run test:watch\"", 80 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md -s", 81 | "commit": "git-cz", 82 | "release": "npm test && npm run build && npm run changelog", 83 | "commitmsg": "validate-commit-msg", 84 | "codecov": "cat coverage/lcov.info | codecov" 85 | }, 86 | "config": { 87 | "commitizen": { 88 | "path": "node_modules/cz-conventional-changelog" 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/directives/mwlCalendar.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | var LOG_PREFIX = 'Bootstrap calendar:'; 5 | 6 | angular 7 | .module('mwl.calendar') 8 | .controller('MwlCalendarCtrl', function($scope, $log, $timeout, $attrs, $locale, moment, calendarTitle, calendarHelper) { 9 | 10 | var vm = this; 11 | 12 | vm.changeView = function(view, newDay) { 13 | vm.view = view; 14 | vm.viewDate = newDay; 15 | }; 16 | 17 | vm.dateClicked = function(date) { 18 | 19 | var rawDate = moment(date).toDate(); 20 | 21 | var nextView = { 22 | year: 'month', 23 | month: 'day', 24 | week: 'day' 25 | }; 26 | 27 | if (vm.onViewChangeClick({calendarDate: rawDate, calendarNextView: nextView[vm.view]}) !== false) { 28 | vm.changeView(nextView[vm.view], rawDate); 29 | } 30 | 31 | }; 32 | 33 | vm.$onInit = function() { 34 | 35 | if (vm.slideBoxDisabled) { 36 | $log.warn(LOG_PREFIX, 'The `slide-box-disabled` option is deprecated and will be removed in the next release. ' + 37 | 'Instead set `cell-auto-open-disabled` to true'); 38 | } 39 | 40 | vm.events = vm.events || []; 41 | vm.excludedDays = vm.excludedDays || []; 42 | 43 | var previousDate = moment(vm.viewDate); 44 | var previousView = vm.view; 45 | 46 | function checkEventIsValid(event) { 47 | if (!event.startsAt) { 48 | $log.warn(LOG_PREFIX, 'Event is missing the startsAt field', event); 49 | } else if (!angular.isDate(event.startsAt)) { 50 | $log.warn(LOG_PREFIX, 'Event startsAt should be a javascript date object. Do `new Date(event.startsAt)` to fix it.', event); 51 | } 52 | 53 | if (event.endsAt) { 54 | if (!angular.isDate(event.endsAt)) { 55 | $log.warn(LOG_PREFIX, 'Event endsAt should be a javascript date object. Do `new Date(event.endsAt)` to fix it.', event); 56 | } 57 | if (moment(event.startsAt).isAfter(moment(event.endsAt))) { 58 | $log.warn(LOG_PREFIX, 'Event cannot start after it finishes', event); 59 | } 60 | } 61 | } 62 | 63 | function refreshCalendar() { 64 | 65 | if (calendarTitle[vm.view] && angular.isDefined($attrs.viewTitle)) { 66 | vm.viewTitle = calendarTitle[vm.view](vm.viewDate); 67 | } 68 | 69 | vm.events.forEach(function(event, index) { 70 | checkEventIsValid(event); 71 | event.calendarEventId = index; 72 | }); 73 | 74 | //if on-timespan-click="calendarDay = calendarDate" is set then don't update the view as nothing needs to change 75 | var currentDate = moment(vm.viewDate); 76 | var shouldUpdate = true; 77 | if ( 78 | previousDate.clone().startOf(vm.view).isSame(currentDate.clone().startOf(vm.view)) && 79 | !previousDate.isSame(currentDate) && 80 | vm.view === previousView 81 | ) { 82 | shouldUpdate = false; 83 | } 84 | previousDate = currentDate; 85 | previousView = vm.view; 86 | 87 | if (shouldUpdate) { 88 | // a $timeout is required as $broadcast is synchronous so if a new events array is set the calendar won't update 89 | $timeout(function() { 90 | $scope.$broadcast('calendar.refreshView'); 91 | }); 92 | } 93 | } 94 | 95 | calendarHelper.loadTemplates().then(function() { 96 | vm.templatesLoaded = true; 97 | 98 | var eventsWatched = false; 99 | 100 | //Refresh the calendar when any of these variables change. 101 | $scope.$watchGroup([ 102 | 'vm.viewDate', 103 | 'vm.view', 104 | 'vm.cellIsOpen', 105 | function() { 106 | return moment.locale() + $locale.id; //Auto update the calendar when the locale changes 107 | } 108 | ], function() { 109 | if (!eventsWatched) { 110 | eventsWatched = true; 111 | //need to deep watch events hence why it isn't included in the watch group 112 | $scope.$watch('vm.events', refreshCalendar, true); //this will call refreshCalendar when the watcher starts (i.e. now) 113 | } else { 114 | refreshCalendar(); 115 | } 116 | }); 117 | 118 | }).catch(function(err) { 119 | $log.error('Could not load all calendar templates', err); 120 | }); 121 | 122 | }; 123 | 124 | if (angular.version.minor < 5) { 125 | vm.$onInit(); 126 | } 127 | 128 | }) 129 | .directive('mwlCalendar', function() { 130 | 131 | return { 132 | template: '
', 133 | restrict: 'E', 134 | scope: { 135 | events: '=', 136 | view: '=', 137 | viewTitle: '=?', 138 | viewDate: '=', 139 | cellIsOpen: '=?', 140 | cellAutoOpenDisabled: '=?', 141 | excludedDays: '=?', 142 | slideBoxDisabled: '=?', 143 | customTemplateUrls: '=?', 144 | draggableAutoScroll: '=?', 145 | onEventClick: '&', 146 | onEventTimesChanged: '&', 147 | onTimespanClick: '&', 148 | onDateRangeSelect: '&?', 149 | onViewChangeClick: '&', 150 | cellModifier: '&', 151 | dayViewStart: '@', 152 | dayViewSegmentSize: '@', 153 | dayViewEnd: '@', 154 | dayViewSplit: '@', 155 | dayViewEventChunkSize: '@', 156 | dayViewEventWidth: '@', 157 | templateScope: '=?', 158 | dayViewTimePosition: '@' 159 | }, 160 | controller: 'MwlCalendarCtrl as vm', 161 | bindToController: true 162 | }; 163 | 164 | }); 165 | -------------------------------------------------------------------------------- /src/directives/mwlCalendarDay.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlCalendarDayCtrl', function($scope, moment, calendarHelper, calendarEventTitle) { 8 | 9 | var vm = this; 10 | 11 | vm.calendarEventTitle = calendarEventTitle; 12 | 13 | function refreshView() { 14 | 15 | vm.timeHidden = vm.dayViewTimePosition === 'hidden'; 16 | vm.dayViewTimePositionOffset = vm.dayViewTimePosition !== 'default' ? 0 : 60; 17 | 18 | vm.dayViewSplit = vm.dayViewSplit || 30; 19 | vm.dayViewHeight = calendarHelper.getDayViewHeight( 20 | vm.dayViewStart, 21 | vm.dayViewEnd, 22 | vm.dayViewSplit, 23 | vm.dayViewSegmentSize 24 | ); 25 | 26 | var view = calendarHelper.getDayView( 27 | vm.events, 28 | vm.viewDate, 29 | vm.dayViewStart, 30 | vm.dayViewEnd, 31 | vm.dayViewSplit, 32 | vm.dayViewEventWidth, 33 | vm.dayViewSegmentSize 34 | ); 35 | 36 | vm.allDayEvents = view.allDayEvents; 37 | vm.nonAllDayEvents = view.events; 38 | vm.viewWidth = view.width + 62; 39 | 40 | } 41 | 42 | $scope.$on('calendar.refreshView', refreshView); 43 | 44 | $scope.$watchGroup([ 45 | 'vm.dayViewStart', 46 | 'vm.dayViewEnd', 47 | 'vm.dayViewSplit' 48 | ], refreshView); 49 | 50 | vm.eventDragComplete = function(event, minuteChunksMoved) { 51 | var minutesDiff = minuteChunksMoved * vm.dayViewSplit; 52 | var newStart = moment(event.startsAt).add(minutesDiff, 'minutes'); 53 | var newEnd = moment(event.endsAt).add(minutesDiff, 'minutes'); 54 | delete event.tempStartsAt; 55 | 56 | vm.onEventTimesChanged({ 57 | calendarEvent: event, 58 | calendarNewEventStart: newStart.toDate(), 59 | calendarNewEventEnd: event.endsAt ? newEnd.toDate() : null 60 | }); 61 | }; 62 | 63 | vm.eventDragged = function(event, minuteChunksMoved) { 64 | var minutesDiff = minuteChunksMoved * vm.dayViewSplit; 65 | event.tempStartsAt = moment(event.startsAt).add(minutesDiff, 'minutes').toDate(); 66 | }; 67 | 68 | vm.eventResizeComplete = function(event, edge, minuteChunksMoved) { 69 | var minutesDiff = minuteChunksMoved * vm.dayViewSplit; 70 | var start = moment(event.startsAt); 71 | var end = moment(event.endsAt); 72 | if (edge === 'start') { 73 | start.add(minutesDiff, 'minutes'); 74 | } else { 75 | end.add(minutesDiff, 'minutes'); 76 | } 77 | delete event.tempStartsAt; 78 | 79 | vm.onEventTimesChanged({ 80 | calendarEvent: event, 81 | calendarNewEventStart: start.toDate(), 82 | calendarNewEventEnd: end.toDate() 83 | }); 84 | }; 85 | 86 | vm.eventResized = function(event, edge, minuteChunksMoved) { 87 | var minutesDiff = minuteChunksMoved * vm.dayViewSplit; 88 | if (edge === 'start') { 89 | event.tempStartsAt = moment(event.startsAt).add(minutesDiff, 'minutes').toDate(); 90 | } 91 | }; 92 | 93 | }) 94 | .directive('mwlCalendarDay', function() { 95 | 96 | return { 97 | template: '
', 98 | restrict: 'E', 99 | require: '^mwlCalendar', 100 | scope: { 101 | events: '=', 102 | viewDate: '=', 103 | onEventClick: '=', 104 | onEventTimesChanged: '=', 105 | onTimespanClick: '=', 106 | onDateRangeSelect: '=', 107 | dayViewStart: '=', 108 | dayViewEnd: '=', 109 | dayViewSplit: '=', 110 | dayViewEventChunkSize: '=', 111 | dayViewSegmentSize: '=', 112 | dayViewEventWidth: '=', 113 | customTemplateUrls: '=?', 114 | cellModifier: '=', 115 | templateScope: '=', 116 | dayViewTimePosition: '=', 117 | draggableAutoScroll: '=' 118 | }, 119 | controller: 'MwlCalendarDayCtrl as vm', 120 | bindToController: true 121 | }; 122 | 123 | }); 124 | -------------------------------------------------------------------------------- /src/directives/mwlCalendarHourList.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | var calendarUtils = require('calendar-utils'); 5 | 6 | angular 7 | .module('mwl.calendar') 8 | .controller('MwlCalendarHourListCtrl', function($scope, $document, moment, calendarHelper) { 9 | var vm = this; 10 | 11 | // source: http://stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript 12 | function getScrollbarWidth() { 13 | var outer = $document[0].createElement('div'); 14 | outer.style.visibility = 'hidden'; 15 | outer.style.width = '100px'; 16 | outer.style.msOverflowStyle = 'scrollbar'; // needed for WinJS apps 17 | 18 | $document[0].body.appendChild(outer); 19 | 20 | var widthNoScroll = outer.offsetWidth; 21 | // force scrollbars 22 | outer.style.overflow = 'scroll'; 23 | 24 | // add innerdiv 25 | var inner = $document[0].createElement('div'); 26 | inner.style.width = '100%'; 27 | outer.appendChild(inner); 28 | 29 | var widthWithScroll = inner.offsetWidth; 30 | 31 | // remove divs 32 | outer.parentNode.removeChild(outer); 33 | 34 | return widthNoScroll - widthWithScroll; 35 | } 36 | 37 | vm.scrollBarWidth = getScrollbarWidth(); 38 | 39 | function updateDays() { 40 | 41 | vm.dayViewSplit = parseInt(vm.dayViewSplit); 42 | var dayStart = (vm.dayViewStart || '00:00').split(':'); 43 | var dayEnd = (vm.dayViewEnd || '23:59').split(':'); 44 | vm.hourGrid = calendarUtils.getDayViewHourGrid({ 45 | viewDate: vm.view === 'week' ? moment(vm.viewDate).startOf('week').toDate() : moment(vm.viewDate).toDate(), 46 | hourSegments: 60 / vm.dayViewSplit, 47 | dayStart: { 48 | hour: dayStart[0], 49 | minute: dayStart[1] 50 | }, 51 | dayEnd: { 52 | hour: dayEnd[0], 53 | minute: dayEnd[1] 54 | } 55 | }); 56 | 57 | vm.hourGrid.forEach(function(hour) { 58 | hour.segments.forEach(function(segment) { 59 | 60 | segment.date = moment(segment.date); 61 | segment.nextSegmentDate = segment.date.clone().add(vm.dayViewSplit, 'minutes'); 62 | 63 | if (vm.view === 'week') { 64 | 65 | segment.days = []; 66 | 67 | for (var i = 0; i < 7; i++) { 68 | var day = { 69 | date: moment(segment.date).add(i, 'days') 70 | }; 71 | day.nextSegmentDate = day.date.clone().add(vm.dayViewSplit, 'minutes'); 72 | vm.cellModifier({calendarCell: day}); 73 | segment.days.push(day); 74 | } 75 | 76 | } else { 77 | vm.cellModifier({calendarCell: segment}); 78 | } 79 | 80 | }); 81 | }); 82 | 83 | } 84 | 85 | var originalLocale = moment.locale(); 86 | 87 | $scope.$on('calendar.refreshView', function() { 88 | 89 | if (originalLocale !== moment.locale()) { 90 | originalLocale = moment.locale(); 91 | updateDays(); 92 | } 93 | 94 | }); 95 | 96 | $scope.$watchGroup([ 97 | 'vm.dayViewStart', 98 | 'vm.dayViewEnd', 99 | 'vm.dayViewSplit', 100 | 'vm.viewDate' 101 | ], function() { 102 | updateDays(); 103 | }); 104 | 105 | vm.eventDropped = function(event, date) { 106 | var newStart = moment(date); 107 | var newEnd = calendarHelper.adjustEndDateFromStartDiff(event.startsAt, newStart, event.endsAt); 108 | 109 | vm.onEventTimesChanged({ 110 | calendarEvent: event, 111 | calendarDate: date, 112 | calendarNewEventStart: newStart.toDate(), 113 | calendarNewEventEnd: newEnd ? newEnd.toDate() : null 114 | }); 115 | }; 116 | 117 | vm.onDragSelectStart = function(date, dayIndex) { 118 | if (!vm.dateRangeSelect) { 119 | vm.dateRangeSelect = { 120 | active: true, 121 | startDate: date, 122 | endDate: date, 123 | dayIndex: dayIndex 124 | }; 125 | } 126 | }; 127 | 128 | vm.onDragSelectMove = function(date) { 129 | if (vm.dateRangeSelect) { 130 | vm.dateRangeSelect.endDate = date; 131 | } 132 | }; 133 | 134 | vm.onDragSelectEnd = function(date) { 135 | if (vm.dateRangeSelect) { 136 | vm.dateRangeSelect.endDate = date; 137 | if (vm.dateRangeSelect.endDate > vm.dateRangeSelect.startDate) { 138 | vm.onDateRangeSelect({ 139 | calendarRangeStartDate: vm.dateRangeSelect.startDate.toDate(), 140 | calendarRangeEndDate: vm.dateRangeSelect.endDate.toDate() 141 | }); 142 | } 143 | delete vm.dateRangeSelect; 144 | } 145 | }; 146 | 147 | }) 148 | .directive('mwlCalendarHourList', function() { 149 | 150 | return { 151 | restrict: 'E', 152 | template: '
', 153 | controller: 'MwlCalendarHourListCtrl as vm', 154 | scope: { 155 | viewDate: '=', 156 | dayViewStart: '=', 157 | dayViewEnd: '=', 158 | dayViewSplit: '=', 159 | dayWidth: '=?', 160 | onTimespanClick: '=', 161 | onDateRangeSelect: '=', 162 | onEventTimesChanged: '=', 163 | customTemplateUrls: '=?', 164 | cellModifier: '=', 165 | templateScope: '=', 166 | view: '@' 167 | }, 168 | bindToController: true 169 | }; 170 | 171 | }); 172 | -------------------------------------------------------------------------------- /src/directives/mwlCalendarSlideBox.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlCalendarSlideBoxCtrl', function($scope, $timeout, calendarConfig, calendarEventTitle) { 8 | 9 | var vm = this; 10 | vm.calendarConfig = calendarConfig; 11 | vm.calendarEventTitle = calendarEventTitle; 12 | 13 | vm.isCollapsed = true; 14 | $scope.$watch('vm.isOpen', function(isOpen) { 15 | //events must be populated first to set the element height before animation will work 16 | $timeout(function() { 17 | vm.isCollapsed = !isOpen; 18 | }); 19 | }); 20 | 21 | }) 22 | .directive('mwlCalendarSlideBox', function() { 23 | 24 | return { 25 | restrict: 'E', 26 | template: '
', 27 | replace: true, 28 | controller: 'MwlCalendarSlideBoxCtrl as vm', 29 | require: ['^?mwlCalendarMonth', '^?mwlCalendarYear'], 30 | link: function(scope, elm, attrs, ctrls) { 31 | scope.isMonthView = !!ctrls[0]; 32 | scope.isYearView = !!ctrls[1]; 33 | }, 34 | scope: { 35 | isOpen: '=', 36 | events: '=', 37 | onEventClick: '=', 38 | cell: '=', 39 | customTemplateUrls: '=?', 40 | templateScope: '=', 41 | draggableAutoScroll: '=' 42 | }, 43 | bindToController: true 44 | }; 45 | 46 | }); 47 | -------------------------------------------------------------------------------- /src/directives/mwlCalendarWeek.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlCalendarWeekCtrl', function($scope, moment, calendarHelper, calendarConfig, calendarEventTitle) { 8 | 9 | var vm = this; 10 | 11 | vm.showTimes = calendarConfig.showTimesOnWeekView; 12 | vm.calendarEventTitle = calendarEventTitle; 13 | 14 | $scope.$on('calendar.refreshView', function() { 15 | vm.dayViewSplit = vm.dayViewSplit || 30; 16 | vm.dayViewHeight = calendarHelper.getDayViewHeight( 17 | vm.dayViewStart, 18 | vm.dayViewEnd, 19 | vm.dayViewSplit 20 | ); 21 | if (vm.showTimes) { 22 | vm.view = calendarHelper.getWeekViewWithTimes( 23 | vm.events, 24 | vm.viewDate, 25 | vm.dayViewStart, 26 | vm.dayViewEnd, 27 | vm.dayViewSplit 28 | ); 29 | } else { 30 | vm.view = calendarHelper.getWeekView(vm.events, vm.viewDate, vm.excludedDays); 31 | } 32 | }); 33 | 34 | vm.weekDragged = function(event, daysDiff, minuteChunksMoved) { 35 | 36 | var newStart = moment(event.startsAt).add(daysDiff, 'days'); 37 | var newEnd = moment(event.endsAt).add(daysDiff, 'days'); 38 | 39 | if (minuteChunksMoved) { 40 | var minutesDiff = minuteChunksMoved * vm.dayViewSplit; 41 | newStart = newStart.add(minutesDiff, 'minutes'); 42 | newEnd = newEnd.add(minutesDiff, 'minutes'); 43 | } 44 | 45 | delete event.tempStartsAt; 46 | 47 | vm.onEventTimesChanged({ 48 | calendarEvent: event, 49 | calendarNewEventStart: newStart.toDate(), 50 | calendarNewEventEnd: event.endsAt ? newEnd.toDate() : null 51 | }); 52 | }; 53 | 54 | vm.eventDropped = function(event, date) { 55 | var daysDiff = moment(date).diff(moment(event.startsAt), 'days'); 56 | vm.weekDragged(event, daysDiff); 57 | }; 58 | 59 | vm.weekResized = function(event, edge, daysDiff) { 60 | 61 | var start = moment(event.startsAt); 62 | var end = moment(event.endsAt); 63 | if (edge === 'start') { 64 | start.add(daysDiff, 'days'); 65 | } else { 66 | end.add(daysDiff, 'days'); 67 | } 68 | 69 | vm.onEventTimesChanged({ 70 | calendarEvent: event, 71 | calendarNewEventStart: start.toDate(), 72 | calendarNewEventEnd: end.toDate() 73 | }); 74 | 75 | }; 76 | 77 | vm.tempTimeChanged = function(event, minuteChunksMoved) { 78 | var minutesDiff = minuteChunksMoved * vm.dayViewSplit; 79 | event.tempStartsAt = moment(event.startsAt).add(minutesDiff, 'minutes').toDate(); 80 | }; 81 | 82 | }) 83 | .directive('mwlCalendarWeek', function() { 84 | 85 | return { 86 | template: '
', 87 | restrict: 'E', 88 | require: '^mwlCalendar', 89 | scope: { 90 | events: '=', 91 | viewDate: '=', 92 | excludedDays: '=', 93 | onEventClick: '=', 94 | onEventTimesChanged: '=', 95 | dayViewStart: '=', 96 | dayViewEnd: '=', 97 | dayViewSplit: '=', 98 | dayViewEventChunkSize: '=', 99 | onTimespanClick: '=', 100 | onDateRangeSelect: '=', 101 | customTemplateUrls: '=?', 102 | cellModifier: '=', 103 | templateScope: '=', 104 | draggableAutoScroll: '=' 105 | }, 106 | controller: 'MwlCalendarWeekCtrl as vm', 107 | link: function(scope, element, attrs, calendarCtrl) { 108 | scope.vm.calendarCtrl = calendarCtrl; 109 | }, 110 | bindToController: true 111 | }; 112 | 113 | }); 114 | -------------------------------------------------------------------------------- /src/directives/mwlCalendarYear.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlCalendarYearCtrl', function($scope, moment, calendarHelper) { 8 | 9 | var vm = this; 10 | vm.openMonthIndex = null; 11 | 12 | function toggleCell() { 13 | vm.openRowIndex = null; 14 | vm.openMonthIndex = null; 15 | 16 | if (vm.cellIsOpen && vm.view) { 17 | vm.view.forEach(function(month, monthIndex) { 18 | if (moment(vm.viewDate).startOf('month').isSame(month.date)) { 19 | vm.openMonthIndex = monthIndex; 20 | vm.openRowIndex = Math.floor(monthIndex / 4); 21 | } 22 | }); 23 | } 24 | } 25 | 26 | $scope.$on('calendar.refreshView', function() { 27 | vm.view = calendarHelper.getYearView(vm.events, vm.viewDate, vm.cellModifier); 28 | 29 | if (vm.cellAutoOpenDisabled) { 30 | toggleCell(); 31 | } else if (!vm.cellAutoOpenDisabled && vm.cellIsOpen && vm.openMonthIndex === null) { 32 | //Auto open the calendar to the current day if set 33 | vm.openMonthIndex = null; 34 | vm.view.forEach(function(month) { 35 | if (moment(vm.viewDate).startOf('month').isSame(month.date)) { 36 | vm.monthClicked(month, true); 37 | } 38 | }); 39 | } 40 | 41 | }); 42 | 43 | vm.monthClicked = function(month, monthClickedFirstRun, $event) { 44 | 45 | if (!monthClickedFirstRun) { 46 | vm.onTimespanClick({ 47 | calendarDate: month.date.toDate(), 48 | calendarCell: month, 49 | $event: $event 50 | }); 51 | if ($event && $event.defaultPrevented) { 52 | return; 53 | } 54 | } 55 | 56 | if (!vm.cellAutoOpenDisabled) { 57 | vm.openRowIndex = null; 58 | var monthIndex = vm.view.indexOf(month); 59 | if (monthIndex === vm.openMonthIndex) { //the month has been clicked and is already open 60 | vm.openMonthIndex = null; //close the open month 61 | vm.cellIsOpen = false; 62 | } else { 63 | vm.openMonthIndex = monthIndex; 64 | vm.openRowIndex = Math.floor(monthIndex / 4); 65 | vm.cellIsOpen = true; 66 | } 67 | } 68 | 69 | }; 70 | 71 | vm.handleEventDrop = function(event, newMonthDate) { 72 | var newStart = moment(event.startsAt) 73 | .year(moment(newMonthDate).year()) 74 | .month(moment(newMonthDate).month()); 75 | var newEnd = calendarHelper.adjustEndDateFromStartDiff(event.startsAt, newStart, event.endsAt); 76 | 77 | vm.onEventTimesChanged({ 78 | calendarEvent: event, 79 | calendarDate: newMonthDate, 80 | calendarNewEventStart: newStart.toDate(), 81 | calendarNewEventEnd: newEnd ? newEnd.toDate() : null 82 | }); 83 | }; 84 | 85 | vm.$onInit = function() { 86 | 87 | if (vm.cellAutoOpenDisabled) { 88 | $scope.$watchGroup([ 89 | 'vm.cellIsOpen', 90 | 'vm.viewDate' 91 | ], toggleCell); 92 | } 93 | 94 | }; 95 | 96 | if (angular.version.minor < 5) { 97 | vm.$onInit(); 98 | } 99 | 100 | }) 101 | .directive('mwlCalendarYear', function() { 102 | 103 | return { 104 | template: '
', 105 | restrict: 'E', 106 | require: '^mwlCalendar', 107 | scope: { 108 | events: '=', 109 | viewDate: '=', 110 | onEventClick: '=', 111 | onEventTimesChanged: '=', 112 | cellIsOpen: '=', 113 | cellAutoOpenDisabled: '=', 114 | onTimespanClick: '=', 115 | cellModifier: '=', 116 | slideBoxDisabled: '=', 117 | customTemplateUrls: '=?', 118 | templateScope: '=' 119 | }, 120 | controller: 'MwlCalendarYearCtrl as vm', 121 | link: function(scope, element, attrs, calendarCtrl) { 122 | scope.vm.calendarCtrl = calendarCtrl; 123 | }, 124 | bindToController: true 125 | }; 126 | 127 | }); 128 | -------------------------------------------------------------------------------- /src/directives/mwlCollapseFallback.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlCollapseFallbackCtrl', function($scope, $attrs, $element) { 8 | 9 | $scope.$watch($attrs.mwlCollapseFallback, function(shouldCollapse) { 10 | if (shouldCollapse) { 11 | $element.addClass('ng-hide'); 12 | } else { 13 | $element.removeClass('ng-hide'); 14 | } 15 | }); 16 | 17 | }) 18 | .directive('mwlCollapseFallback', function($injector) { 19 | 20 | if ($injector.has('uibCollapseDirective')) { 21 | return {}; 22 | } 23 | 24 | return { 25 | restrict: 'A', 26 | controller: 'MwlCollapseFallbackCtrl' 27 | }; 28 | 29 | }); 30 | -------------------------------------------------------------------------------- /src/directives/mwlDateModifier.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlDateModifierCtrl', function($element, $attrs, $scope, moment) { 8 | 9 | var vm = this; 10 | 11 | function onClick() { 12 | if (angular.isDefined($attrs.setToToday)) { 13 | vm.date = new Date(); 14 | } else if (angular.isDefined($attrs.increment)) { 15 | vm.date = moment(vm.date).add(1, vm.increment); 16 | if (vm.excludedDays && vm.increment.indexOf('day') > -1) { 17 | while (vm.excludedDays.indexOf(vm.date.day()) > -1) { 18 | vm.date.add(1, vm.increment); 19 | } 20 | } 21 | vm.date = vm.date.toDate(); 22 | } else if (angular.isDefined($attrs.decrement)) { 23 | vm.date = moment(vm.date).subtract(1, vm.decrement); 24 | if (vm.excludedDays && vm.decrement.indexOf('day') > -1) { 25 | while (vm.excludedDays.indexOf(vm.date.day()) > -1) { 26 | vm.date.subtract(1, vm.decrement); 27 | } 28 | } 29 | vm.date = vm.date.toDate(); 30 | } 31 | $scope.$apply(); 32 | } 33 | 34 | $element.bind('click', onClick); 35 | 36 | $scope.$on('$destroy', function() { 37 | $element.unbind('click', onClick); 38 | }); 39 | 40 | }) 41 | .directive('mwlDateModifier', function() { 42 | 43 | return { 44 | restrict: 'A', 45 | controller: 'MwlDateModifierCtrl as vm', 46 | scope: { 47 | date: '=', 48 | increment: '=', 49 | decrement: '=', 50 | excludedDays: '=?' 51 | }, 52 | bindToController: true 53 | }; 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /src/directives/mwlDragSelect.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlDragSelectCtrl', function($scope, $element, $parse, $attrs) { 8 | 9 | function handleMouseEvent(callbackName) { 10 | return function(event) { 11 | if (callbackName && event.button !== 2) { 12 | $parse(callbackName)($scope); 13 | $scope.$apply(); 14 | } 15 | event.preventDefault(); 16 | }; 17 | } 18 | 19 | var onMouseDown = handleMouseEvent($attrs.onDragSelectStart); 20 | var onMouseMove = handleMouseEvent($attrs.onDragSelectMove); 21 | var onMouseUp = handleMouseEvent($attrs.onDragSelectEnd); 22 | 23 | function enableMouseListeners() { 24 | $element.on('mousedown', onMouseDown); 25 | $element.on('mousemove', onMouseMove); 26 | $element.on('mouseup', onMouseUp); 27 | } 28 | 29 | function disableMouseListeners() { 30 | $element.off('mousedown', onMouseDown); 31 | $element.off('mousemove', onMouseMove); 32 | $element.off('mouseup', onMouseUp); 33 | } 34 | 35 | $scope.$watch($attrs.mwlDragSelect, function(isEnabled) { 36 | if (isEnabled) { 37 | enableMouseListeners(); 38 | } else { 39 | disableMouseListeners(); 40 | } 41 | }); 42 | 43 | $scope.$on('$destroy', function() { 44 | disableMouseListeners(); 45 | }); 46 | 47 | }) 48 | .directive('mwlDragSelect', function() { 49 | 50 | return { 51 | restrict: 'A', 52 | controller: 'MwlDragSelectCtrl' 53 | }; 54 | 55 | }); 56 | -------------------------------------------------------------------------------- /src/directives/mwlDraggable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlDraggableCtrl', function($element, $scope, $window, $parse, $attrs, $timeout, interact) { 8 | 9 | if (!interact) { 10 | return; 11 | } 12 | 13 | var snap, snapGridDimensions; 14 | if ($attrs.snapGrid) { 15 | snapGridDimensions = $parse($attrs.snapGrid)($scope); 16 | snap = { 17 | targets: [ 18 | interact.createSnapGrid(snapGridDimensions) 19 | ] 20 | }; 21 | } 22 | 23 | function translateElement(elm, transformValue) { 24 | return elm 25 | .css('-ms-transform', transformValue) 26 | .css('-webkit-transform', transformValue) 27 | .css('transform', transformValue); 28 | } 29 | 30 | var autoScroll = $parse($attrs.autoScroll)($scope); 31 | if (typeof autoScroll === 'undefined') { 32 | autoScroll = true; 33 | } 34 | 35 | interact($element[0]).draggable({ 36 | autoScroll: autoScroll, 37 | snap: snap, 38 | onstart: function(event) { 39 | angular.element(event.target).addClass('dragging-active'); 40 | event.target.dropData = $parse($attrs.dropData)($scope); 41 | event.target.style.pointerEvents = 'none'; 42 | if ($attrs.onDragStart) { 43 | $parse($attrs.onDragStart)($scope); 44 | $scope.$apply(); 45 | } 46 | }, 47 | onmove: function(event) { 48 | 49 | var elm = angular.element(event.target); 50 | var x = (parseFloat(elm.attr('data-x')) || 0) + (event.dx || 0); 51 | var y = (parseFloat(elm.attr('data-y')) || 0) + (event.dy || 0); 52 | 53 | switch ($parse($attrs.axis)($scope)) { 54 | case 'x': 55 | y = 0; 56 | break; 57 | 58 | case 'y': 59 | x = 0; 60 | break; 61 | 62 | default: 63 | } 64 | 65 | if ($window.getComputedStyle(elm[0]).position === 'static') { 66 | elm.css('position', 'relative'); 67 | } 68 | 69 | translateElement(elm, 'translate(' + x + 'px, ' + y + 'px)') 70 | .css('z-index', 50) 71 | .attr('data-x', x) 72 | .attr('data-y', y); 73 | 74 | if ($attrs.onDrag) { 75 | $parse($attrs.onDrag)($scope, {x: x, y: y}); 76 | $scope.$apply(); 77 | } 78 | 79 | }, 80 | onend: function(event) { 81 | 82 | var elm = angular.element(event.target); 83 | var x = elm.attr('data-x'); 84 | var y = elm.attr('data-y'); 85 | 86 | event.target.style.pointerEvents = 'auto'; 87 | if ($attrs.onDragEnd) { 88 | $parse($attrs.onDragEnd)($scope, {x: x, y: y}); 89 | $scope.$apply(); 90 | } 91 | 92 | $timeout(function() { 93 | translateElement(elm, '') 94 | .css('z-index', 'auto') 95 | .removeAttr('data-x') 96 | .removeAttr('data-y') 97 | .removeClass('dragging-active'); 98 | }); 99 | 100 | } 101 | }); 102 | 103 | $scope.$watch($attrs.mwlDraggable, function(enabled) { 104 | interact($element[0]).draggable({ 105 | enabled: enabled 106 | }); 107 | }); 108 | 109 | $scope.$on('$destroy', function() { 110 | interact($element[0]).unset(); 111 | }); 112 | 113 | }) 114 | .directive('mwlDraggable', function() { 115 | 116 | return { 117 | restrict: 'A', 118 | controller: 'MwlDraggableCtrl' 119 | }; 120 | 121 | }); 122 | -------------------------------------------------------------------------------- /src/directives/mwlDroppable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlDroppableCtrl', function($element, $scope, $parse, $attrs, interact) { 8 | 9 | if (!interact) { 10 | return; 11 | } 12 | 13 | var DROP_ACTIVE_CLASS = $attrs.dropActiveClass || 'drop-active'; 14 | var INTERACT_OVERLAP_TYPE = $attrs.dropOverlap || 'pointer'; 15 | 16 | interact($element[0]).dropzone({ 17 | ondragenter: function(event) { 18 | angular.element(event.target).addClass(DROP_ACTIVE_CLASS); 19 | }, 20 | ondragleave: function(event) { 21 | angular.element(event.target).removeClass(DROP_ACTIVE_CLASS); 22 | }, 23 | ondropdeactivate: function(event) { 24 | angular.element(event.target).removeClass(DROP_ACTIVE_CLASS); 25 | }, 26 | ondrop: function(event) { 27 | if (event.relatedTarget.dropData) { 28 | $parse($attrs.onDrop)($scope, {dropData: event.relatedTarget.dropData}); 29 | $scope.$apply(); 30 | } 31 | }, 32 | overlap: INTERACT_OVERLAP_TYPE 33 | }); 34 | 35 | $scope.$on('$destroy', function() { 36 | interact($element[0]).unset(); 37 | }); 38 | 39 | }) 40 | .directive('mwlDroppable', function() { 41 | 42 | return { 43 | restrict: 'A', 44 | controller: 'MwlDroppableCtrl' 45 | }; 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /src/directives/mwlDynamicDirectiveTemplate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlDynamicDirectiveTemplateCtrl', function($compile, $scope, $attrs, $element, $templateCache, $log, calendarConfig) { 8 | 9 | $scope.$watch($attrs.overrides, function(overrides) { 10 | 11 | var templateName = calendarConfig.templates[$attrs.name]; 12 | if ( 13 | overrides && 14 | angular.isObject(overrides) && 15 | overrides[$attrs.name] 16 | ) { 17 | if ($templateCache.get(overrides[$attrs.name])) { 18 | templateName = overrides[$attrs.name]; 19 | } else { 20 | $log.warn('Bootstrap Calendar', 'The custom template for ' + overrides[$attrs.name] + 21 | ' was not found in the template cache. Please ensure it is pre-loaded via a script tag ' + 22 | ' or ' + 23 | 'via a tool such as https://www.npmjs.com/package/gulp-angular-templatecache'); 24 | } 25 | } 26 | var template = $templateCache.get(templateName); 27 | $element.html(template); 28 | $compile($element.contents())($scope); 29 | 30 | }); 31 | 32 | }) 33 | .directive('mwlDynamicDirectiveTemplate', function() { 34 | 35 | return { 36 | restrict: 'A', 37 | controller: 'MwlDynamicDirectiveTemplateCtrl' 38 | }; 39 | 40 | }); 41 | -------------------------------------------------------------------------------- /src/directives/mwlElementDimensions.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlElementDimensionsCtrl', function($element, $scope, $parse, $attrs, $window) { 8 | 9 | function setDimensions() { 10 | $parse($attrs.mwlElementDimensions).assign($scope, { 11 | width: $element[0].offsetWidth - 1, 12 | height: $element[0].offsetHeight 13 | }); 14 | $scope.$applyAsync(); 15 | } 16 | 17 | var win = angular.element($window); 18 | 19 | win.bind('resize', setDimensions); 20 | 21 | setDimensions(); 22 | 23 | $scope.$on('$destroy', function() { 24 | win.unbind('resize', setDimensions); 25 | }); 26 | 27 | }) 28 | .directive('mwlElementDimensions', function() { 29 | 30 | return { 31 | restrict: 'A', 32 | controller: 'MwlElementDimensionsCtrl' 33 | }; 34 | 35 | }); 36 | -------------------------------------------------------------------------------- /src/directives/mwlResizable.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .controller('MwlResizableCtrl', function($element, $scope, $parse, $attrs, $timeout, interact) { 8 | 9 | if (!interact) { 10 | return; 11 | } 12 | 13 | var snap, snapGridDimensions; 14 | if ($attrs.snapGrid) { 15 | snapGridDimensions = $parse($attrs.snapGrid)($scope); 16 | snap = { 17 | targets: [ 18 | interact.createSnapGrid(snapGridDimensions) 19 | ] 20 | }; 21 | } 22 | 23 | var originalDimensions = {}; 24 | var originalDimensionsStyle = {}; 25 | var resizeEdge; 26 | 27 | function getUnitsResized(edge, elm) { 28 | var unitsResized = {}; 29 | unitsResized.edge = edge; 30 | if (edge === 'start') { 31 | unitsResized.x = elm.data('x'); 32 | unitsResized.y = elm.data('y'); 33 | } else if (edge === 'end') { 34 | unitsResized.x = parseFloat(elm.css('width').replace('px', '')) - originalDimensions.width; 35 | unitsResized.y = parseFloat(elm.css('height').replace('px', '')) - originalDimensions.height; 36 | } 37 | return unitsResized; 38 | } 39 | 40 | interact($element[0]).resizable({ 41 | edges: $parse($attrs.resizeEdges)($scope), 42 | snap: snap, 43 | onstart: function(event) { 44 | 45 | resizeEdge = 'end'; 46 | var elm = angular.element(event.target); 47 | originalDimensions.height = elm[0].offsetHeight; 48 | originalDimensions.width = elm[0].offsetWidth; 49 | originalDimensionsStyle.height = elm.css('height'); 50 | originalDimensionsStyle.width = elm.css('width'); 51 | 52 | }, 53 | onmove: function(event) { 54 | 55 | if (event.rect.width > 0 && event.rect.height > 0) { 56 | var elm = angular.element(event.target); 57 | var x = parseFloat(elm.data('x') || 0); 58 | var y = parseFloat(elm.data('y') || 0); 59 | 60 | elm.css({ 61 | width: event.rect.width + 'px', 62 | height: event.rect.height + 'px' 63 | }); 64 | 65 | // translate when resizing from top or left edges 66 | x += event.deltaRect.left; 67 | y += event.deltaRect.top; 68 | 69 | elm.css('transform', 'translate(' + x + 'px,' + y + 'px)'); 70 | 71 | elm.data('x', x); 72 | elm.data('y', y); 73 | 74 | if (event.deltaRect.left !== 0 || event.deltaRect.top !== 0) { 75 | resizeEdge = 'start'; 76 | } 77 | 78 | if ($attrs.onResize) { 79 | $parse($attrs.onResize)($scope, getUnitsResized(resizeEdge, elm)); 80 | $scope.$apply(); 81 | } 82 | 83 | } 84 | 85 | }, 86 | onend: function(event) { 87 | 88 | var elm = angular.element(event.target); 89 | var unitsResized = getUnitsResized(resizeEdge, elm); 90 | 91 | $timeout(function() { 92 | elm 93 | .data('x', null) 94 | .data('y', null) 95 | .css({ 96 | transform: '', 97 | width: originalDimensionsStyle.width, 98 | height: originalDimensionsStyle.height 99 | }); 100 | }); 101 | 102 | if ($attrs.onResizeEnd) { 103 | $parse($attrs.onResizeEnd)($scope, unitsResized); 104 | $scope.$apply(); 105 | } 106 | 107 | } 108 | }); 109 | 110 | $scope.$watch($attrs.mwlResizable, function(enabled) { 111 | interact($element[0]).resizable({ 112 | enabled: enabled 113 | }); 114 | }); 115 | 116 | $scope.$on('$destroy', function() { 117 | interact($element[0]).unset(); 118 | }); 119 | 120 | }) 121 | .directive('mwlResizable', function() { 122 | 123 | return { 124 | restrict: 'A', 125 | controller: 'MwlResizableCtrl' 126 | }; 127 | 128 | }); 129 | -------------------------------------------------------------------------------- /src/entry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | require('./less/calendar.less'); 4 | 5 | var angular = require('angular'); 6 | 7 | function requireAll(r) { 8 | r.keys().forEach(r); 9 | } 10 | 11 | var templates = {}; 12 | 13 | if (EXCLUDE_TEMPLATES === false) { 14 | 15 | var templatesContext = require.context('./templates', false, /\.html/); 16 | 17 | templatesContext.keys().forEach(function(templateName) { 18 | var templateNameWithoutPrefix = templateName.replace('./', ''); 19 | var cacheTemplateName = 'mwl/' + templateNameWithoutPrefix; 20 | var configTemplateName = templateNameWithoutPrefix.replace('.html', ''); 21 | templates[configTemplateName] = { 22 | cacheTemplateName: cacheTemplateName, 23 | template: templatesContext(templateName) 24 | }; 25 | }); 26 | 27 | } 28 | 29 | module.exports = angular 30 | .module('mwl.calendar', []) 31 | .config(function(calendarConfig) { 32 | angular.forEach(templates, function(template, templateName) { 33 | if (!calendarConfig.templates[templateName]) { 34 | calendarConfig.templates[templateName] = template.cacheTemplateName; 35 | } 36 | }); 37 | }) 38 | .run(function($templateCache, $interpolate) { 39 | 40 | angular.forEach(templates, function(template) { 41 | if (!$templateCache.get(template.cacheTemplateName)) { 42 | var templateContents = template.template 43 | .replace('{{', $interpolate.startSymbol()) 44 | .replace('}}', $interpolate.endSymbol()); 45 | $templateCache.put(template.cacheTemplateName, templateContents); 46 | } 47 | }); 48 | 49 | }).name; 50 | 51 | requireAll(require.context('./directives', true, /\.js$/)); 52 | requireAll(require.context('./filters', true, /\.js$/)); 53 | requireAll(require.context('./services', true, /\.js$/)); 54 | -------------------------------------------------------------------------------- /src/filters/calendarDate.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .filter('calendarDate', function(calendarHelper, calendarConfig) { 8 | 9 | function calendarDate(date, format, getFromConfig) { 10 | 11 | if (getFromConfig === true) { 12 | format = calendarConfig.dateFormats[format]; 13 | } 14 | 15 | return calendarHelper.formatDate(date, format); 16 | 17 | } 18 | 19 | // This is stateful because the locale can change as well 20 | // as calendarConfig.dateFormats which would change the value outside of this filter 21 | calendarDate.$stateful = true; 22 | 23 | return calendarDate; 24 | 25 | }); 26 | -------------------------------------------------------------------------------- /src/filters/calendarLimitTo.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .filter('calendarLimitTo', function(limitToFilter) { 8 | 9 | if (angular.version.minor >= 4) { //1.4+ supports the begin attribute 10 | return limitToFilter; 11 | } 12 | 13 | //Copied from the angular source. Only 1.4 has the begin functionality. 14 | return function(input, limit, begin) { 15 | if (Math.abs(Number(limit)) === Infinity) { 16 | limit = Number(limit); 17 | } else { 18 | limit = parseInt(limit); 19 | } 20 | if (isNaN(limit)) { 21 | return input; 22 | } 23 | 24 | if (angular.isNumber(input)) { 25 | input = input.toString(); 26 | } 27 | if (!angular.isArray(input) && !angular.isString(input)) { 28 | return input; 29 | } 30 | 31 | begin = (!begin || isNaN(begin)) ? 0 : parseInt(begin); 32 | begin = (begin < 0 && begin >= -input.length) ? input.length + begin : begin; 33 | 34 | if (limit >= 0) { 35 | return input.slice(begin, begin + limit); 36 | } else if (begin === 0) { 37 | return input.slice(limit, input.length); 38 | } else { 39 | return input.slice(Math.max(0, begin + limit), begin); 40 | } 41 | }; 42 | 43 | }); 44 | -------------------------------------------------------------------------------- /src/filters/calendarTruncateEventTitle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .filter('calendarTruncateEventTitle', function() { 8 | 9 | return function(string, length, boxHeight) { 10 | if (!string) { 11 | return ''; 12 | } 13 | 14 | //Only truncate if if actually needs truncating 15 | if (string.length >= length && string.length / 20 > boxHeight / 30) { 16 | return string.substr(0, length) + '...'; 17 | } else { 18 | return string; 19 | } 20 | }; 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /src/filters/calendarTrustAsHtml.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .filter('calendarTrustAsHtml', function($sce) { 8 | 9 | return function(text) { 10 | return $sce.trustAsHtml(text); 11 | }; 12 | 13 | }); 14 | -------------------------------------------------------------------------------- /src/less/calendar.less: -------------------------------------------------------------------------------- 1 | @import "variables.less"; 2 | @import "grid.less"; 3 | @import "theme.less"; 4 | @import "month.less"; 5 | @import "week.less"; 6 | @import "day.less"; 7 | @import "events.less"; 8 | 9 | -------------------------------------------------------------------------------- /src/less/day.less: -------------------------------------------------------------------------------- 1 | .cal-day-box { 2 | text-wrap: none; 3 | overflow-x: auto !important; 4 | overflow-y: hidden; 5 | 6 | .cal-day-hour-part { 7 | height: 30px; 8 | box-sizing: border-box; 9 | -moz-box-sizing: border-box; 10 | -webkit-box-sizing: border-box; 11 | border-bottom: thin dashed @borderColor; 12 | 13 | .cal-day-hour-part-time { 14 | width: 60px; 15 | text-align: center; 16 | float: left; 17 | } 18 | 19 | .cal-day-hour-part-spacer { 20 | height: 30px; 21 | display: inline-block; 22 | } 23 | 24 | &:hover { background-color: @dayHover; } 25 | } 26 | .cal-day-hour-part-selected { 27 | background-color: @borderColor; 28 | } 29 | .cal-day-hour { 30 | .day-highlight { 31 | height: 30px; 32 | } 33 | 34 | background-color: @eventBorderColor; 35 | &:nth-child(odd) { 36 | background-color: @rowHover; 37 | } 38 | } 39 | .cal-hours { 40 | font-weight: bold; 41 | font-size: 12px; 42 | } 43 | 44 | .cal-day-panel { 45 | position: relative; 46 | padding-left: 60px; 47 | border: solid 1px #e1e1e1; 48 | } 49 | .cal-day-panel-hour { 50 | position: absolute; 51 | width: 100%; 52 | margin-left: -60px; 53 | } 54 | .day-event { 55 | position: absolute; 56 | width: 150px; 57 | overflow: hidden; 58 | padding: 2px 3px !important; 59 | 60 | a { 61 | font-size: 12px; 62 | text-overflow: ellipsis; 63 | } 64 | 65 | } 66 | .day-highlight { 67 | padding-top: 2px; 68 | padding-left: 8px; 69 | padding-right: 8px; 70 | box-sizing: border-box; 71 | -moz-box-sizing: border-box; 72 | -webkit-box-sizing: border-box; 73 | border: 1px solid @eventStandardColor; 74 | margin: 1px 1px; 75 | overflow: hidden; 76 | text-overflow: ellipsis; 77 | } 78 | } 79 | 80 | mwl-calendar-day.time-hidden { 81 | .cal-day-hour-part-time { 82 | display: none; 83 | } 84 | } 85 | 86 | mwl-calendar-day.time-on-side { 87 | .cal-day-box { 88 | overflow: visible !important; 89 | margin-left: @sideMargin; 90 | } 91 | .cal-day-panel { 92 | min-width: initial !important; 93 | } 94 | .cal-day-hour-part-time { 95 | margin-left: -@sideMargin; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/less/events.less: -------------------------------------------------------------------------------- 1 | mwl-calendar { 2 | 3 | .event { 4 | display: block; 5 | background-color: @eventStandardColor; 6 | width: @eventSize; 7 | height: @eventSize; 8 | margin-right: @eventMargin; 9 | margin-bottom: @eventMargin; 10 | -webkit-box-shadow: inset 0px 0px 5px 0px rgba(0, 0, 0, 0.4); 11 | box-shadow: inset 0px 0px 5px 0px rgba(0, 0, 0, 0.4); 12 | border-radius: @eventBorderRadius; 13 | border: @eventBorderSize solid @eventBorderColor; 14 | } 15 | 16 | .event-block { 17 | display: block; 18 | background-color: #c3c3c3; 19 | width: 20px; 20 | height: 100%; 21 | } 22 | 23 | .cal-event-list .event.pull-left { 24 | margin-top: 3px; 25 | } 26 | 27 | .day-highlight:hover, 28 | .day-highlight { 29 | background-color: @eventHiliteStandart; 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/less/grid-mixin.less: -------------------------------------------------------------------------------- 1 | .gridForWeekOf(@days) { 2 | @oneDay: 100% / @days; 3 | @oneDayIE: 0.9993781095 * @oneDay; 4 | //magic constant, have no idea from where it comes from 5 | 6 | .generate-day(); 7 | 8 | .generate-day(@i:1) when (@i =< @days) { 9 | .cal-row-fluid .cal-cell@{i} { 10 | width: (@i * @oneDay); 11 | *width: (@i * @oneDayIE); 12 | } 13 | 14 | .cal-row-fluid .cal-offset@{i}, 15 | .cal-row-fluid .cal-offset@{i}:first-child, 16 | .cal-week-box .cal-offset@{i} { 17 | margin-left: (@i * @oneDay); 18 | *margin-left: (@i * @oneDayIE); 19 | } 20 | .generate-day(@i + 1); 21 | } 22 | 23 | } 24 | 25 | .gridForWeeks(@i:1) when (@i =< 7) { 26 | .cal-week-box, 27 | .cal-month-box 28 | { 29 | &.cal-grid-@{i} { 30 | .gridForWeekOf(@i); 31 | } 32 | } 33 | 34 | .gridForWeeks(@i + 1); 35 | } 36 | -------------------------------------------------------------------------------- /src/less/grid.less: -------------------------------------------------------------------------------- 1 | @import "grid-mixin.less"; 2 | 3 | mwl-calendar { 4 | 5 | [class*="cal-cell"] { 6 | float: left; 7 | margin-left: 0; 8 | min-height: 1px; 9 | } 10 | 11 | .cal-row-fluid { 12 | width: 100%; 13 | *zoom: 1; 14 | } 15 | 16 | .cal-row-fluid:before, 17 | .cal-row-fluid:after { 18 | display: table; 19 | content: ""; 20 | line-height: 0; 21 | } 22 | 23 | .cal-row-fluid:after { 24 | clear: both; 25 | } 26 | 27 | .cal-row-fluid [class*="cal-cell"] { 28 | display: block; 29 | width: 100%; 30 | -webkit-box-sizing: border-box; 31 | -moz-box-sizing: border-box; 32 | box-sizing: border-box; 33 | float: left; 34 | margin-left: 0%; 35 | *margin-left: -0.05213764337851929%; 36 | } 37 | 38 | .cal-row-fluid [class*="cal-cell"]:first-child { 39 | margin-left: 0; 40 | } 41 | 42 | .cal-row-fluid .controls-row [class*="cal-cell"] + [class*="cal-cell"] { 43 | margin-left: 0%; 44 | } 45 | 46 | .gridForWeeks(); 47 | 48 | [class*="cal-cell"].hide, 49 | .cal-row-fluid [class*="cal-cell"].hide { 50 | display: none; 51 | } 52 | 53 | [class*="cal-cell"].pull-right, 54 | .cal-row-fluid [class*="cal-cell"].pull-right { 55 | float: right; 56 | } 57 | 58 | 59 | } -------------------------------------------------------------------------------- /src/less/month.less: -------------------------------------------------------------------------------- 1 | mwl-calendar .cal-month-box { 2 | .cal-row-fluid { 3 | border-right: @borderSizevert @borderStyle @borderColor; 4 | border-left: @borderSizevert @borderStyle @borderColor; 5 | } 6 | 7 | .cal-row-head { 8 | border-left: none; 9 | border-right: none; 10 | [class*="cal-cell"] { 11 | border: none; 12 | overflow: hidden; 13 | min-height: unset; 14 | text-overflow: ellipsis; 15 | } 16 | } 17 | 18 | .cal-month-day { 19 | position: relative; 20 | display: block; 21 | width: 100%; 22 | .cal-events-num { 23 | margin-left: 10px; 24 | margin-top: 18px; 25 | } 26 | } 27 | 28 | .cal-week-box-cell { 29 | position: absolute; 30 | width: 70px; 31 | left: -71px; 32 | top: -1px; 33 | padding: 8px 5px; 34 | cursor: pointer; 35 | } 36 | 37 | .cal-slide-box { 38 | position: relative; 39 | } 40 | 41 | .cal-slide-tick { 42 | position: absolute; 43 | width: 16px; 44 | margin-left: -7px; 45 | height: 9px; 46 | top: -1px; 47 | z-index: 1; 48 | } 49 | .cal-slide-tick.tick-month1 { 50 | left: 12.5%; 51 | } 52 | .cal-slide-tick.tick-month2 { 53 | left: 37.5%; 54 | } 55 | .cal-slide-tick.tick-month3 { 56 | left: 62.5%; 57 | } 58 | .cal-slide-tick.tick-month4 { 59 | left: 87.5%; 60 | } 61 | 62 | .cal-slide-tick.tick-day1 { 63 | left: 7.14285714285715%; 64 | } 65 | .cal-slide-tick.tick-day2 { 66 | left: 21.42857142857143%; 67 | } 68 | .cal-slide-tick.tick-day3 { 69 | left: 35.71428571428572%; 70 | } 71 | .cal-slide-tick.tick-day4 { 72 | left: 50%; 73 | } 74 | .cal-slide-tick.tick-day5 { 75 | left: 64.2857142857143%; 76 | } 77 | .cal-slide-tick.tick-day6 { 78 | left: 78.57142857142859%; 79 | } 80 | .cal-slide-tick.tick-day7 { 81 | left: 92.85714285714285%; 82 | } 83 | .events-list { 84 | position: absolute; 85 | bottom: 0; 86 | left: 0; 87 | z-index: 50; 88 | } 89 | .cal-slide-content ul.unstyled { 90 | margin-bottom: 0; 91 | } 92 | .cal-slide-content ul li.dragging-active .event-item { 93 | color: black; 94 | } 95 | 96 | .cal-day-selected { 97 | background-color: #ededed; 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/less/theme.less: -------------------------------------------------------------------------------- 1 | mwl-calendar { 2 | .cal-row-head { 3 | [class*="cal-cell"] { 4 | border: none; 5 | padding: 5px 0; 6 | text-align: center; 7 | font-weight: bolder; 8 | } 9 | [class*="cal-cell"] small { 10 | font-weight: normal; 11 | } 12 | } 13 | .cal-year-box .row:hover, 14 | .cal-row-fluid:hover { 15 | background-color: @rowHover; 16 | } 17 | .cal-month-day { 18 | height: @rowHeightMonth; 19 | } 20 | 21 | [class*="cal-cell"]:hover, 22 | .cell-focus, 23 | [class*="cal-cell"] .drop-active, 24 | .cal-cell.drop-active, 25 | .cal-week-box .cal-cell1.drop-active, 26 | .cal-day-hour-part.drop-active { 27 | background-color: @dayHover; 28 | } 29 | 30 | .cal-year-box [class*="span"], 31 | .cal-month-box [class*="cal-cell"] { 32 | min-height: @rowHeightMonth; 33 | position: relative; 34 | } 35 | 36 | .cal-year-box, 37 | .cal-month-box { 38 | [class*="span"] + [class*="span"], 39 | [class*="cal-cell"] + [class*="cal-cell"] { 40 | border-left: @borderSizevert @borderStyle @borderColor; 41 | } 42 | } 43 | 44 | .cal-year-box [class*="span"] { 45 | min-height: @rowHeightYear; 46 | } 47 | 48 | .cal-year-box .row, 49 | .cal-month-box .cal-row-fluid { 50 | border-bottom: @borderSizehoriz @borderStyle @borderColor; 51 | margin-left: 0px; 52 | margin-right: 0px; 53 | } 54 | 55 | .cal-year-box, 56 | .cal-week-box { 57 | border-top: @borderSizehoriz @borderStyle @borderColor; 58 | border-bottom: @borderSizehoriz @borderStyle @borderColor; 59 | border-right: @borderSizevert @borderStyle @borderColor; 60 | border-left: @borderSizevert @borderStyle @borderColor; 61 | border-radius: 2px; 62 | } 63 | 64 | span[data-cal-date] { 65 | font-size: 1.2em; 66 | font-weight: normal; 67 | opacity: 0.5; 68 | transition: all .3s ease-in-out; 69 | -webkit-transition: all .1s ease-in-out; 70 | -moz-transition: all .1s ease-in-out; 71 | -ms-transition: all .1s ease-in-out; 72 | -o-transition: all .1s ease-in-out; 73 | margin-top: 15px; 74 | margin-right: 15px; 75 | } 76 | span[data-cal-date]:hover { 77 | opacity: 1; 78 | } 79 | 80 | .cal-day-outmonth span[data-cal-date] { 81 | opacity: 0.1; 82 | cursor: default; 83 | } 84 | 85 | .cal-day-today { 86 | background-color: #e8fde7; 87 | } 88 | 89 | .cal-day-today span[data-cal-date] { 90 | color: darkgreen; 91 | } 92 | .cal-month-box .cal-day-today span[data-cal-date] { 93 | font-size: 1.9em; 94 | } 95 | .cal-day-holiday span[data-cal-date] { 96 | color: #800080; 97 | } 98 | 99 | .cal-day-weekend span[data-cal-date] { 100 | color: darkred; 101 | } 102 | 103 | .cal-week-box-cell { 104 | border: @borderSize @borderStyle @borderColor; 105 | border-right: 0px; 106 | border-radius: 5px 0 0 5px; 107 | background-color: @rowHover; 108 | text-align: right; 109 | } 110 | 111 | .cal-day-tick { 112 | border: @borderSize @borderStyle @borderColor; 113 | border-top: 0px solid; 114 | border-radius: 0 0 5px 5px; 115 | background-color: @dayHover; 116 | text-align: center; 117 | 118 | .fa { 119 | display: none; 120 | } 121 | } 122 | 123 | .cal-day-tick { 124 | position: absolute; 125 | right: 50%; 126 | bottom: -21px; 127 | padding: 0px 5px; 128 | cursor: pointer; 129 | z-index: 5; 130 | text-align: center; 131 | width: 26px; 132 | margin-right: -17px; 133 | } 134 | 135 | .cal-slide-box { 136 | border-top: 0px solid #8c8c8c; 137 | } 138 | 139 | .cal-slide-content { 140 | padding: 20px; 141 | color: #ffffff; 142 | background-color: #555555; 143 | -webkit-box-shadow: inset 0px 0px 15px 0px rgba(0, 0, 0, 0.5); 144 | box-shadow: inset 0px 0px 15px 0px rgba(0, 0, 0, 0.5); 145 | } 146 | 147 | .cal-slide-content a.event-item { 148 | color: #ffffff; 149 | font-weight: normal; 150 | } 151 | 152 | a.event-item-edit, 153 | a.event-item-delete, 154 | a.event-item-action { 155 | padding-left: 5px; 156 | } 157 | 158 | .cal-year-box .cal-slide-content a.event-item, 159 | .cal-year-box a.event-item-edit, 160 | .cal-year-box a.event-item-delete, 161 | .cal-year-box a.event-item-action { 162 | position: relative; 163 | top: -3px; 164 | } 165 | 166 | .events-list { 167 | max-height: @rowHeightMonth - 53px; 168 | padding-left: 5px; 169 | } 170 | .cal-column { 171 | border-left: @borderSize @borderStyle @borderColor; 172 | } 173 | a.cal-event-week { 174 | text-decoration: none; 175 | color: #151515; 176 | } 177 | .badge-important { 178 | background-color: #b94a48; 179 | } 180 | 181 | .pointer { 182 | cursor: pointer; 183 | } 184 | 185 | .cal-year-box:last-child { 186 | border-bottom: 0px; 187 | } 188 | 189 | .cal-context { 190 | width: 100%; 191 | } 192 | 193 | .cal-events-num { 194 | margin-top: 20px; 195 | } 196 | 197 | @media (max-width: 991px) { 198 | 199 | .cal-year-box [class*="span"]:nth-child(2) { 200 | border-right: 0px; 201 | } 202 | 203 | .cal-year-box [class*="span"]:nth-child(1), .cal-year-box [class*="span"]:nth-child(2) { 204 | border-bottom: 1px solid #e1e1e1; 205 | } 206 | 207 | } 208 | 209 | } 210 | -------------------------------------------------------------------------------- /src/less/variables.less: -------------------------------------------------------------------------------- 1 | @rowHeightMonth: 100px; 2 | @rowHeightYear: 60px; 3 | 4 | // Events 5 | @eventStandardColor: #c3c3c3; 6 | @eventHiliteStandart: #dddddd; 7 | 8 | // MONTH 9 | @rowHover: #fafafa; 10 | @dayHover: darken(@rowHover, 5%); 11 | @borderColor: darken(@rowHover, 10%); 12 | @borderSize: 1px; 13 | @borderSizehoriz: 1px; 14 | @borderSizevert: 1px; 15 | @borderStyle: solid; 16 | 17 | @eventBorderSize: 1px; 18 | @eventBorderColor: #ffffff; 19 | @eventBorderRadius:8px; 20 | @eventMargin: 2px; 21 | @eventSize: 12px; 22 | @sideMargin: 55px; -------------------------------------------------------------------------------- /src/less/week.less: -------------------------------------------------------------------------------- 1 | .cal-week-box { 2 | position: relative; 3 | 4 | [data-event-class] { 5 | white-space: nowrap; 6 | height: 30px; 7 | line-height: 30px; 8 | text-overflow: ellipsis; 9 | overflow: hidden; 10 | padding-top: 0px !important; 11 | margin-top: 0px !important; 12 | margin-bottom: 0px !important; 13 | font-size: 12px; 14 | padding: 0 3px !important; 15 | } 16 | .cal-day-panel { 17 | border: 0px !important; 18 | } 19 | 20 | .cal-row-head { 21 | border-bottom: 1px solid #e1e1e1; 22 | } 23 | } 24 | 25 | .cal-week-box:not(.cal-day-box) { 26 | 27 | .cal-row-fluid { 28 | margin-bottom: 2px; 29 | } 30 | 31 | .cal-row-fluid:hover, 32 | [class*="cal-cell"]:hover { 33 | background-color: inherit !important; 34 | } 35 | 36 | [data-event-class] { 37 | margin-left: 2px; 38 | margin-right: 2px; 39 | } 40 | 41 | .border-left-rounded { 42 | border-top-left-radius: 5px; 43 | border-bottom-left-radius: 5px; 44 | } 45 | 46 | .border-right-rounded { 47 | border-top-right-radius: 5px; 48 | border-bottom-right-radius: 5px; 49 | } 50 | 51 | } 52 | 53 | .cal-week-box.cal-day-box { 54 | .cal-row-head { 55 | padding-left: 60px; 56 | } 57 | .cal-day-panel { 58 | overflow-x: hidden; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/services/calendarConfig.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .constant('calendarConfig', { 8 | allDateFormats: { 9 | angular: { 10 | date: { 11 | hour: 'ha', 12 | day: 'd MMM', 13 | month: 'MMMM', 14 | weekDay: 'EEEE', 15 | time: 'HH:mm', 16 | datetime: 'MMM d, h:mm a' 17 | }, 18 | title: { 19 | day: 'EEEE d MMMM, yyyy', 20 | week: 'Week {week} of {year}', 21 | month: 'MMMM yyyy', 22 | year: 'yyyy' 23 | } 24 | }, 25 | moment: { 26 | date: { 27 | hour: 'ha', 28 | day: 'D MMM', 29 | month: 'MMMM', 30 | weekDay: 'dddd', 31 | time: 'HH:mm', 32 | datetime: 'MMM D, h:mm a' 33 | }, 34 | title: { 35 | day: 'dddd D MMMM, YYYY', 36 | week: 'Week {week} of {year}', 37 | month: 'MMMM YYYY', 38 | year: 'YYYY' 39 | } 40 | } 41 | }, 42 | get dateFormats() { 43 | return this.allDateFormats[this.dateFormatter].date; 44 | }, 45 | get titleFormats() { 46 | return this.allDateFormats[this.dateFormatter].title; 47 | }, 48 | dateFormatter: 'angular', 49 | showTimesOnWeekView: false, 50 | displayAllMonthEvents: false, 51 | i18nStrings: { 52 | weekNumber: 'Week {week}' 53 | }, 54 | templates: {}, 55 | colorTypes: { 56 | info: { 57 | primary: '#1e90ff', 58 | secondary: '#d1e8ff' 59 | }, 60 | important: { 61 | primary: '#ad2121', 62 | secondary: '#fae3e3' 63 | }, 64 | warning: { 65 | primary: '#e3bc08', 66 | secondary: '#fdf1ba' 67 | }, 68 | inverse: { 69 | primary: '#1b1b1b', 70 | secondary: '#c1c1c1' 71 | }, 72 | special: { 73 | primary: '#800080', 74 | secondary: '#ffe6ff' 75 | }, 76 | success: { 77 | primary: '#006400', 78 | secondary: '#caffca' 79 | } 80 | } 81 | }); 82 | -------------------------------------------------------------------------------- /src/services/calendarEventTitle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .factory('calendarEventTitle', function(calendarDateFilter, calendarTruncateEventTitleFilter) { 8 | 9 | function yearView(event) { 10 | return event.title + ' (' + calendarDateFilter(event.startsAt, 'datetime', true) + ')'; 11 | } 12 | 13 | function monthView(event) { 14 | return event.title + ' (' + calendarDateFilter(event.startsAt, 'time', true) + ')'; 15 | } 16 | 17 | function monthViewTooltip(event) { 18 | return calendarDateFilter(event.startsAt, 'time', true) + ' - ' + event.title; 19 | } 20 | 21 | function weekView(event) { 22 | return event.title; 23 | } 24 | 25 | function weekViewTooltip(event) { 26 | return event.title; 27 | } 28 | 29 | function dayView(event) { 30 | return event.allDay ? event.title : calendarTruncateEventTitleFilter(event.title, 20, event.height); 31 | } 32 | 33 | function dayViewTooltip(event) { 34 | return event.title; 35 | } 36 | 37 | return { 38 | yearView: yearView, 39 | monthView: monthView, 40 | monthViewTooltip: monthViewTooltip, 41 | weekView: weekView, 42 | weekViewTooltip: weekViewTooltip, 43 | dayView: dayView, 44 | dayViewTooltip: dayViewTooltip 45 | }; 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /src/services/calendarTitle.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | angular 6 | .module('mwl.calendar') 7 | .factory('calendarTitle', function(moment, calendarConfig, calendarHelper) { 8 | 9 | function day(viewDate) { 10 | return calendarHelper.formatDate(viewDate, calendarConfig.titleFormats.day); 11 | } 12 | 13 | function week(viewDate) { 14 | return calendarConfig.titleFormats.week 15 | .replace('{week}', moment(viewDate).isoWeek()) 16 | .replace('{year}', moment(viewDate).startOf('isoweek').format('YYYY')); 17 | } 18 | 19 | function month(viewDate) { 20 | return calendarHelper.formatDate(viewDate, calendarConfig.titleFormats.month); 21 | } 22 | 23 | function year(viewDate) { 24 | return calendarHelper.formatDate(viewDate, calendarConfig.titleFormats.year); 25 | } 26 | 27 | return { 28 | day: day, 29 | week: week, 30 | month: month, 31 | year: year 32 | }; 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /src/services/interact.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | var interact; 5 | try { 6 | interact = require('interactjs'); 7 | } catch (e) { 8 | /* istanbul ignore next */ 9 | interact = null; 10 | } 11 | 12 | angular 13 | .module('mwl.calendar') 14 | .constant('interact', interact); 15 | -------------------------------------------------------------------------------- /src/services/moment.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | var moment = require('moment'); 5 | 6 | angular 7 | .module('mwl.calendar') 8 | .constant('moment', moment); 9 | -------------------------------------------------------------------------------- /src/templates/calendar.html: -------------------------------------------------------------------------------- 1 |
5 | 6 |
The value passed to the view attribute of the calendar is not set
7 | 8 |
The value passed to view-date attribute of the calendar is not set
9 | 10 | 23 | 24 | 25 | 41 | 42 | 43 | 60 | 61 | 62 | 85 | 86 |
87 | -------------------------------------------------------------------------------- /src/templates/calendarDayView.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
6 |
11 | 12 | 13 | 14 | - 15 | 16 | 17 | 22 | 23 |
24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 | 33 | 45 | 46 | 47 |
71 | 72 | 73 | , 74 | 75 | 76 | 80 | 81 | 82 | 83 | 90 | 91 | 92 |
93 | 94 |
95 | 96 |
97 | -------------------------------------------------------------------------------- /src/templates/calendarHourList.html: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |
19 |
20 | 21 |
22 |
23 | 24 |
28 |
29 | 30 |   31 |
32 |
46 |
47 |
48 | 49 |
50 | 51 |
52 | -------------------------------------------------------------------------------- /src/templates/calendarMonthCell.html: -------------------------------------------------------------------------------- 1 |
19 | 20 | 24 | 25 | 26 | 31 | 32 | 33 |
34 | 35 | 36 |
37 | 38 | 39 | 40 |
41 | 42 |
43 | 44 |
45 | -------------------------------------------------------------------------------- /src/templates/calendarMonthCellEvents.html: -------------------------------------------------------------------------------- 1 |
2 | 17 | 18 |
19 | -------------------------------------------------------------------------------- /src/templates/calendarMonthView.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |
9 |
10 |
17 | 18 |
19 |
20 | 21 | 29 | 30 | 31 |
32 | 33 |
34 | -------------------------------------------------------------------------------- /src/templates/calendarSlideBox.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 33 |
34 |
35 | -------------------------------------------------------------------------------- /src/templates/calendarWeekView.html: -------------------------------------------------------------------------------- 1 |
2 |
4 |
14 | 15 | 16 |
17 | 18 | 23 | 24 | 25 | 26 |
27 | 28 |
29 | 30 |
31 | 32 | 45 | 46 | 47 |
48 |
49 |
50 |
59 |
73 | 74 | 82 | 83 | 90 | 91 |
92 |
93 |
94 |
95 | 96 |
97 | 98 |
99 |
100 | -------------------------------------------------------------------------------- /src/templates/calendarYearView.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
12 | 13 | 18 | 19 | 20 | 24 | 25 | 26 |
29 | 30 | 31 |
32 | 33 |
34 |
35 | 36 | 43 | 44 | 45 |
46 | 47 |
48 | -------------------------------------------------------------------------------- /test/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "mwl/test", 3 | "rules": { 4 | "no-undefined": 0 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /test/unit/config.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('calendar config', function() { 6 | 7 | beforeEach(angular.mock.module('mwl.calendar', function($interpolateProvider) { 8 | $interpolateProvider.startSymbol('[['); 9 | $interpolateProvider.endSymbol(']]'); 10 | })); 11 | 12 | var $templateCache; 13 | 14 | beforeEach(inject(function(_$templateCache_) { 15 | $templateCache = _$templateCache_; 16 | })); 17 | 18 | it('should replace the interpolation symbol with the user configured one in templates', function() { 19 | expect($templateCache.get('mwl/calendarMonthCell.html').indexOf('class="cal-month-day [[ day.cssClass ]]"') > -1).to.be.true; 20 | }); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /test/unit/directives/mwlCalendarDay.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'), 4 | moment = require('moment'); 5 | 6 | describe('mwlCalendarDay directive', function() { 7 | var MwlCalendarCtrl, 8 | element, 9 | scope, 10 | $rootScope, 11 | directiveScope, 12 | showModal, 13 | calendarHelper, 14 | template = 15 | ''; 25 | var calendarDay = new Date(2015, 4, 1); 26 | 27 | function prepareScope(vm) { 28 | //These variables MUST be set as a minimum for the calendar to work 29 | vm.viewDate = calendarDay; 30 | vm.dayViewStart = '06:00'; 31 | vm.dayViewEnd = '22:59'; 32 | vm.dayViewsplit = 30; 33 | vm.cellModifier = sinon.spy(); 34 | vm.events = [ 35 | { 36 | calendarEventId: 0, 37 | title: 'An event', 38 | type: 'warning', 39 | startsAt: moment(calendarDay).startOf('week').subtract(2, 'days').add(8, 'hours').toDate(), 40 | endsAt: moment(calendarDay).startOf('week').add(1, 'week').add(9, 'hours').toDate(), 41 | draggable: true, 42 | resizable: true 43 | }, { 44 | calendarEventId: 1, 45 | title: ' Another event, with a html title', 46 | type: 'info', 47 | startsAt: moment(calendarDay).subtract(1, 'day').toDate(), 48 | endsAt: moment(calendarDay).add(5, 'days').toDate(), 49 | draggable: true, 50 | resizable: true 51 | }, { 52 | calendarEventId: 2, 53 | title: 'This is a really long event title that occurs on every year', 54 | type: 'important', 55 | startsAt: moment(calendarDay).startOf('day').add(7, 'hours').toDate(), 56 | endsAt: moment(calendarDay).startOf('day').add(19, 'hours').toDate(), 57 | recursOn: 'year', 58 | draggable: true, 59 | resizable: true 60 | } 61 | ]; 62 | 63 | showModal = sinon.spy(); 64 | 65 | vm.onEventClick = function(event) { 66 | showModal('Clicked', event); 67 | }; 68 | 69 | vm.onEventTimesChanged = function(event) { 70 | showModal('Dropped or resized', event); 71 | }; 72 | } 73 | 74 | beforeEach(angular.mock.module('mwl.calendar')); 75 | 76 | beforeEach(angular.mock.inject(function($compile, _$rootScope_, _calendarHelper_) { 77 | $rootScope = _$rootScope_; 78 | calendarHelper = _calendarHelper_; 79 | scope = $rootScope.$new(); 80 | prepareScope(scope); 81 | 82 | element = $compile(template)(scope); 83 | scope.$apply(); 84 | directiveScope = element.isolateScope(); 85 | MwlCalendarCtrl = directiveScope.vm; 86 | })); 87 | 88 | it('should get the new day view when calendar refreshes', function() { 89 | sinon.stub(calendarHelper, 'getDayViewHeight').returns(1000); 90 | var event1 = {event: 'event1'}; 91 | var event2 = {event: 'event2', allDay: true}; 92 | sinon.stub(calendarHelper, 'getDayView').returns({events: [{event: event1}], allDayEvents: [event2]}); 93 | scope.$broadcast('calendar.refreshView'); 94 | expect(calendarHelper.getDayViewHeight).to.have.been.calledWith('06:00', '22:59', 30); 95 | expect(MwlCalendarCtrl.dayViewHeight).to.equal(1000); 96 | expect(calendarHelper.getDayView).to.have.been.calledWith(scope.events, scope.viewDate, '06:00', '22:59', 30); 97 | expect(MwlCalendarCtrl.nonAllDayEvents).to.eql([{event: event1}]); 98 | expect(MwlCalendarCtrl.allDayEvents).to.eql([event2]); 99 | }); 100 | 101 | it('should call the callback function when you finish dragging and event', function() { 102 | MwlCalendarCtrl.eventDragComplete(scope.events[0], 1); 103 | expect(showModal).to.have.been.calledWith('Dropped or resized', { 104 | calendarEvent: scope.events[0], 105 | calendarNewEventStart: new Date(2015, 3, 24, 8, 30), 106 | calendarNewEventEnd: new Date(2015, 4, 3, 9, 30) 107 | }); 108 | }); 109 | 110 | it('should call the callback function when you finish dragging an event with no endsAt', function() { 111 | delete scope.events[0].endsAt; 112 | MwlCalendarCtrl.eventDragComplete(scope.events[0], 1); 113 | expect(showModal).to.have.been.calledWith('Dropped or resized', { 114 | calendarEvent: scope.events[0], 115 | calendarNewEventStart: new Date(2015, 3, 24, 8, 30), 116 | calendarNewEventEnd: null 117 | }); 118 | }); 119 | 120 | it('should update the temporary start position while dragging', function() { 121 | MwlCalendarCtrl.eventDragged(scope.events[0], 1); 122 | expect(scope.events[0].tempStartsAt).to.eql(new Date(2015, 3, 24, 8, 30)); 123 | }); 124 | 125 | it('should call the callback function when you finish resizing and event', function() { 126 | MwlCalendarCtrl.eventResizeComplete(scope.events[0], 'start', 1); 127 | expect(showModal).to.have.been.calledWith('Dropped or resized', { 128 | calendarEvent: scope.events[0], 129 | calendarNewEventStart: new Date(2015, 3, 24, 8, 30), 130 | calendarNewEventEnd: new Date(2015, 4, 3, 9, 0) 131 | }); 132 | 133 | MwlCalendarCtrl.eventResizeComplete(scope.events[0], 'end', 1); 134 | expect(showModal).to.have.been.calledWith('Dropped or resized', { 135 | calendarEvent: scope.events[0], 136 | calendarNewEventStart: new Date(2015, 3, 24, 8, 0), 137 | calendarNewEventEnd: new Date(2015, 4, 3, 9, 30) 138 | }); 139 | }); 140 | 141 | it('should update the temporary start position while resizing', function() { 142 | MwlCalendarCtrl.eventResized(scope.events[0], 'start', 1); 143 | expect(scope.events[0].tempStartsAt).to.eql(new Date(2015, 3, 24, 8, 30)); 144 | }); 145 | 146 | it('should update the events when the day view split changes', function() { 147 | scope.viewDate = moment(calendarDay).startOf('day').add(1, 'hour').toDate(); 148 | scope.$apply(); 149 | scope.$broadcast('calendar.refreshView'); 150 | scope.dayViewSplit = 15; 151 | scope.$apply(); 152 | expect(MwlCalendarCtrl.nonAllDayEvents[0].height).to.equal(2038); 153 | }); 154 | 155 | }); 156 | -------------------------------------------------------------------------------- /test/unit/directives/mwlCalendarSlideBox.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'), 4 | moment = require('moment'); 5 | 6 | describe('mwlCalendarSlideBox directive', function() { 7 | var element, 8 | scope, 9 | $rootScope, 10 | directiveScope, 11 | template = 12 | ''; 16 | 17 | function prepareScope(vm) { 18 | //These variables MUST be set as a minimum for the calendar to work 19 | vm.isOpen = false; 20 | vm.events = [ 21 | { 22 | calendarEventId: 0, 23 | title: 'An event', 24 | type: 'warning', 25 | startsAt: moment().startOf('week').subtract(2, 'days').add(8, 'hours').toDate(), 26 | endsAt: moment().startOf('week').add(1, 'week').add(9, 'hours').toDate(), 27 | draggable: true, 28 | resizable: true 29 | }, { 30 | calendarEventId: 1, 31 | title: ' Another event, with a html title', 32 | type: 'info', 33 | startsAt: moment().subtract(1, 'day').toDate(), 34 | endsAt: moment().add(5, 'days').toDate(), 35 | draggable: true, 36 | resizable: true 37 | }, { 38 | calendarEventId: 2, 39 | title: 'This is a really long event title that occurs on every year', 40 | type: 'important', 41 | startsAt: moment().startOf('day').add(7, 'hours').toDate(), 42 | endsAt: moment().startOf('day').add(19, 'hours').toDate(), 43 | recursOn: 'year', 44 | draggable: true, 45 | resizable: true 46 | } 47 | ]; 48 | } 49 | 50 | beforeEach(angular.mock.module('mwl.calendar')); 51 | 52 | beforeEach(angular.mock.inject(function($compile, _$rootScope_) { 53 | $rootScope = _$rootScope_; 54 | scope = $rootScope.$new(); 55 | prepareScope(scope); 56 | 57 | element = $compile(template)(scope); 58 | scope.$apply(); 59 | directiveScope = element.isolateScope(); 60 | })); 61 | 62 | it('should initialise scope properties', function() { 63 | expect(directiveScope.isMonthView).to.be.false; 64 | expect(directiveScope.isYearView).to.be.false; 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /test/unit/directives/mwlCalendarYear.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'), 4 | moment = require('moment'); 5 | 6 | describe('mwlCalendarYear directive', function() { 7 | var MwlCalendarCtrl, 8 | element, 9 | scope, 10 | $rootScope, 11 | directiveScope, 12 | showModal, 13 | calendarHelper, 14 | template = 15 | ''; 27 | var calendarDay = new Date(2015, 4, 1); 28 | 29 | function prepareScope(vm) { 30 | //These variables MUST be set as a minimum for the calendar to work 31 | vm.viewDate = calendarDay; 32 | vm.cellIsOpen = false; 33 | vm.dayViewStart = '06:00'; 34 | vm.dayViewEnd = '22:59'; 35 | vm.dayViewsplit = 30; 36 | vm.events = [ 37 | { 38 | title: 'An event', 39 | type: 'warning', 40 | startsAt: moment(calendarDay).startOf('week').subtract(2, 'days').add(8, 'hours').toDate(), 41 | endsAt: moment(calendarDay).startOf('week').add(1, 'week').add(9, 'hours').toDate(), 42 | draggable: true, 43 | resizable: true 44 | }, { 45 | title: ' Another event, with a html title', 46 | type: 'info', 47 | startsAt: moment(calendarDay).subtract(1, 'day').toDate(), 48 | endsAt: moment(calendarDay).add(5, 'days').toDate(), 49 | draggable: true, 50 | resizable: true 51 | }, { 52 | title: 'This is a really long event title that occurs on every year', 53 | type: 'important', 54 | startsAt: moment(calendarDay).startOf('day').add(7, 'hours').toDate(), 55 | endsAt: moment(calendarDay).startOf('day').add(19, 'hours').toDate(), 56 | recursOn: 'year', 57 | draggable: true, 58 | resizable: true 59 | } 60 | ]; 61 | 62 | showModal = sinon.spy(); 63 | 64 | vm.onEventClick = function(event) { 65 | showModal('Clicked', event); 66 | }; 67 | 68 | vm.onTimeSpanClick = function(event) { 69 | showModal('Day clicked', event); 70 | }; 71 | 72 | vm.onEventTimesChanged = function(event) { 73 | showModal('Dropped or resized', event); 74 | }; 75 | } 76 | 77 | beforeEach(angular.mock.module('mwl.calendar')); 78 | 79 | beforeEach(angular.mock.inject(function($compile, _$rootScope_, _calendarHelper_) { 80 | $rootScope = _$rootScope_; 81 | calendarHelper = _calendarHelper_; 82 | scope = $rootScope.$new(); 83 | prepareScope(scope); 84 | element = angular.element(template); 85 | element.data('$mwlCalendarController', {}); 86 | element = $compile(element)(scope); 87 | scope.$apply(); 88 | directiveScope = element.isolateScope(); 89 | MwlCalendarCtrl = directiveScope.vm; 90 | })); 91 | 92 | it('should get the new year view when calendar refreshes and show the list of events for the current month if required', function() { 93 | var yearView = [{date: moment(calendarDay), inMonth: true}]; 94 | sinon.stub(calendarHelper, 'getYearView').returns(yearView); 95 | scope.$broadcast('calendar.refreshView'); 96 | expect(calendarHelper.getYearView).to.have.been.calledWith(scope.events, scope.viewDate); 97 | expect(MwlCalendarCtrl.view).to.equal(yearView); 98 | }); 99 | 100 | it('should toggle the event list for the selected month ', function() { 101 | MwlCalendarCtrl.view = [{date: moment(calendarDay), inMonth: true}]; 102 | //Open event list 103 | MwlCalendarCtrl.cellIsOpen = true; 104 | scope.$apply(); 105 | expect(MwlCalendarCtrl.openRowIndex).to.equal(0); 106 | expect(MwlCalendarCtrl.openMonthIndex).to.equal(0); 107 | 108 | //Close event list 109 | MwlCalendarCtrl.cellIsOpen = false; 110 | scope.$apply(); 111 | expect(MwlCalendarCtrl.openRowIndex).to.equal(null); 112 | expect(MwlCalendarCtrl.openMonthIndex).to.equal(null); 113 | }); 114 | 115 | it('should call the on time span clicked callback', function() { 116 | scope.onTimeSpanClick = sinon.spy(); 117 | scope.$apply(); 118 | MwlCalendarCtrl.view = [{date: moment(calendarDay), inMonth: true}]; 119 | MwlCalendarCtrl.monthClicked(MwlCalendarCtrl.view[0], false); 120 | expect(scope.onTimeSpanClick).to.have.been.calledWith({ 121 | calendarDate: MwlCalendarCtrl.view[0].date.toDate(), 122 | calendarCell: MwlCalendarCtrl.view[0], 123 | $event: undefined 124 | }); 125 | }); 126 | 127 | it('should call the callback function when you finish dropping an event', function() { 128 | MwlCalendarCtrl.handleEventDrop(scope.events[0], new Date(2015, 11, 1)); 129 | expect(showModal).to.have.been.calledWith('Dropped or resized', { 130 | calendarEvent: scope.events[0], 131 | calendarDate: new Date(2015, 11, 1), 132 | calendarNewEventStart: new Date(2015, 11, 24, 8, 0), 133 | calendarNewEventEnd: new Date(2016, 0, 2, 9, 0) 134 | }); 135 | }); 136 | 137 | it('should call the callback function when you finish dropping an event with no end date', function() { 138 | delete scope.events[0].endsAt; 139 | MwlCalendarCtrl.handleEventDrop(scope.events[0], new Date(2015, 11, 1)); 140 | expect(showModal).to.have.been.calledWith('Dropped or resized', { 141 | calendarEvent: scope.events[0], 142 | calendarDate: new Date(2015, 11, 1), 143 | calendarNewEventStart: new Date(2015, 11, 24, 8, 0), 144 | calendarNewEventEnd: null 145 | }); 146 | }); 147 | 148 | }); 149 | -------------------------------------------------------------------------------- /test/unit/directives/mwlCollapseFallback.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('mwlCollapseFallback directive', function() { 6 | var element, 7 | scope, 8 | $rootScope, 9 | template = '
'; 10 | 11 | function prepareScope(vm) { 12 | //These variables MUST be set as a minimum for the calendar to work 13 | vm.isCollapsed = true; 14 | } 15 | 16 | beforeEach(angular.mock.module('mwl.calendar')); 17 | 18 | describe('when acting as a fallback', function() { 19 | beforeEach(angular.mock.inject(function($compile, _$rootScope_) { 20 | $rootScope = _$rootScope_; 21 | scope = $rootScope.$new(); 22 | prepareScope(scope); 23 | element = $compile(template)(scope); 24 | scope.$apply(); 25 | })); 26 | 27 | it('should initialise toggle the ng-hide class', function() { 28 | expect(element.hasClass('ng-hide')).to.be.true; 29 | scope.isCollapsed = false; 30 | scope.$apply(); 31 | expect(element.hasClass('ng-hide')).to.be.false; 32 | }); 33 | }); 34 | 35 | describe('when it is not required', function() { 36 | beforeEach(angular.mock.module(function($provide) { 37 | $provide.value('uibCollapseDirective', {}); 38 | })); 39 | 40 | beforeEach(angular.mock.inject(function($compile, _$rootScope_) { 41 | $rootScope = _$rootScope_; 42 | scope = $rootScope.$new(); 43 | sinon.spy(scope, '$watch'); 44 | prepareScope(scope); 45 | element = $compile(template)(scope); 46 | scope.$apply(); 47 | })); 48 | 49 | it('should not set any watches', function() { 50 | expect(scope.$watch).not.to.have.been.called; 51 | }); 52 | }); 53 | 54 | }); 55 | -------------------------------------------------------------------------------- /test/unit/directives/mwlDateModifier.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('mwlDateModifier directive', function() { 6 | var element, 7 | scope, 8 | $rootScope, 9 | $compile, 10 | template = 11 | ''; 19 | 20 | function prepareScope(vm) { 21 | //These variables MUST be set as a minimum for the calendar to work 22 | vm.date = new Date(2015, 0, 5); 23 | vm.increment = 'days'; 24 | vm.decrement = 'months'; 25 | } 26 | 27 | beforeEach(angular.mock.module('mwl.calendar')); 28 | 29 | beforeEach(angular.mock.inject(function(_$compile_, _$rootScope_) { 30 | $compile = _$compile_; 31 | $rootScope = _$rootScope_; 32 | scope = $rootScope.$new(); 33 | prepareScope(scope); 34 | })); 35 | 36 | it('should increment the date by one unit of the provided attribute value', function() { 37 | element = angular.element(template).removeAttr('set-to-today'); 38 | element = $compile(element)(scope); 39 | scope.$apply(); 40 | element.triggerHandler('click'); 41 | expect(scope.date).to.eql(new Date(2015, 0, 6)); 42 | }); 43 | 44 | it('should decrement the date by one unit of the provided attribute value', function() { 45 | element = angular.element(template).removeAttr('set-to-today').removeAttr('increment'); 46 | element = $compile(element)(scope); 47 | scope.$apply(); 48 | element.triggerHandler('click'); 49 | expect(scope.date).to.eql(new Date(2014, 11, 5)); 50 | }); 51 | 52 | it('should set the date to today', function() { 53 | element = $compile(template)(scope); 54 | scope.$apply(); 55 | element.triggerHandler('click'); 56 | expect(scope.date.toString()).to.equal((new Date()).toString()); 57 | }); 58 | 59 | it('should skip the excluded days when incrementing days', function() { 60 | element = angular.element(template).removeAttr('set-to-today'); 61 | scope.date = new Date(2015, 0, 2); 62 | scope.excludedDays = [0, 6]; 63 | element = $compile(element)(scope); 64 | scope.$apply(); 65 | element.triggerHandler('click'); 66 | expect(scope.date.toString()).to.equal((new Date(2015, 0, 5)).toString()); 67 | }); 68 | 69 | it('should skip the excluded days when decrementing days', function() { 70 | element = angular.element(template).removeAttr('set-to-today').removeAttr('increment'); 71 | scope.excludedDays = [0, 6]; 72 | scope.decrement = 'days'; 73 | element = $compile(element)(scope); 74 | scope.$apply(); 75 | element.triggerHandler('click'); 76 | expect(scope.date.toString()).to.equal((new Date(2015, 0, 2)).toString()); 77 | }); 78 | 79 | }); 80 | -------------------------------------------------------------------------------- /test/unit/directives/mwlDragSelect.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('mwlDragSelect directive', function() { 6 | 7 | beforeEach(angular.mock.module('mwl.calendar')); 8 | 9 | var elm, scope; 10 | beforeEach(angular.mock.inject(function($compile, $rootScope, $document) { 11 | scope = $rootScope.$new(); 12 | scope.onStart = sinon.spy(); 13 | scope.onMove = sinon.spy(); 14 | scope.onEnd = sinon.spy(); 15 | elm = $compile( 16 | '
' 18 | )(scope); 19 | $document.find('body').append(elm); 20 | scope.$apply(); 21 | })); 22 | 23 | afterEach(function() { 24 | scope.$destroy(); 25 | elm.remove(); 26 | }); 27 | 28 | function triggerEvent(type) { 29 | /* global document */ 30 | var event = document.createEvent('Event'); 31 | event.initEvent(type, true, true); 32 | elm[0].dispatchEvent(event); 33 | } 34 | 35 | describe('isEnabled = true', function() { 36 | 37 | beforeEach(function() { 38 | scope.isEnabled = true; 39 | scope.$apply(); 40 | }); 41 | 42 | it('should call the mousedown callback when the drag select', function() { 43 | triggerEvent('mousedown'); 44 | expect(scope.onStart).to.have.been.calledOnce; 45 | }); 46 | 47 | it('should call the mousemove callback when the drag select', function() { 48 | triggerEvent('mousemove'); 49 | expect(scope.onMove).to.have.been.calledOnce; 50 | }); 51 | 52 | it('should call the mouseup callback when the drag select', function() { 53 | triggerEvent('mouseup'); 54 | expect(scope.onEnd).to.have.been.calledOnce; 55 | }); 56 | 57 | it('should not fire drag events when the right mouse button is pressed', function() { 58 | var event = document.createEvent('Event'); 59 | event.initEvent('mousedown', true, true); 60 | event.button = 2; 61 | elm[0].dispatchEvent(event); 62 | 63 | expect(scope.onStart).not.to.have.been.called; 64 | }); 65 | 66 | }); 67 | 68 | describe('isEnabled = false', function() { 69 | 70 | beforeEach(function() { 71 | scope.isEnabled = false; 72 | scope.$apply(); 73 | }); 74 | 75 | it('should not call the mousedown callback when the drag select', function() { 76 | triggerEvent('mousedown'); 77 | expect(scope.onStart).not.to.have.been.called; 78 | }); 79 | 80 | it('should not call the mousemove callback when the drag select', function() { 81 | triggerEvent('mousemove'); 82 | expect(scope.onMove).not.to.have.been.called; 83 | }); 84 | 85 | it('should not call the mouseup callback when the drag select', function() { 86 | triggerEvent('mouseup'); 87 | expect(scope.onEnd).not.to.have.been.called; 88 | }); 89 | 90 | }); 91 | 92 | }); 93 | -------------------------------------------------------------------------------- /test/unit/directives/mwlDraggable.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('mwlDraggable directive', function() { 6 | var element, 7 | scope, 8 | $rootScope, 9 | interact, 10 | interactInstance, 11 | draggableOptions, 12 | elementTarget, 13 | $window, 14 | $compile, 15 | $timeout, 16 | template = 17 | '
'; 26 | 27 | function prepareScope(vm) { 28 | //These variables MUST be set as a minimum for the calendar to work 29 | vm.draggable = true; 30 | vm.dropData = 'myData'; 31 | vm.onDragStart = sinon.spy(); 32 | vm.onDrag = sinon.spy(); 33 | vm.onDragEnd = sinon.spy(); 34 | } 35 | 36 | beforeEach(angular.mock.module('mwl.calendar')); 37 | 38 | beforeEach(angular.mock.module(function($provide) { 39 | interact = sinon.stub(); 40 | interact.createSnapGrid = sinon.spy(); 41 | $provide.constant('interact', interact); 42 | })); 43 | 44 | beforeEach(angular.mock.inject(function(_$compile_, _$rootScope_, _$window_, _$timeout_) { 45 | $window = _$window_; 46 | $compile = _$compile_; 47 | $rootScope = _$rootScope_; 48 | $timeout = _$timeout_; 49 | scope = $rootScope.$new(); 50 | prepareScope(scope); 51 | 52 | interactInstance = { 53 | draggable: sinon.spy(), 54 | unset: sinon.spy() 55 | }; 56 | interact.returns(interactInstance); 57 | element = $compile(template)(scope); 58 | draggableOptions = interactInstance.draggable.args[0][0]; 59 | 60 | elementTarget = angular.element('
'); 61 | angular.element($window.document.body).append(elementTarget); 62 | })); 63 | 64 | afterEach(function() { 65 | elementTarget.remove(); 66 | }); 67 | 68 | it('should initialise interact', function() { 69 | expect(interact.createSnapGrid).to.have.been.calledWith({x: 30, y: 30}); 70 | expect(interact).to.have.been.calledWith(element[0]); 71 | }); 72 | 73 | it('should handle on drag start', function() { 74 | var event = { 75 | target: elementTarget[0] 76 | }; 77 | 78 | draggableOptions.onstart(event); 79 | expect(angular.element(event.target).hasClass('dragging-active')).to.be.true; 80 | expect(angular.element(event.target).css('pointerEvents')).to.equal('none'); 81 | expect(scope.onDragStart).to.have.been.called; 82 | }); 83 | 84 | it('should handle on drag move', function() { 85 | var event = { 86 | target: elementTarget[0], 87 | dx: 0, 88 | dy: 30 89 | }; 90 | 91 | draggableOptions.onstart(event); 92 | draggableOptions.onmove(event); 93 | expect(angular.element(event.target).css('z-index')).to.equal('50'); 94 | expect(angular.element(event.target).attr('data-y')).to.equal('30'); 95 | expect(angular.element(event.target).attr('data-x')).to.equal('0'); 96 | expect(scope.onDrag).to.have.been.calledWith(0, 1); 97 | }); 98 | 99 | it('should handle on drag end', function() { 100 | var event = { 101 | target: elementTarget[0], 102 | dx: 0, 103 | dy: 180 104 | }; 105 | 106 | draggableOptions.onstart(event); 107 | draggableOptions.onmove(event); 108 | draggableOptions.onend(event); 109 | $timeout.flush(); 110 | expect(angular.element(event.target).hasClass('dragging-active')).to.be.false; 111 | expect(angular.element(event.target).css('pointerEvents')).to.equal('auto'); 112 | expect(angular.element(event.target).css('transform')).to.equal(''); 113 | expect(angular.element(event.target).css('-webkit-transform')).to.equal(''); 114 | expect(angular.element(event.target).css('-ms-transform')).to.equal(''); 115 | expect(angular.element(event.target).css('z-index')).to.equal('auto'); 116 | expect(scope.onDragEnd).to.have.been.calledWith(0, 6); 117 | }); 118 | 119 | it('should unset interact when scope gets destroyed', function() { 120 | scope.$destroy(); 121 | expect(interactInstance.unset).to.have.been.called; 122 | }); 123 | 124 | it('should disable dragging on the event', function() { 125 | scope.draggable = false; 126 | scope.$apply(); 127 | expect(interactInstance.draggable).to.have.been.calledWith({enabled: false}); 128 | }); 129 | 130 | }); 131 | -------------------------------------------------------------------------------- /test/unit/directives/mwlDroppable.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('mwlDroppable directive', function() { 6 | var element, 7 | scope, 8 | $rootScope, 9 | interact, 10 | interactInstance, 11 | dropzoneOptions, 12 | $compile, 13 | template = '
'; 14 | 15 | function prepareScope(vm) { 16 | //These variables MUST be set as a minimum for the calendar to work 17 | vm.dropData = 'myData'; 18 | vm.onDrop = sinon.spy(); 19 | } 20 | 21 | beforeEach(angular.mock.module('mwl.calendar')); 22 | 23 | beforeEach(angular.mock.module(function($provide) { 24 | interact = sinon.stub(); 25 | $provide.constant('interact', interact); 26 | })); 27 | 28 | beforeEach(angular.mock.inject(function(_$compile_, _$rootScope_) { 29 | $compile = _$compile_; 30 | $rootScope = _$rootScope_; 31 | scope = $rootScope.$new(); 32 | prepareScope(scope); 33 | 34 | interactInstance = { 35 | dropzone: sinon.spy(), 36 | unset: sinon.spy() 37 | }; 38 | interact.returns(interactInstance); 39 | element = $compile(template)(scope); 40 | dropzoneOptions = interactInstance.dropzone.args[0][0]; 41 | })); 42 | 43 | it('should initialise interact', function() { 44 | expect(interact).to.have.been.calledWith(element[0]); 45 | }); 46 | 47 | it('should handle on drag enter', function() { 48 | var event = { 49 | target: angular.element('
')[0], 50 | relatedTarget: {dropData: scope.dropData} 51 | }; 52 | 53 | dropzoneOptions.ondragenter(event); 54 | expect(angular.element(event.target).hasClass('drop-active')).to.be.true; 55 | }); 56 | 57 | it('should handle on drag leave', function() { 58 | var event = { 59 | target: angular.element('
')[0], 60 | relatedTarget: {dropData: scope.dropData} 61 | }; 62 | 63 | dropzoneOptions.ondragenter(event); 64 | dropzoneOptions.ondragleave(event); 65 | expect(angular.element(event.target).hasClass('drop-active')).to.be.false; 66 | }); 67 | 68 | it('should handle on drop', function() { 69 | var event = { 70 | target: angular.element('
')[0], 71 | relatedTarget: {dropData: scope.dropData} 72 | }; 73 | 74 | dropzoneOptions.ondrop(event); 75 | expect(scope.onDrop).to.have.been.calledWith('myData'); 76 | }); 77 | 78 | it('should handle on drop deactivate', function() { 79 | var event = { 80 | target: angular.element('
')[0], 81 | relatedTarget: {dropData: scope.dropData} 82 | }; 83 | 84 | dropzoneOptions.ondragenter(event); 85 | dropzoneOptions.ondropdeactivate(event); 86 | expect(angular.element(event.target).hasClass('drop-active')).to.be.false; 87 | }); 88 | 89 | it('should unset interact when scope gets destroyed', function() { 90 | scope.$destroy(); 91 | expect(interactInstance.unset).to.have.been.called; 92 | }); 93 | 94 | }); 95 | -------------------------------------------------------------------------------- /test/unit/directives/mwlDynamicDirectiveTemplate.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('dynamicDirectiveTemplate directive', function() { 6 | 7 | beforeEach(angular.mock.module('mwl.calendar')); 8 | 9 | var scope, elm, calendarConfig, $templateCache, $log; 10 | beforeEach(inject(function($rootScope, $compile, _$templateCache_, _calendarConfig_, _$log_) { 11 | $templateCache = _$templateCache_; 12 | calendarConfig = _calendarConfig_; 13 | $log = _$log_; 14 | scope = $rootScope.$new(); 15 | calendarConfig.templates = { 16 | foo: 'foo.html' 17 | }; 18 | scope.baz = 'baz'; 19 | $templateCache.put('foo.html', 'foo {{ baz }}'); 20 | elm = $compile('
')(scope); 21 | scope.$apply(); 22 | })); 23 | 24 | afterEach(function() { 25 | elm.remove(); 26 | scope.$destroy(); 27 | }); 28 | 29 | it('should fallback to the default template if no custom templates are set', function() { 30 | expect(elm.text()).to.equal('foo baz'); 31 | }); 32 | 33 | it('should fallback to the default template if the custom template name doesnt exist in the cache', function() { 34 | scope.overrides = { 35 | foo: 'bam.html' 36 | }; 37 | scope.$apply(); 38 | expect(elm.text()).to.equal('foo baz'); 39 | }); 40 | 41 | it('should use the custom template', function() { 42 | $templateCache.put('bar.html', 'bar {{ baz }}'); 43 | scope.overrides = { 44 | foo: 'bar.html' 45 | }; 46 | scope.$apply(); 47 | expect(elm.text()).to.equal('bar baz'); 48 | }); 49 | 50 | it('should log a warning when the template is not in the cache', function() { 51 | $log.warn = sinon.spy(); 52 | scope.overrides = { 53 | foo: 'bam.html' 54 | }; 55 | scope.$apply(); 56 | expect($log.warn).to.have.been.calledOnce; 57 | }); 58 | 59 | }); 60 | -------------------------------------------------------------------------------- /test/unit/directives/mwlElementDimensions.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('mwlElementDimensions directive', function() { 6 | var element, 7 | scope, 8 | $rootScope, 9 | $window, 10 | template = '
'; 11 | 12 | function prepareScope(vm) { 13 | //These variables MUST be set as a minimum for the calendar to work 14 | vm.elementDimensions = {}; 15 | } 16 | 17 | beforeEach(angular.mock.module('mwl.calendar')); 18 | 19 | beforeEach(angular.mock.inject(function($compile, _$rootScope_, _$window_) { 20 | $window = _$window_; 21 | $rootScope = _$rootScope_; 22 | scope = $rootScope.$new(); 23 | prepareScope(scope); 24 | element = angular.element(template); 25 | angular.element($window.document.body).append(element); 26 | element = $compile(element)(scope); 27 | scope.$apply(); 28 | })); 29 | 30 | afterEach(function() { 31 | element.remove(); 32 | }); 33 | 34 | it('should initialise scope properties', function() { 35 | expect(scope.elementDimensions).to.eql({width: 99, height: 50}); 36 | }); 37 | 38 | it('should update the element dimensions when the window is resized', function() { 39 | element[0].style.width = '150px'; 40 | element[0].style.height = '20px'; 41 | $window.dispatchEvent(new $window.Event('resize')); 42 | expect(scope.elementDimensions).to.eql({width: 149, height: 20}); 43 | }); 44 | 45 | }); 46 | -------------------------------------------------------------------------------- /test/unit/directives/mwlResizable.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | describe('mwlresizable directive', function() { 6 | var element, 7 | scope, 8 | $rootScope, 9 | interact, 10 | interactInstance, 11 | resizableOptions, 12 | $compile, 13 | $timeout, 14 | template = 15 | '
'; 23 | 24 | function prepareScope(vm) { 25 | //These variables MUST be set as a minimum for the calendar to work 26 | vm.resizable = true; 27 | vm.dropData = 'myData'; 28 | vm.onResizeStart = sinon.spy(); 29 | vm.onResize = sinon.spy(); 30 | vm.onResizeEnd = sinon.spy(); 31 | } 32 | 33 | beforeEach(angular.mock.module('mwl.calendar')); 34 | 35 | beforeEach(angular.mock.module(function($provide) { 36 | interact = sinon.stub(); 37 | interact.createSnapGrid = sinon.spy(); 38 | $provide.constant('interact', interact); 39 | })); 40 | 41 | beforeEach(angular.mock.inject(function(_$compile_, _$rootScope_, _$timeout_) { 42 | $compile = _$compile_; 43 | $rootScope = _$rootScope_; 44 | $timeout = _$timeout_; 45 | scope = $rootScope.$new(); 46 | prepareScope(scope); 47 | 48 | interactInstance = { 49 | resizable: sinon.spy(), 50 | unset: sinon.spy() 51 | }; 52 | interact.returns(interactInstance); 53 | element = $compile(template)(scope); 54 | resizableOptions = interactInstance.resizable.args[0][0]; 55 | })); 56 | 57 | it('should initialise interact', function() { 58 | expect(interact.createSnapGrid).to.have.been.calledWith({y: 30}); 59 | expect(interact).to.have.been.calledWith(element[0]); 60 | }); 61 | 62 | it('should handle on resize start', function() { 63 | var event = { 64 | target: angular.element('
')[0] 65 | }; 66 | 67 | resizableOptions.onstart(event); 68 | }); 69 | 70 | it('should handle on resize move', function() { 71 | var event = { 72 | target: angular.element('
')[0], 73 | rect: { 74 | width: 100, 75 | height: 50 76 | }, 77 | deltaRect: { 78 | left: 0, 79 | top: -30 80 | } 81 | }; 82 | 83 | resizableOptions.onstart(event); 84 | resizableOptions.onmove(event); 85 | expect(angular.element(event.target).data('y')).to.equal(-30); 86 | expect(angular.element(event.target).data('x')).to.equal(0); 87 | expect(scope.onResize).to.have.been.calledWith(0, -1); 88 | 89 | event = { 90 | target: angular.element('
')[0], 91 | rect: { 92 | width: 100, 93 | height: 50 94 | }, 95 | deltaRect: { 96 | left: 0, 97 | top: 60 98 | } 99 | }; 100 | 101 | scope.onResize.reset(); 102 | resizableOptions.onmove(event); 103 | expect(angular.element(event.target).data('y')).to.equal(60); 104 | expect(angular.element(event.target).data('x')).to.equal(0); 105 | expect(scope.onResize).to.have.been.calledWith(0, 2); 106 | }); 107 | 108 | it('should not resize the element to height 0', function() { 109 | 110 | var event = { 111 | target: angular.element('
')[0], 112 | rect: { 113 | width: 100, 114 | height: 0 115 | }, 116 | deltaRect: { 117 | left: 0, 118 | top: -30 119 | } 120 | }; 121 | resizableOptions.onmove(event); 122 | expect(angular.element(event.target).data('x')).to.be.undefined; 123 | expect(angular.element(event.target).data('y')).to.be.undefined; 124 | expect(angular.element(event.target).css('height')).to.equal(''); 125 | expect(angular.element(event.target).css('width')).to.equal(''); 126 | expect(angular.element(event.target).css('transform')).to.be.undefined; 127 | expect(scope.onResize).not.to.have.been.called; 128 | 129 | }); 130 | 131 | it('should not resize the element to width 0', function() { 132 | 133 | var event = { 134 | target: angular.element('
')[0], 135 | rect: { 136 | width: 0, 137 | height: 100 138 | }, 139 | deltaRect: { 140 | left: 0, 141 | top: -30 142 | } 143 | }; 144 | resizableOptions.onmove(event); 145 | expect(angular.element(event.target).data('x')).to.be.undefined; 146 | expect(angular.element(event.target).data('y')).to.be.undefined; 147 | expect(angular.element(event.target).css('height')).to.equal(''); 148 | expect(angular.element(event.target).css('width')).to.equal(''); 149 | expect(angular.element(event.target).css('transform')).to.be.undefined; 150 | expect(scope.onResize).not.to.have.been.called; 151 | 152 | }); 153 | 154 | it('should handle on resize end', function() { 155 | var event = { 156 | target: angular.element('
')[0], 157 | rect: { 158 | width: 1, 159 | height: 120 160 | }, 161 | deltaRect: { 162 | left: 0, 163 | top: 0 164 | } 165 | }; 166 | 167 | resizableOptions.onstart(event); 168 | resizableOptions.onmove(event); 169 | resizableOptions.onend(event); 170 | $timeout.flush(); 171 | expect(scope.onResizeEnd).to.have.been.calledWith(1, 4); 172 | expect(angular.element(event.target).css('transform')).to.eql(''); 173 | expect(angular.element(event.target).css('width')).to.eql('30px'); 174 | expect(angular.element(event.target).css('height')).to.eql('30px'); 175 | }); 176 | 177 | it('should unset interact when scope gets destroyed', function() { 178 | scope.$destroy(); 179 | expect(interactInstance.unset).to.have.been.called; 180 | }); 181 | 182 | it('should disable resizing on the event', function() { 183 | scope.resizable = false; 184 | scope.$apply(); 185 | expect(interactInstance.resizable).to.have.been.calledWith({enabled: false}); 186 | }); 187 | 188 | }); 189 | -------------------------------------------------------------------------------- /test/unit/entry.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | require('angular'); 3 | require('angular-mocks'); 4 | require('../../src/entry'); 5 | 6 | var testsContext = require.context('.', true, /\.spec/); 7 | testsContext.keys().forEach(testsContext); 8 | -------------------------------------------------------------------------------- /test/unit/filters/calendarDate.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | beforeEach(angular.mock.module('mwl.calendar')); 6 | 7 | describe('calendarDateFilter', function() { 8 | 9 | var dateFilter, calendarHelper; 10 | 11 | beforeEach(inject(function($filter, _calendarHelper_) { 12 | dateFilter = $filter('calendarDate'); 13 | calendarHelper = _calendarHelper_; 14 | })); 15 | 16 | it('should be marked as stateful', function() { 17 | expect(dateFilter.$stateful).to.be.true; 18 | }); 19 | 20 | it('should should call calendarHelper.formatDate with the given date and format', function() { 21 | var spy = sinon.spy(calendarHelper, 'formatDate'); 22 | var date = new Date(); 23 | dateFilter(date, 'yyyy'); 24 | expect(spy).to.have.been.calledWith(date, 'yyyy'); 25 | }); 26 | 27 | it('should should use the prdefined format from calendarConfig.dateFormats if getFromConfig is passed', function() { 28 | var spy = sinon.spy(calendarHelper, 'formatDate'); 29 | var date = new Date(); 30 | dateFilter(date, 'hour', true); 31 | expect(spy).to.have.been.calledWith(date, 'ha'); 32 | }); 33 | 34 | }); 35 | -------------------------------------------------------------------------------- /test/unit/filters/calendarTruncateEventTitle.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | beforeEach(angular.mock.module('mwl.calendar')); 5 | 6 | describe('calendarTruncateEventTitleFilter', function() { 7 | 8 | var eventTitleFilter; 9 | 10 | beforeEach(inject(function(_calendarTruncateEventTitleFilter_) { 11 | eventTitleFilter = _calendarTruncateEventTitleFilter_; 12 | })); 13 | 14 | it('should return an empty string when passed a false value', function() { 15 | expect(eventTitleFilter(null)).to.equal(''); 16 | }); 17 | 18 | it('should return the original string', function() { 19 | expect(eventTitleFilter('test', 10, 100)).to.equal('test'); 20 | }); 21 | 22 | it('should return a truncated string for the first 5 characters', function() { 23 | expect(eventTitleFilter('A really long string', 5, 10)).to.equal('A rea...'); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /test/unit/filters/calendarTrustAsHtml.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | 5 | beforeEach(angular.mock.module('mwl.calendar')); 6 | 7 | describe('calendarTrustAsHtml', function() { 8 | 9 | var calendarTrustAsHtml, $sce; 10 | 11 | beforeEach(inject(function($filter, _$sce_) { 12 | calendarTrustAsHtml = $filter('calendarTrustAsHtml'); 13 | $sce = _$sce_; 14 | })); 15 | 16 | it('should mark the text as html', function() { 17 | sinon.spy($sce, 'trustAsHtml'); 18 | var htmlString = 'text'; 19 | calendarTrustAsHtml(htmlString); 20 | expect($sce.trustAsHtml).to.have.been.calledWith(htmlString); 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /test/unit/services/calendarEventTitle.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | beforeEach(angular.mock.module('mwl.calendar')); 5 | 6 | describe('calendarEventTitle', function() { 7 | 8 | var calendarEventTitle; 9 | beforeEach(inject(function(_calendarEventTitle_) { 10 | calendarEventTitle = _calendarEventTitle_; 11 | })); 12 | 13 | it('should get the year view title', function() { 14 | expect(calendarEventTitle.yearView({ 15 | title: 'Event name', 16 | startsAt: new Date('October 20, 2015 02:00:00') 17 | })).to.equal('Event name (Oct 20, 2:00 AM)'); 18 | }); 19 | 20 | it('should get the month view title', function() { 21 | expect(calendarEventTitle.monthView({ 22 | title: 'Event name', 23 | startsAt: new Date('October 20, 2015 02:00:00') 24 | })).to.equal('Event name (02:00)'); 25 | }); 26 | 27 | it('should get the month view tooltip', function() { 28 | expect(calendarEventTitle.monthViewTooltip({ 29 | title: 'Event name', 30 | startsAt: new Date('October 20, 2015 02:00:00') 31 | })).to.equal('02:00 - Event name'); 32 | }); 33 | 34 | it('should get the week view title', function() { 35 | expect(calendarEventTitle.weekView({ 36 | title: 'Event name', 37 | startsAt: new Date('October 20, 2015 02:00:00') 38 | })).to.equal('Event name'); 39 | }); 40 | 41 | it('should get the week view tooltip', function() { 42 | expect(calendarEventTitle.weekViewTooltip({ 43 | title: 'Event name', 44 | startsAt: new Date('October 20, 2015 02:00:00') 45 | })).to.equal('Event name'); 46 | }); 47 | 48 | it('should get the day view title', function() { 49 | expect(calendarEventTitle.dayView({ 50 | title: 'A really long event title that gets truncated', 51 | startsAt: new Date('October 20, 2015 02:00:00'), 52 | height: 10 53 | })).to.equal('A really long event ...'); 54 | }); 55 | 56 | it('should get the day view title for an all day event', function() { 57 | expect(calendarEventTitle.dayView({ 58 | title: 'A really long event title thats not truncated', 59 | startsAt: new Date('October 20, 2015 02:00:00'), 60 | height: 10, 61 | allDay: true 62 | })).to.equal('A really long event title thats not truncated'); 63 | }); 64 | 65 | }); 66 | -------------------------------------------------------------------------------- /test/unit/services/calendarTitle.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | beforeEach(angular.mock.module('mwl.calendar')); 5 | 6 | describe('calendarTitle', function() { 7 | 8 | var calendarTitle; 9 | var calendarDay = new Date(2015, 4, 1); 10 | 11 | beforeEach(inject(function(_calendarTitle_) { 12 | calendarTitle = _calendarTitle_; 13 | })); 14 | 15 | it('should give the correct day title', function() { 16 | expect(calendarTitle.day(calendarDay)).to.equal('Friday 1 May, 2015'); 17 | }); 18 | 19 | it('should give the correct week title', function() { 20 | expect(calendarTitle.week(calendarDay)).to.equal('Week 18 of 2015'); 21 | }); 22 | 23 | it('should use the start of the week for the year number', function() { 24 | expect(calendarTitle.week(new Date('2016-01-01'))).to.equal('Week 53 of 2015'); 25 | }); 26 | 27 | it('should use the start of the iso week for the year number', function() { 28 | expect(calendarTitle.week(new Date('2018-01-01'))).to.equal('Week 1 of 2018'); 29 | }); 30 | 31 | it('should give the correct month title', function() { 32 | expect(calendarTitle.month(calendarDay)).to.equal('May 2015'); 33 | }); 34 | 35 | it('should give the correct year title', function() { 36 | expect(calendarTitle.year(calendarDay)).to.equal('2015'); 37 | }); 38 | 39 | }); 40 | -------------------------------------------------------------------------------- /test/unit/services/interact.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | var interactLib = require('interactjs'); 5 | beforeEach(angular.mock.module('mwl.calendar')); 6 | 7 | describe('interactjs', function() { 8 | 9 | describe('library exists', function() { 10 | it('should be the interactjs library', inject(function(interact) { 11 | 12 | expect(interact).to.eql(interactLib); 13 | 14 | })); 15 | }); 16 | 17 | }); 18 | -------------------------------------------------------------------------------- /test/unit/services/moment.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var angular = require('angular'); 4 | var momentLib = require('moment'); 5 | beforeEach(angular.mock.module('mwl.calendar')); 6 | 7 | describe('moment', function() { 8 | 9 | it('should be the window moment object', inject(function(moment) { 10 | 11 | expect(moment).to.eql(momentLib); 12 | 13 | })); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /webpack.config.build.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const webpack = require('webpack'); 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin'); 5 | 6 | module.exports = env => { 7 | 8 | env = env || {}; 9 | 10 | const MIN = process.argv.indexOf('-p') > -1; 11 | let cssFilename, jsFilename; 12 | jsFilename = cssFilename = 'angular-bootstrap-calendar'; 13 | if (!env.excludeTemplates) { 14 | jsFilename += '-tpls'; 15 | } 16 | if (MIN) { 17 | jsFilename += '.min'; 18 | cssFilename += '.min'; 19 | } 20 | jsFilename += '.js'; 21 | cssFilename += '.css'; 22 | 23 | function getBanner() { 24 | const pkg = require('./package.json'); 25 | return ` 26 | /** 27 | * ${pkg.name} - ${pkg.description} 28 | * @version v${pkg.version} 29 | * @link ${pkg.homepage} 30 | * @license ${pkg.license} 31 | */ 32 | `.trim(); 33 | } 34 | 35 | const config = { 36 | entry: __dirname + '/src/entry.js', 37 | output: { 38 | path: __dirname + '/dist/js', 39 | filename: jsFilename, 40 | libraryTarget: 'umd', 41 | library: 'angularBootstrapCalendarModuleName' 42 | }, 43 | externals: { 44 | angular: 'angular', 45 | moment: 'moment', 46 | 'interactjs': { 47 | root: 'interact', 48 | commonjs: 'interactjs', 49 | commonjs2: 'interactjs', 50 | amd: 'interact' 51 | } 52 | }, 53 | devtool: MIN ? 'source-map' : false, 54 | module: { 55 | rules: [{ 56 | test: /.*\.js$/, 57 | loader: 'eslint-loader', 58 | exclude: /node_modules/, 59 | enforce: 'pre' 60 | }, { 61 | test: /\.html$/, 62 | loader: 'htmlhint-loader', 63 | exclude: /node_modules/, 64 | enforce: 'pre' 65 | }, { 66 | test: /.*\.js$/, 67 | loader: 'ng-annotate-loader', 68 | exclude: /node_modules/ 69 | }, { 70 | test: /\.html$/, 71 | loader: 'html-loader', 72 | exclude: /node_modules/ 73 | }, { 74 | test: /\.less/, 75 | use: ExtractTextPlugin.extract({ 76 | fallback: 'style-loader', 77 | use: 'css-loader?sourceMap!less-loader?sourceMap' 78 | }), 79 | exclude: /node_modules/ 80 | }] 81 | }, 82 | plugins: [ 83 | new webpack.NoEmitOnErrorsPlugin(), 84 | new webpack.BannerPlugin({ 85 | banner: getBanner(), 86 | raw: true, 87 | entryOnly: true 88 | }), 89 | new ExtractTextPlugin('../css/' + cssFilename), 90 | new webpack.DefinePlugin({ 91 | EXCLUDE_TEMPLATES: !!env.excludeTemplates 92 | }) 93 | ] 94 | }; 95 | 96 | if (env.excludeTemplates) { 97 | config.plugins.push(new webpack.IgnorePlugin(/templates\/(.+)\.html$/)); 98 | } 99 | 100 | return config; 101 | 102 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const webpack = require('webpack'); 4 | const WebpackNotifierPlugin = require('webpack-notifier'); 5 | 6 | module.exports = { 7 | entry: __dirname + '/src/entry.js', 8 | devtool: 'eval', 9 | output: { 10 | filename: 'angular-bootstrap-calendar.js' 11 | }, 12 | module: { 13 | rules: [{ 14 | test: /.*\.js$/, 15 | loader: 'eslint-loader', 16 | exclude: /node_modules/, 17 | enforce: 'pre' 18 | }, { 19 | test: /\.html$/, 20 | loader: 'htmlhint-loader', 21 | exclude: /node_modules/, 22 | enforce: 'pre' 23 | }, { 24 | test: /\.less$/, 25 | loader: 'style-loader!css-loader!less-loader', 26 | exclude: /node_modules/ 27 | }, { 28 | test: /\.html$/, 29 | loader: 'html-loader', 30 | exclude: /node_modules/ 31 | }] 32 | }, 33 | devServer: { 34 | port: 8000, 35 | inline: true, 36 | hot: true 37 | }, 38 | plugins: [ 39 | new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), 40 | new webpack.HotModuleReplacementPlugin(), 41 | new WebpackNotifierPlugin(), 42 | new webpack.DefinePlugin({ 43 | EXCLUDE_TEMPLATES: false 44 | }) 45 | ] 46 | }; 47 | --------------------------------------------------------------------------------