├── .eslintignore ├── .babelrc ├── src ├── templates │ ├── year.html │ ├── day.inner.html │ ├── week.html │ ├── weekdays-header.html │ ├── day.html │ ├── month.html │ └── calendar.html ├── week.directive.js ├── month.directive.js ├── day.directive.js ├── index.js ├── calendar.provider.spec.js ├── calendar.directive.js ├── calendar.provider.js ├── calendar.controller.spec.js ├── calendar.directive.spec.js ├── calendar.controller.js ├── calendar.service.spec.js ├── calendar.scss └── calendar.service.js ├── .gitignore ├── bower.json ├── LICENSE ├── .eslintrc ├── webpack.config.js ├── karma.conf.js ├── package.json ├── CODE_OF_CONDUCT.md ├── dist ├── angular-json-calendar.min.css ├── angular-json-calendar.css └── angular-json-calendar.js.map └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | webpack.config.js 2 | package.json 3 | bower.json 4 | dist/ 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["babel-plugin-add-module-exports"] 4 | } -------------------------------------------------------------------------------- /src/templates/year.html: -------------------------------------------------------------------------------- 1 |
5 | 6 | 7 | 8 |
9 | 10 | -------------------------------------------------------------------------------- /src/templates/day.inner.html: -------------------------------------------------------------------------------- 1 | 9 | 10 | -------------------------------------------------------------------------------- /src/templates/week.html: -------------------------------------------------------------------------------- 1 | 10 | 11 | -------------------------------------------------------------------------------- /src/templates/weekdays-header.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | {{ day }} 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/week.directive.js: -------------------------------------------------------------------------------- 1 | import weekTemplate from './templates/week.html'; 2 | 3 | export function bcWeekDirective( 4 | ) { 5 | 'ngInject'; 6 | 7 | const directive = { 8 | restrict: 'E', 9 | scope: true, 10 | bindToController: { 11 | bcCollection: '=', 12 | }, 13 | templateUrl: weekTemplate, 14 | controller: () => {}, 15 | controllerAs: 'vm', 16 | }; 17 | 18 | return directive; 19 | 20 | } 21 | 22 | -------------------------------------------------------------------------------- /src/month.directive.js: -------------------------------------------------------------------------------- 1 | import monthTemplate from './templates/month.html'; 2 | 3 | export function bcMonthDirective( 4 | ) { 5 | 'ngInject'; 6 | 7 | const directive = { 8 | restrict: 'E', 9 | scope: true, 10 | bindToController: { 11 | bcCollection: '=', 12 | bcWeekdaysHeader: '=', 13 | }, 14 | templateUrl: monthTemplate, 15 | controller: () => {}, 16 | controllerAs: 'vm', 17 | }; 18 | 19 | return directive; 20 | 21 | } 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/day.directive.js: -------------------------------------------------------------------------------- 1 | import dayWrapperTemplate from './templates/day.html'; 2 | 3 | export function bcDayDirective( 4 | bcCalendarConfig 5 | ) { 6 | 'ngInject'; 7 | 8 | const directive = { 9 | restrict: 'E', 10 | scope: true, 11 | bindToController: { 12 | bcCollection: '=', 13 | }, 14 | templateUrl: dayWrapperTemplate, 15 | controller: function(bcCalendarService) { 16 | 'ngInject'; 17 | 18 | // Get the inner-day template from the service 19 | this.dayTemplate = bcCalendarService.getDayTemplate(); 20 | }, 21 | controllerAs: 'vm', 22 | }; 23 | 24 | return directive; 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | bower_components 29 | 30 | # Coveralls coverage 31 | .coveralls.yml 32 | 33 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import { bcCalendarConfig } from './calendar.provider'; 2 | import { bcCalendarService } from './calendar.service'; 3 | import { bcCalendarDirective } from './calendar.directive'; 4 | import { bcMonthDirective } from './month.directive'; 5 | import { bcWeekDirective } from './week.directive'; 6 | import { bcDayDirective } from './day.directive'; 7 | 8 | export default angular.module('bc.JsonCalendar', []) 9 | .provider('bcCalendarConfig', bcCalendarConfig) 10 | .service('bcCalendarService', bcCalendarService) 11 | .directive('bcCalendar', bcCalendarDirective) 12 | .directive('bcMonth', bcMonthDirective) 13 | .directive('bcWeek', bcWeekDirective) 14 | .directive('bcDay', bcDayDirective) 15 | ; 16 | 17 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-json-calendar", 3 | "description": "An Angular service that generates calendar data as a json object.", 4 | "homepage": "https://github.com/benjamincharity/angular-json-calendar", 5 | "authors": [ 6 | "Benjamin Charity " 7 | ], 8 | "license": "MIT", 9 | "main": [ 10 | "dist/angular-json-calendar.js", 11 | "dist/angular-json-calendar.css" 12 | ], 13 | "keywords": [ 14 | "angular", 15 | "calendar", 16 | "json", 17 | "date", 18 | "dates", 19 | "schedule" 20 | ], 21 | "dependencies": { 22 | "angular": "^1.4.0", 23 | "moment": "^2.13.0" 24 | }, 25 | "ignore": [ 26 | "**/.*", 27 | "src", 28 | "node_modules", 29 | "package.json", 30 | "webpack.config.js" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /src/templates/day.html: -------------------------------------------------------------------------------- 1 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/templates/month.html: -------------------------------------------------------------------------------- 1 | 34 | 35 | -------------------------------------------------------------------------------- /src/templates/calendar.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | 8 | 9 | 10 | {{ day }} 11 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 28 | 29 | 34 | 35 |
36 | 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Benjamin Charity 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/eslint-config-moment/index.js", 3 | 4 | "plugins": ["angular"], 5 | 6 | "ecmaFeatures": { 7 | "globalReturn": true, 8 | "jsx": true, 9 | "modules": true 10 | }, 11 | 12 | "env": { 13 | "browser": true, 14 | "es6": true, 15 | "node": true 16 | }, 17 | 18 | "globals": { 19 | "document": false, 20 | "escape": false, 21 | "navigator": false, 22 | "unescape": false, 23 | "window": false, 24 | "before": true, 25 | "sinon": true, 26 | "angular": true, 27 | 28 | "afterEach": false, 29 | "beforeEach": false, 30 | "describe": false, 31 | "expect": false, 32 | "it": false, 33 | "jasmine": false, 34 | "pending": false, 35 | "spyOn": false, 36 | "waits": false, 37 | "waitsFor": false, 38 | "xdescribe": false, 39 | "xit": false, 40 | 41 | "beforeAll": false, 42 | "afterAll": false, 43 | "runs": false 44 | }, 45 | 46 | "parser": "babel-eslint", 47 | 48 | "rules": { 49 | // Needed for angular 50 | "no-use-before-define": 0, 51 | // Needed for angular 52 | "no-unused-vars": 0, 53 | "angular/log": 0, 54 | // Disabled this since we uses classes for services 55 | // http://www.michaelbromley.co.uk/blog/350/exploring-es6-classes-in-angularjs-1-x%20nice 56 | "angular/no-service-method": 0 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | var webpack = require('webpack'); 2 | var UglifyJsPlugin = webpack.optimize.UglifyJsPlugin; 3 | var env = process.env.WEBPACK_ENV; 4 | var path = require('path'); 5 | var libraryName = 'angular-json-calendar'; 6 | 7 | 8 | var config = { 9 | entry: { 10 | 'angular-json-calendar': './src/index.js', 11 | 'angular-json-calendar.min': './src/index.js', 12 | }, 13 | devtool: 'inline-source-map', 14 | output: { 15 | path: __dirname + '/dist', 16 | filename: "[name].js", 17 | library: libraryName, 18 | libraryTarget: 'umd', 19 | umdNamedDefine: true 20 | }, 21 | module: { 22 | preLoaders: [ 23 | { 24 | test: /\.js$/, 25 | loader: 'eslint-loader', 26 | exclude: /node_modules/ 27 | } 28 | ], 29 | loaders: [ 30 | { 31 | test: /\.js$/, 32 | loaders: [ 33 | 'ng-annotate', 34 | 'babel?presets[]=es2015' 35 | ], 36 | exclude: /(node_modules|bower_components)/ 37 | }, 38 | { 39 | test: /\.(html|svg)$/, 40 | loader: 'ngtemplate!html' 41 | } 42 | ] 43 | }, 44 | resolve: { 45 | root: path.resolve('./src'), 46 | extensions: ['', '.js'] 47 | }, 48 | plugins: [ 49 | new webpack.optimize.UglifyJsPlugin({ 50 | include: /\.min\.js$/, 51 | minimize: true 52 | }) 53 | ] 54 | }; 55 | 56 | module.exports = config; 57 | 58 | -------------------------------------------------------------------------------- /src/calendar.provider.spec.js: -------------------------------------------------------------------------------- 1 | describe('bcCalendarConfig', () => { 2 | let bcCalendarConfig; 3 | 4 | // Include the module 5 | beforeEach(angular.mock.module('bc.JsonCalendar')); 6 | 7 | // Inject the service 8 | beforeEach(inject((_bcCalendarConfig_) => { 9 | bcCalendarConfig = _bcCalendarConfig_; 10 | })); 11 | 12 | 13 | 14 | 15 | describe('startDate', () => { 16 | 17 | it('should have a valid start date', () => { 18 | expect(moment(bcCalendarConfig.startDate).isValid()).toEqual(true); 19 | }); 20 | 21 | }); 22 | 23 | 24 | describe('weekdayStyle', () => { 25 | 26 | it('should have an array for the style: letter', () => { 27 | expect(bcCalendarConfig.weekdayStyle.letter).toBeDefined(); 28 | }); 29 | 30 | it('should have an array for the style: abbreviation', () => { 31 | expect(bcCalendarConfig.weekdayStyle.abbreviation).toBeDefined(); 32 | }); 33 | 34 | it('should have an array for the style: word', () => { 35 | expect(bcCalendarConfig.weekdayStyle.word).toBeDefined(); 36 | }); 37 | 38 | }); 39 | 40 | 41 | describe('dayTemplate', () => { 42 | 43 | it('should have a default template defined', () => { 44 | expect(bcCalendarConfig.dayTemplate).toBeDefined(); 45 | }); 46 | 47 | }); 48 | 49 | 50 | describe('setDayTemplate', () => { 51 | const template = `Test content!`; 52 | 53 | beforeEach(() => { 54 | bcCalendarConfig.setDayTemplate(template); 55 | }); 56 | 57 | it('should have a custom user day template', () => { 58 | expect(bcCalendarConfig.userDayTemplate).toEqual(template); 59 | }); 60 | 61 | }); 62 | 63 | 64 | }); 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/calendar.directive.js: -------------------------------------------------------------------------------- 1 | import { CalendarController } from './calendar.controller'; 2 | import calendarTemplate from './templates/calendar.html'; 3 | import monthTemplate from './templates/month.html'; 4 | import weekTemplate from './templates/week.html'; 5 | import dayTemplate from './templates/day.html'; 6 | 7 | export function bcCalendarDirective( 8 | ) { 9 | 'ngInject'; 10 | 11 | // Define possible templates 12 | const templates = { 13 | month: monthTemplate, 14 | week: weekTemplate, 15 | day: dayTemplate, 16 | }; 17 | 18 | const directive = { 19 | restrict: 'E', 20 | scope: {}, 21 | bindToController: { 22 | bcStartDate: '@?', // date - default to today 23 | bcEndDate: '@?', // date - if not present, use create using bcDays 24 | bcNestingDepth: '@?', // string [month|week|day] - defaults: month 25 | bcDays: '@?', // integer - default to 30 (used to create bcEndDate) 26 | bcDayTitleFormat: '@?', // string [word|abbreviation|letter] - default: abbreviation 27 | bcMonthTitleFormat: '@?', // string - any valid Moment date format - default: MMMM 28 | bcDateSelected: '&', // function will be called when a date is selected (tap/click) 29 | bcShowWeekdays: '=?', // determine if the weekdays header should be created 30 | bcShowMonthTitles: '=?', // determine if the month titles should be visible 31 | bcDayTemplate: '@?', // overwrite the default 'day' template 32 | bcDateFormat: '@?', // define a custom date format for the day 33 | }, 34 | link: linkFunction, 35 | templateUrl: calendarTemplate, 36 | controller: CalendarController, 37 | controllerAs: '$ctrl', 38 | }; 39 | 40 | return directive; 41 | 42 | 43 | 44 | 45 | /** 46 | * Link 47 | */ 48 | function linkFunction($scope, $element, $attrs, $ctrl) { 49 | 50 | // Set the correct template based on the desired nesting depth 51 | $ctrl.getTemplateUrl = () => { 52 | return templates[$ctrl.nestingDepth]; 53 | }; 54 | 55 | } 56 | 57 | 58 | 59 | } 60 | 61 | 62 | -------------------------------------------------------------------------------- /src/calendar.provider.js: -------------------------------------------------------------------------------- 1 | import dayTemplate from './templates/day.inner.html'; 2 | 3 | export class bcCalendarConfig { 4 | 5 | // Define defaults 6 | constructor() { 7 | 'ngInject'; 8 | 9 | // The calendar will begin with today 10 | this.startDate = moment(new Date()).startOf('day').format(); 11 | 12 | // The default interval type [day|week|month] 13 | this.nestingDepth = 'month'; 14 | 15 | // How many days should be generated 16 | this.days = 30; 17 | 18 | // Define the different possible representations of the weekday 19 | this.weekdayStyle = { 20 | letter: [ 21 | 'S', 22 | 'M', 23 | 'T', 24 | 'W', 25 | 'T', 26 | 'F', 27 | 'S', 28 | ], 29 | abbreviation: [ 30 | 'Sun', 31 | 'Mon', 32 | 'Tue', 33 | 'Wed', 34 | 'Thur', 35 | 'Fri', 36 | 'Sat', 37 | ], 38 | word: [ 39 | 'Sunday', 40 | 'Monday', 41 | 'Tuesday', 42 | 'Wednesday', 43 | 'Thursday', 44 | 'Friday', 45 | 'Saturday', 46 | ], 47 | }; 48 | 49 | // Set the default word type (M vs Mon vs Monday) 50 | this.dayTitleFormat = 'abbreviation'; 51 | 52 | // Should the calendar show the weekday names above each column? 53 | this.showWeekdays = true; 54 | 55 | // Define the default template for a day 56 | this.dayTemplate = dayTemplate; 57 | 58 | // Allow the user to set a custom template 59 | this.setDayTemplate = (template) => { 60 | this.userDayTemplate = template; 61 | } 62 | 63 | // Define the default format for a day 64 | this.dateFormat = 'D'; 65 | 66 | // Define the default format for a month title 67 | this.monthTitleFormat = 'MMMM' 68 | 69 | // Should month titles be shown by default? 70 | this.showMonthTitles = true; 71 | 72 | } 73 | 74 | 75 | 76 | 77 | $get() { 78 | return this; 79 | } 80 | 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | var WebpackConf = require('./webpack.config.js'); 3 | WebpackConf.entry = {}; 4 | WebpackConf.devtool = 'inline-source-map'; 5 | 6 | module.exports = function(config) { 7 | config.set({ 8 | 9 | // base path that will be used to resolve all patterns (eg. files, exclude) 10 | basePath: './', 11 | 12 | 13 | // frameworks to use 14 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 15 | frameworks: ['jasmine'], 16 | 17 | 18 | // list of files / patterns to load in the browser 19 | files: [ 20 | 'node_modules/angular/angular.js', 21 | 'node_modules/angular-mocks/angular-mocks.js', 22 | 'node_modules/moment/moment.js', 23 | 'src/index.js', 24 | 'src/*.spec.js' 25 | ], 26 | 27 | 28 | // list of files to exclude 29 | exclude: [ 30 | 'app/node_modules/**/!(angular|moment|angular-mocks).js' 31 | ], 32 | 33 | 34 | // preprocess matching files before serving them to the browser 35 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 36 | preprocessors: { 37 | 'src/!(*.spec).js': ['webpack', 'coverage'], 38 | 'src/*.spec.js': [ 'webpack', 'sourcemap' ] 39 | }, 40 | 41 | 42 | webpack: WebpackConf, 43 | 44 | 45 | // Don't spam the browser console 46 | webpackServer: { 47 | noInfo: true 48 | }, 49 | 50 | 51 | ngHtml2JsPreprocessor: { 52 | moduleName: 'templates' 53 | }, 54 | 55 | 56 | // test results reporter to use 57 | // possible values: 'dots', 'progress' 58 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 59 | reporters: ['spec', 'coverage', 'coveralls'], 60 | 61 | 62 | // web server port 63 | port: 9876, 64 | 65 | 66 | // enable / disable colors in the output (reporters and logs) 67 | colors: true, 68 | 69 | 70 | // level of logging 71 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 72 | logLevel: config.LOG_INFO, 73 | 74 | 75 | // enable / disable watching file and executing tests whenever any file changes 76 | autoWatch: true, 77 | 78 | 79 | // start these browsers 80 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 81 | browsers: ['PhantomJS'], 82 | 83 | 84 | // Continuous Integration mode 85 | // if true, Karma captures browsers, runs the tests and exits 86 | singleRun: false, 87 | 88 | // Concurrency level 89 | // how many browser should be started simultaneous 90 | concurrency: Infinity, 91 | 92 | 93 | // Configure the reporter 94 | coverageReporter: { 95 | type : 'lcov', 96 | dir : 'coverage/', 97 | includeAllSources: true 98 | } 99 | }) 100 | } 101 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-json-calendar", 3 | "version": "2.0.0", 4 | "description": "An Angular service that generates calendar data as a json object.", 5 | "keywords": [ 6 | "angular", 7 | "calendar", 8 | "json", 9 | "date", 10 | "dates", 11 | "schedule" 12 | ], 13 | "author": "Benjamin Charity ", 14 | "license": "MIT", 15 | "bugs": { 16 | "url": "https://github.com/benjamincharity/angular-json-calendar/issues" 17 | }, 18 | "homepage": "https://github.com/benjamincharity/angular-json-calendar", 19 | "main": "dist/angular-json-calendar.js", 20 | "scripts": { 21 | "build": "npm run build:js && npm run build:css", 22 | "build:js": "WEBPACK_ENV=build webpack && osx-notifier --type \"pass\" --title \"Build successful\" --message \"gg\" --group \"npmBuild\"", 23 | "build:css": "npm run scss:compressed && npm run scss:expanded && npm run autoprefixer", 24 | "autoprefixer": "postcss -u autoprefixer -r dist/*.css", 25 | "scss:compressed": "node-sass --output-style compressed -o dist/ src/calendar.scss && mv dist/calendar.css dist/angular-json-calendar.min.css", 26 | "scss:expanded": "node-sass --output-style expanded -o dist/ src/calendar.scss && mv dist/calendar.css dist/angular-json-calendar.css", 27 | "watch:css": "onchange 'src/*.scss' -- npm run build:css", 28 | "watch:js": "onchange 'src/*.js' 'src/*.html' -e 'src/*.spec.js' -- npm run build:js", 29 | "watch": "parallelshell 'npm run test' 'npm run watch:css' 'npm run watch:js'", 30 | "test": "karma start --singleRun=true" 31 | }, 32 | "repository": { 33 | "type": "git", 34 | "url": "https://github.com/benjamincharity/angular-json-calendar.git" 35 | }, 36 | "dependencies": { 37 | "angular": "^1.4.0", 38 | "moment": "^2.13.0" 39 | }, 40 | "devDependencies": { 41 | "angular": "^1.4.0", 42 | "angular-mocks": "^1.5.8", 43 | "autoprefixer": "^6.3.3", 44 | "babel": "6.3.13", 45 | "babel-core": "^6.11.4", 46 | "babel-eslint": "5.0.0", 47 | "babel-loader": "^6.2.4", 48 | "babel-plugin-add-module-exports": "0.1.2", 49 | "babel-preset-es2015": "^6.3.13", 50 | "eslint": "1.7.2", 51 | "eslint-config-moment": "^1.5.0", 52 | "eslint-loader": "1.1.0", 53 | "eslint-plugin-angular": "^1.4.1", 54 | "html-loader": "^0.4.2", 55 | "jasmine": "^2.4.1", 56 | "jasmine-core": "^2.4.1", 57 | "karma": "^1.1.1", 58 | "karma-chrome-launcher": "^1.0.1", 59 | "karma-coverage": "^1.1.1", 60 | "karma-coveralls": "^1.1.2", 61 | "karma-jasmine": "^1.0.2", 62 | "karma-phantomjs-launcher": "^1.0.1", 63 | "karma-sourcemap-loader": "^0.3.7", 64 | "karma-spec-reporter": "0.0.26", 65 | "karma-webpack": "^1.7.0", 66 | "loader-utils": "^0.2.12", 67 | "minifier": "^0.7.1", 68 | "ng-annotate-loader": "^0.1.0", 69 | "ng-annotate-webpack-plugin": "^0.1.2", 70 | "ngtemplate-loader": "^1.3.1", 71 | "node-sass": "^3.4.2", 72 | "onchange": "^2.2.0", 73 | "osx-notifier": "^0.2.2", 74 | "parallelshell": "^2.0.0", 75 | "phantomjs": "^2.1.7", 76 | "postcss-cli": "^2.5.1", 77 | "sass-loader": "^3.1.2", 78 | "source-map": "^0.5.3", 79 | "webpack": "1.12.9", 80 | "webpack-dev-server": "^1.14.1", 81 | "yargs": "3.32.0" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | nationality, personal appearance, race, religion, or sexual identity and 10 | orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at ben@benjamincharity.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at [http://contributor-covenant.org/version/1/4][version] 72 | 73 | [homepage]: http://contributor-covenant.org 74 | [version]: http://contributor-covenant.org/version/1/4/ 75 | 76 | -------------------------------------------------------------------------------- /src/calendar.controller.spec.js: -------------------------------------------------------------------------------- 1 | describe('CalendarController', () => { 2 | let $compile; 3 | let $rootScope; 4 | let $templateCache; 5 | 6 | // Include the module 7 | beforeEach(angular.mock.module('bc.JsonCalendar')); 8 | 9 | // Inject the service 10 | beforeEach(inject((_$compile_, _$rootScope_, _$templateCache_) => { 11 | $compile = _$compile_; 12 | $rootScope = _$rootScope_; 13 | $templateCache = _$templateCache_; 14 | })); 15 | 16 | 17 | describe('bcDays are used', () => { 18 | let $scope; 19 | let element; 20 | let vm; 21 | const THREE_DAYS = 3; 22 | 23 | beforeEach(() => { 24 | $scope = $rootScope.$new(); 25 | element = angular.element( 26 | `` 27 | ); 28 | element = $compile(element)($scope); 29 | $scope.$apply(); 30 | vm = element.isolateScope().$ctrl; 31 | }); 32 | 33 | it('should be set to create three days', () => { 34 | expect(vm.days).toEqual(THREE_DAYS); 35 | }); 36 | 37 | it('should have generated DOM for the days', () => { 38 | expect(element.find('span')[0]).toBeDefined(); 39 | }); 40 | 41 | }); 42 | 43 | 44 | describe('selectDate', () => { 45 | let $scope; 46 | let element; 47 | let vm; 48 | 49 | beforeEach(() => { 50 | $scope = $rootScope.$new(); 51 | element = angular.element( 52 | `` 53 | ); 54 | element = $compile(element)($scope); 55 | $scope.$apply(); 56 | vm = element.isolateScope().$ctrl; 57 | }); 58 | 59 | afterEach(() => { 60 | vm.selectedDate = null; 61 | }); 62 | 63 | it('should not initially have a selected date', () => { 64 | expect(vm.selectedDate).toBeNull(); 65 | }); 66 | 67 | it('should set the selected date', () => { 68 | const today = element[0].querySelectorAll('.bc-calendar__day--today')[0]; 69 | angular.element(today).triggerHandler('click'); 70 | expect(vm.selectedDate).toBeDefined(); 71 | }); 72 | 73 | }); 74 | 75 | 76 | describe('formatDate', () => { 77 | let $scope; 78 | let element; 79 | let vm; 80 | let date; 81 | const FORMAT = 'DD/MM/YYYY' 82 | 83 | beforeEach(() => { 84 | $scope = $rootScope.$new(); 85 | element = angular.element( 86 | `` 87 | ); 88 | element = $compile(element)($scope); 89 | $scope.$apply(); 90 | vm = element.isolateScope().$ctrl; 91 | 92 | date = '2016-05-01T00:00:00.027Z'; 93 | }); 94 | 95 | it('should format the date', () => { 96 | const prettyDate = vm.formatDate(date, FORMAT); 97 | expect(/\d{2}\/\d{2}\/\d{4}/.test(prettyDate)).toBe(true); 98 | }); 99 | 100 | }); 101 | 102 | 103 | describe('_buildCalendar', () => { 104 | let $scope; 105 | let element; 106 | let vm; 107 | 108 | beforeEach(() => { 109 | $scope = $rootScope.$new(); 110 | element = angular.element( 111 | `` 112 | ); 113 | element = $compile(element)($scope); 114 | $scope.$apply(); 115 | vm = element.isolateScope().$ctrl; 116 | }); 117 | 118 | it('should have created a collection', () => { 119 | expect(vm.bcCollection).toBeDefined(); 120 | }); 121 | 122 | }); 123 | 124 | 125 | describe('templateCache', () => { 126 | let $scope; 127 | let element; 128 | let vm; 129 | 130 | beforeEach(() => { 131 | $scope = $rootScope.$new(); 132 | element = angular.element( 133 | `` 134 | ); 135 | element = $compile(element)($scope); 136 | $scope.$apply(); 137 | vm = element.isolateScope().vm; 138 | }); 139 | 140 | it('should have a custom template in the cache', () => { 141 | expect($templateCache.get('userDayTemplate.html')).toBeDefined(); 142 | }); 143 | 144 | }); 145 | 146 | 147 | }); 148 | 149 | -------------------------------------------------------------------------------- /src/calendar.directive.spec.js: -------------------------------------------------------------------------------- 1 | describe('bcCalendarDirective', () => { 2 | let $compile; 3 | let $rootScope; 4 | 5 | // Include the module 6 | beforeEach(angular.mock.module('bc.JsonCalendar')); 7 | 8 | // Inject the service 9 | beforeEach(inject((_$compile_, _$rootScope_) => { 10 | $compile = _$compile_; 11 | $rootScope = _$rootScope_; 12 | })); 13 | 14 | 15 | 16 | describe('templateExists', () => { 17 | 18 | it('should have a template', () => { 19 | const element = $compile('')($rootScope); 20 | $rootScope.$digest(); 21 | 22 | expect(element.html()).toContain('bc-calendar__day'); 23 | }); 24 | 25 | }); 26 | 27 | 28 | describe('controllerExists', () => { 29 | let $scope; 30 | let element; 31 | let vm; 32 | 33 | beforeEach(inject(() => { 34 | 35 | $scope = $rootScope.$new(); 36 | 37 | element = ''; 38 | 39 | // Create the directive 40 | element = $compile(element)($scope); 41 | 42 | // Activate the $digest cycle 43 | $scope.$apply(); 44 | 45 | // Save reference to vm 46 | vm = element.isolateScope().$ctrl; 47 | 48 | })); 49 | 50 | it('should have a $scope property for the start date', () => { 51 | expect(vm.bcStartDate).toBeDefined(); 52 | }); 53 | 54 | }); 55 | 56 | 57 | describe('days are generated', () => { 58 | let $scope; 59 | let element; 60 | let vm; 61 | 62 | beforeEach(() => { 63 | $scope = $rootScope.$new(); 64 | element = angular.element( 65 | `` 66 | ); 67 | element = $compile(element)($scope); 68 | $scope.$apply(); 69 | vm = element.isolateScope().vm; 70 | }); 71 | 72 | it('should NOT have generated DOM for a week', () => { 73 | const week = element[0].querySelectorAll('.bc-calendar__week')[0]; 74 | expect(week).not.toBeDefined(); 75 | }); 76 | 77 | it('should have generated DOM for the days', () => { 78 | const day = element[0].querySelectorAll('.bc-calendar__day')[0]; 79 | expect(day).toBeDefined(); 80 | }); 81 | 82 | }); 83 | 84 | 85 | describe('weeks are generated', () => { 86 | let $scope; 87 | let element; 88 | let vm; 89 | 90 | beforeEach(() => { 91 | $scope = $rootScope.$new(); 92 | element = angular.element( 93 | `` 94 | ); 95 | element = $compile(element)($scope); 96 | $scope.$apply(); 97 | vm = element.isolateScope().vm; 98 | }); 99 | 100 | it('should have generated DOM for a week', () => { 101 | const week = element[0].querySelectorAll('.bc-calendar__week')[0]; 102 | expect(week).toBeDefined(); 103 | }); 104 | 105 | it('should NOT have generated DOM for a month', () => { 106 | const month = element[0].querySelectorAll('.bc-calendar__month')[0]; 107 | expect(month).not.toBeDefined(); 108 | }); 109 | 110 | }); 111 | 112 | 113 | describe('months are generated', () => { 114 | let $scope; 115 | let element; 116 | let vm; 117 | 118 | beforeEach(() => { 119 | $scope = $rootScope.$new(); 120 | element = angular.element( 121 | `` 122 | ); 123 | element = $compile(element)($scope); 124 | $scope.$apply(); 125 | vm = element.isolateScope().vm; 126 | }); 127 | 128 | it('should have generated DOM for a month', () => { 129 | const month = element[0].querySelectorAll('.bc-calendar__month')[0]; 130 | expect(month).toBeDefined(); 131 | }); 132 | 133 | }); 134 | 135 | 136 | describe('weekday headers', () => { 137 | 138 | describe('visible headers', () => { 139 | let $scope; 140 | let element; 141 | let vm; 142 | 143 | beforeEach(() => { 144 | $scope = $rootScope.$new(); 145 | element = angular.element( 146 | `` 147 | ); 148 | element = $compile(element)($scope); 149 | $scope.$apply(); 150 | vm = element.isolateScope().vm; 151 | }); 152 | 153 | it('should have visible weekday headers', () => { 154 | const title = element[0].querySelectorAll('.bc-calendar__weekdays')[0]; 155 | expect(title).toBeDefined(); 156 | }); 157 | }); 158 | 159 | 160 | describe('hidden headers', () => { 161 | let $scope; 162 | let element; 163 | let vm; 164 | 165 | beforeEach(() => { 166 | $scope = $rootScope.$new(); 167 | element = angular.element( 168 | `` 169 | ); 170 | element = $compile(element)($scope); 171 | $scope.$apply(); 172 | vm = element.isolateScope().vm; 173 | }); 174 | 175 | it('should NOT have visible weekday headers', () => { 176 | const title = element[0].querySelectorAll('.bc-calendar__weekdays')[0]; 177 | expect(title).not.toBeDefined(); 178 | }); 179 | }); 180 | 181 | 182 | 183 | 184 | }); 185 | 186 | 187 | 188 | 189 | }); 190 | 191 | -------------------------------------------------------------------------------- /src/calendar.controller.js: -------------------------------------------------------------------------------- 1 | export class CalendarController { 2 | 3 | constructor( 4 | $templateCache, 5 | bcCalendarConfig, bcCalendarService 6 | ) { 7 | 'ngInject'; 8 | 9 | this.$templateCache = $templateCache; 10 | this.bcCalendarConfig = bcCalendarConfig; 11 | this.bcCalendarService = bcCalendarService; 12 | 13 | 14 | this._activate(); 15 | 16 | } 17 | 18 | 19 | 20 | 21 | _activate() { 22 | // Define today's date 23 | this.today = moment(new Date()).startOf('day'); 24 | 25 | // Define the start date for the calendar 26 | this.startDate = this.bcStartDate || this.bcCalendarConfig.startDate; 27 | 28 | // If the end date was defined 29 | if (this.bcEndDate) { 30 | 31 | // Define how many days are needed using the end date 32 | this.days = this.bcCalendarService.durationInDays(this.startDate, this.bcEndDate); 33 | 34 | } else { 35 | 36 | // Define how many days are needed from the count passed in or the config 37 | this.days = parseInt(this.bcDays || this.bcCalendarConfig.days, 10); 38 | 39 | } 40 | 41 | // Define how deep to organize the calendar 42 | this.nestingDepth = this.bcNestingDepth || this.bcCalendarConfig.nestingDepth; 43 | 44 | // Define the weekday headers format 45 | this.weekdays = this.bcDayTitleFormat ? 46 | this.bcCalendarConfig.weekdayStyle[this.bcDayTitleFormat] : 47 | this.bcCalendarConfig.weekdayStyle[this.bcCalendarConfig.dayTitleFormat]; 48 | 49 | // Define the format for the month title 50 | this.monthTitleFormat = this.bcMonthTitleFormat || this.bcCalendarConfig.monthTitleFormat; 51 | 52 | // Define if month titles should be visible 53 | this.showMonthTitles = typeof(this.bcShowMonthTitles) === 'boolean' ? 54 | this.bcShowMonthTitles : this.bcCalendarConfig.showMonthTitles; 55 | 56 | // Initially no date is selected 57 | this.selectedDate = null; 58 | 59 | // Set the visibility of the calendar weekdays headers 60 | this.showWeekdays = typeof(this.bcShowWeekdays) === 'boolean' ? 61 | this.bcShowWeekdays : this.bcCalendarConfig.showWeekdays; 62 | 63 | 64 | // If a custom day template has been set in either location (attribute or provider) 65 | if (this.bcDayTemplate || this.bcCalendarConfig.userDayTemplate) { 66 | // Name the template location 67 | const templateLocation = 'userDayTemplate.html'; 68 | 69 | // If the user set a template via the directive attribute 70 | if (this.bcDayTemplate) { 71 | // Put the user template into the cache 72 | this.$templateCache.put(templateLocation, this.bcDayTemplate); 73 | } 74 | 75 | // If the user defined a template using the provider 76 | if (this.bcCalendarConfig.userDayTemplate) { 77 | // Put the user template into the cache 78 | this.$templateCache.put(templateLocation, this.bcCalendarConfig.userDayTemplate); 79 | } 80 | 81 | // Store the inner-day template on the service 82 | this.bcCalendarService.storeDayTemplate(templateLocation); 83 | 84 | } else { 85 | // No custom template was defined 86 | 87 | // Store the inner-day template on the service 88 | this.bcCalendarService.storeDayTemplate(this.bcCalendarConfig.dayTemplate); 89 | } 90 | 91 | // Define the date format for the individual day 92 | this.dateFormat = this.bcDateFormat || this.bcCalendarConfig.dateFormat; 93 | 94 | // Build array of days 95 | const days = this.bcCalendarService.buildDays(this.days, this.startDate); 96 | 97 | // Build the calendar JSON and expose to the DOM 98 | this._buildCalendar(days, this.nestingDepth); 99 | 100 | } 101 | 102 | 103 | 104 | 105 | 106 | /** 107 | * Check to see if the day is prior to the current date 108 | * This is used to disable the unselectable days 109 | * 110 | * @param {Date} date 111 | * @return {Bool} 112 | */ 113 | isBeforeToday(date) { 114 | return this.bcCalendarService.dateIsBeforeToday(date); 115 | } 116 | 117 | 118 | /** 119 | * Check to see if the day matches the current date 120 | * 121 | * @param {Date} date 122 | * @return {Bool} 123 | */ 124 | isDayToday(date) { 125 | return this.bcCalendarService.isDayToday(date, this.startDate); 126 | } 127 | 128 | 129 | /** 130 | * Select a date 131 | * 132 | * @param {Object} day 133 | */ 134 | selectDate(day) { 135 | // Set the selected day 136 | this.selectedDate = day; 137 | 138 | // Only call the passed method if it exists and a valid date was chosen 139 | if (day.date && this.bcDateSelected) { 140 | this.bcDateSelected({ 141 | date: day.date, 142 | }); 143 | } 144 | } 145 | 146 | 147 | /** 148 | * Format a date using moment 149 | * 150 | * @param {String} date 151 | * @param {String} format 152 | * @return {String} formattedDate 153 | */ 154 | formatDate(date, format) { 155 | if (!date) { 156 | return false; 157 | } 158 | 159 | return moment(date).format(format); 160 | } 161 | 162 | 163 | /** 164 | * Build the calendar JSON 165 | * 166 | * @param {Array} days 167 | * @param {String} depth 168 | * @return {Element} element 169 | */ 170 | _buildCalendar(days, depth) { 171 | 172 | // Call the correct organization method based on the nesting depth 173 | if (depth === 'month') { 174 | 175 | this.bcCollection = this.bcCalendarService.organizeMonths(days); 176 | 177 | } else if (depth === 'week') { 178 | 179 | this.bcCollection = this.bcCalendarService.organizeWeeks(days); 180 | 181 | } else if (depth === 'day') { 182 | 183 | this.bcCollection = days; 184 | 185 | } 186 | } 187 | 188 | 189 | } 190 | 191 | -------------------------------------------------------------------------------- /src/calendar.service.spec.js: -------------------------------------------------------------------------------- 1 | describe('bcCalendarService', () => { 2 | const ALL_DAYS_COUNT = 60; 3 | let bcCalendarService; 4 | let ALL_DAYS; 5 | 6 | // Include the module 7 | beforeEach(angular.mock.module('bc.JsonCalendar')); 8 | 9 | // Inject the service 10 | beforeEach(inject((_bcCalendarService_) => { 11 | bcCalendarService = _bcCalendarService_; 12 | 13 | // Create large array of days for all tests to use 14 | ALL_DAYS = bcCalendarService.buildDays(ALL_DAYS_COUNT); 15 | })); 16 | 17 | 18 | 19 | describe('dateIsBeforeToday', () => { 20 | const pastDate = '2016-05-01T00:00:00.027Z'; 21 | 22 | it('should verify that the date is before today', () => { 23 | expect(bcCalendarService.dateIsBeforeToday(pastDate)).toBe(true); 24 | }); 25 | 26 | }); 27 | 28 | 29 | describe('isDayToday', () => { 30 | const date1 = moment('2013-02-08').format(); 31 | const date2 = moment('2013-02-08').format(); 32 | 33 | it('should verify that the date is today', () => { 34 | expect(bcCalendarService.isDayToday(date1, date2)).toBe(true); 35 | }); 36 | 37 | }); 38 | 39 | 40 | describe('integerToArray', () => { 41 | const ARRAY_LENGTH = 3; 42 | let array; 43 | 44 | beforeEach(() => { 45 | array = bcCalendarService.integerToArray(ARRAY_LENGTH); 46 | }); 47 | 48 | it('should be an array the length of ARRAY_LENGTH', () => { 49 | expect(array.length).toEqual(ARRAY_LENGTH); 50 | }); 51 | 52 | }); 53 | 54 | 55 | describe('padDaysLeft', () => { 56 | const COUNT = 3; 57 | const START = moment('2016-11-05').format(); 58 | let pad; 59 | 60 | beforeEach(() => { 61 | pad = bcCalendarService.padDaysLeft(START, COUNT); 62 | }); 63 | 64 | afterEach(() => { 65 | pad = null; 66 | }); 67 | 68 | it('should be an array of days equal to COUNT', () => { 69 | expect(pad.length).toEqual(COUNT); 70 | }); 71 | 72 | it('should be ordered from earliest to latest', () => { 73 | const lastDay = pad[pad.length - 1].date; 74 | const secondToLastDay = pad[pad.length - 2].date; 75 | 76 | expect(moment(secondToLastDay).isBefore(lastDay)).toBe(true); 77 | }); 78 | 79 | }); 80 | 81 | 82 | describe('padBlankTiles', () => { 83 | const PADDED_LENGTH = 5; 84 | const DIRECTION = 'right'; 85 | const COUNT = 2; 86 | const DAYS_NEEDED = 3; 87 | let array; 88 | 89 | beforeEach(() => { 90 | array = ALL_DAYS.slice(0, DAYS_NEEDED); 91 | }); 92 | 93 | afterEach(() => { 94 | array = null; 95 | }); 96 | 97 | it('should contain the correct number of objects', () => { 98 | array = bcCalendarService.padBlankTiles(array, COUNT); 99 | 100 | expect(array.length).toEqual(PADDED_LENGTH); 101 | }); 102 | 103 | it('should have a blank date at the start and real date at the end', () => { 104 | array = bcCalendarService.padBlankTiles(array, COUNT); 105 | 106 | expect(array[0].date).toBe(null); 107 | expect(array[array.length - 1].date).not.toBe(null); 108 | }); 109 | 110 | it('should have a real date at the start and a blank date at the end', () => { 111 | array = bcCalendarService.padBlankTiles(array, COUNT, DIRECTION); 112 | 113 | expect(array[0].date).not.toBe(null); 114 | expect(array[array.length - 1].date).toBe(null); 115 | }); 116 | 117 | }); 118 | 119 | 120 | describe('chunk', () => { 121 | const SIZE = 3; 122 | const DAYS_NEEDED = 8; 123 | let splitGroup; 124 | let group; 125 | 126 | beforeEach(() => { 127 | group = ALL_DAYS.slice(0, DAYS_NEEDED); 128 | 129 | splitGroup = bcCalendarService.chunk(group, SIZE); 130 | }); 131 | 132 | it('should be a group of arrays the length of SIZE', () => { 133 | expect(splitGroup[0].length).toEqual(SIZE); 134 | }); 135 | 136 | }); 137 | 138 | 139 | describe('durationInDays', () => { 140 | const DAYS_NEEDED = 4; 141 | const END_VALUE = 5; 142 | let duration; 143 | let START; 144 | let END; 145 | 146 | beforeEach(() => { 147 | START = ALL_DAYS[0].date; 148 | END = ALL_DAYS[DAYS_NEEDED].date; 149 | 150 | duration = bcCalendarService.durationInDays(START, END); 151 | }); 152 | 153 | it('should have a duration of END_VALUE', () => { 154 | expect(duration).toEqual(END_VALUE); 155 | }); 156 | 157 | }); 158 | 159 | 160 | describe('organizeWeeks', () => { 161 | const WEEK_LENGTH = 7; 162 | const DAYS_NEEDED = 22; 163 | let weeks; 164 | 165 | beforeEach(() => { 166 | const days = ALL_DAYS.slice(0, DAYS_NEEDED); 167 | 168 | weeks = bcCalendarService.organizeWeeks(days); 169 | }); 170 | 171 | it('should be organized into arrays the length of WEEK_LENGTH', () => { 172 | expect(weeks[0].length).toEqual(WEEK_LENGTH); 173 | }); 174 | 175 | }); 176 | 177 | 178 | describe('organizeMonths', () => { 179 | let allDays; 180 | let months; 181 | 182 | beforeEach(() => { 183 | allDays = ALL_DAYS; 184 | 185 | months = bcCalendarService.organizeMonths(allDays); 186 | }); 187 | 188 | it('should be an array of objects', () => { 189 | expect(months[0]).toBeDefined(); 190 | }); 191 | }); 192 | 193 | 194 | describe('buildDays', () => { 195 | const LIMIT = 6; 196 | let days; 197 | 198 | beforeEach(() => { 199 | days = bcCalendarService.buildDays(LIMIT); 200 | }); 201 | 202 | afterEach(() => { 203 | days = null; 204 | }); 205 | 206 | it('should be an array the length of LIMIT', () => { 207 | expect(days.length).toEqual(LIMIT); 208 | }); 209 | 210 | it('should have a date on each item', () => { 211 | expect(days[0].date).toBeDefined(); 212 | }); 213 | 214 | }); 215 | 216 | }); 217 | 218 | -------------------------------------------------------------------------------- /dist/angular-json-calendar.min.css: -------------------------------------------------------------------------------- 1 | .bc-calendar--months .bc-calendar__month{display:block;margin-bottom:2rem}.bc-calendar--months .bc-calendar__month .bc-calendar__month-title{display:block;font-size:2rem;padding:1rem;text-align:center}.bc-calendar--months .bc-calendar__weekdays{color:#999;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap}.bc-calendar--months .bc-calendar__weekdays .bc-calendar__day,.bc-calendar--months .bc-calendar__weekdays .bc-calendar__day:first-of-type,.bc-calendar--months .bc-calendar__weekdays .bc-calendar__day:first-of-type:not(.bc-calendar__day--pad){border:0}.bc-calendar--months .bc-calendar__week-wrapper{display:block}.bc-calendar--months .bc-calendar__week{border-top:1px solid #999;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.bc-calendar--months .bc-calendar__week:first-of-type{border-top:0}.bc-calendar--months .bc-calendar__week:first-of-type .bc-calendar__day{border-top:1px solid #999}.bc-calendar--months .bc-calendar__week:first-of-type .bc-calendar__day--pad{border-top-color:transparent}.bc-calendar--months .bc-calendar__week:first-of-type .bc-calendar__day--pad:last-child{border-right:1px solid #999}.bc-calendar--months .bc-calendar__week:last-of-type .bc-calendar__day{border-bottom:1px solid #999}.bc-calendar--months .bc-calendar__week:nth-of-type(odd) .bc-calendar__day:nth-of-type(odd){background-color:rgba(0,0,0,0.03)}.bc-calendar--months .bc-calendar__week:nth-of-type(odd) .bc-calendar__day.bc-calendar__day--pad{background:transparent;border-color:transparent}.bc-calendar--months .bc-calendar__week:nth-of-type(even) .bc-calendar__day:nth-of-type(even){background-color:rgba(0,0,0,0.03)}.bc-calendar--months .bc-calendar__week:nth-of-type(even) .bc-calendar__day--pad:nth-of-type(even){background-color:transparent}.bc-calendar--months .bc-calendar__week:nth-of-type(even) .bc-calendar__day.bc-calendar__day--pad{background:transparent;border-color:transparent}.bc-calendar--months .bc-calendar__day-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between;width:100%}.bc-calendar--months .bc-calendar__day{border-right:1px solid #999;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;position:relative;text-align:center;width:calc(((100% / 7) - 2px) - 6.25rem)}.bc-calendar--months .bc-calendar__day::before{content:'';display:block;height:0;padding-top:100%}.bc-calendar--months .bc-calendar__day:first-of-type:not(.bc-calendar__day--pad){border-left:1px solid #999}.bc-calendar--months .bc-calendar__day--weekdays{line-height:2em}.bc-calendar--months .bc-calendar__day--weekdays::before{display:none}.bc-calendar--months .bc-calendar__day.bc-calendar__day--pad+.bc-calendar__day:not(.bc-calendar__day--pad)::after{background-color:#999;content:'';display:block;position:absolute;bottom:-1px;left:-1px;top:-1px;width:1px}.bc-calendar--months .bc-calendar__day-time{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;font-size:12px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;position:absolute;bottom:0;left:0;right:0;top:0}.bc-calendar--months .bc-calendar__day-time small{display:block}.bc-calendar--months .bc-calendar__day .week--date{color:#999;font-size:.8rem;font-weight:600;text-transform:uppercase;position:absolute;top:.4em;left:.4em}.bc-calendar--months .bc-calendar__day .month{font-size:.6rem;font-weight:600;letter-spacing:.1em;opacity:.2;text-transform:uppercase;position:absolute;bottom:.2em;left:0;right:0}.bc-calendar--days .bc-calendar__weekdays{border-bottom:2px solid #246b86;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap}.bc-calendar--days .bc-calendar__week:nth-of-type(odd) .bc-calendar__day:nth-of-type(odd){background-color:rgba(0,0,0,0.05)}.bc-calendar--days .bc-calendar__week:nth-of-type(odd) .bc-calendar__day--pad:nth-of-type(odd){background-color:transparent}.bc-calendar--days .bc-calendar__week:nth-of-type(even) .bc-calendar__day:nth-of-type(even){background-color:rgba(0,0,0,0.05)}.bc-calendar--days .bc-calendar__week:nth-of-type(even) .bc-calendar__day--pad:nth-of-type(even){background-color:transparent}.bc-calendar--days .bc-calendar__week:nth-of-type(even) .bc-calendar__day:nth-of-type(odd):not(.bc-calendar__day--pad){border:1px solid rgba(0,0,0,0.05)}.bc-calendar--days .bc-calendar__day-wrapper{display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:justify;-ms-flex-pack:justify;justify-content:space-between}.bc-calendar--days .bc-calendar__day{border:1px solid transparent;-webkit-box-flex:1;-ms-flex:1 1 auto;flex:1 1 auto;position:relative;text-align:center;width:calc((100% / 7) - 6.25rem)}.bc-calendar--days .bc-calendar__day::before{content:'';display:block;height:0;padding-top:100%}.bc-calendar--days .bc-calendar__day--weekdays{line-height:2em}.bc-calendar--days .bc-calendar__day--weekdays::before{display:none}.bc-calendar--days .bc-calendar__day-time{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;font-size:12px;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;position:absolute;bottom:0;left:0;right:0;top:0}.bc-calendar--days .bc-calendar__day-time small{display:block}.bc-calendar--days .bc-calendar__day .week--date{color:#246b86;font-size:.8rem;font-weight:600;text-transform:uppercase;position:absolute;top:.4em;left:.4em}.bc-calendar--days .bc-calendar__day .month{font-size:.6rem;font-weight:600;letter-spacing:.1em;opacity:.2;text-transform:uppercase;position:absolute;bottom:.2em;left:0;right:0}.bc-calendar--sidescroll{overflow-x:scroll;white-space:nowrap}.bc-calendar--sidescroll .bc-calendar__day{border:1px solid #add8e6;display:inline-block;position:relative;text-align:center;width:8rem}.bc-calendar--sidescroll .bc-calendar__day::before{content:'';display:block;height:0;padding-top:80%}.bc-calendar--sidescroll .bc-calendar__day-time{-webkit-box-align:center;-ms-flex-align:center;align-items:center;display:-webkit-box;display:-ms-flexbox;display:flex;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-box-pack:center;-ms-flex-pack:center;justify-content:center;position:absolute;bottom:0;left:0;right:0;top:0}.bc-calendar--sidescroll .week--day{font-size:11px;font-weight:600;opacity:.5;text-transform:uppercase;position:absolute;bottom:0;left:0;right:0} 2 | -------------------------------------------------------------------------------- /src/calendar.scss: -------------------------------------------------------------------------------- 1 | 2 | $gray: #999; 3 | $blue: #add8e6; 4 | $black: #000; 5 | $deep_blue: #246b86; 6 | 7 | 8 | .bc-calendar { 9 | 10 | // 11 | // Months 12 | // 13 | &--months { 14 | 15 | // container for a month 16 | .bc-calendar__month { 17 | display: block; 18 | margin-bottom: 2rem; 19 | 20 | // the month name 21 | .bc-calendar__month-title { 22 | display: block; 23 | font-size: 2rem; 24 | padding: 1rem; 25 | text-align: center; 26 | } 27 | } 28 | 29 | // container for heading row (M, T, W, T...) 30 | .bc-calendar__weekdays { 31 | color: $gray; 32 | display: flex; 33 | flex-flow: row nowrap; 34 | 35 | .bc-calendar__day, 36 | .bc-calendar__day:first-of-type, 37 | .bc-calendar__day:first-of-type:not(.bc-calendar__day--pad) { 38 | border: 0; 39 | } 40 | } 41 | 42 | // Target the actual directive wrapper 43 | .bc-calendar__week-wrapper { 44 | display: block; 45 | } 46 | 47 | //
container for week 48 | .bc-calendar__week { 49 | border-top: 1px solid $gray; 50 | display: flex; 51 | flex-flow: row nowrap; 52 | justify-content: space-between; 53 | 54 | // target first week 55 | &:first-of-type { 56 | border-top: 0; 57 | 58 | .bc-calendar__day { 59 | border-top: 1px solid $gray; 60 | } 61 | 62 | .bc-calendar__day--pad { 63 | border-top-color: transparent; 64 | 65 | &:last-child { 66 | border-right: 1px solid $gray; 67 | } 68 | } 69 | } 70 | 71 | // target last week 72 | &:last-of-type { 73 | .bc-calendar__day { 74 | border-bottom: 1px solid $gray; 75 | } 76 | } 77 | 78 | // target all odd weeks/rows 79 | &:nth-of-type(odd) { 80 | .bc-calendar__day:nth-of-type(odd) { 81 | background-color: rgba($black, .03); 82 | } 83 | 84 | .bc-calendar__day.bc-calendar__day--pad { 85 | background: transparent; 86 | border-color: transparent; 87 | } 88 | } 89 | 90 | // target all even weeks/rows 91 | &:nth-of-type(even) { 92 | .bc-calendar__day:nth-of-type(even) { 93 | background-color: rgba($black, .03); 94 | } 95 | 96 | .bc-calendar__day--pad:nth-of-type(even) { 97 | background-color: transparent; 98 | } 99 | 100 | .bc-calendar__day.bc-calendar__day--pad { 101 | background: transparent; 102 | border-color: transparent; 103 | } 104 | } 105 | } 106 | 107 | // Target the actual directive wrapper 108 | .bc-calendar__day-wrapper { 109 | display: flex; 110 | flex-flow: row nowrap; 111 | justify-content: space-between; 112 | width: 100%; 113 | } 114 | 115 | .bc-calendar__day { 116 | border-right: 1px solid $gray; 117 | flex: 1 1 auto; 118 | position: relative; 119 | text-align: center; 120 | width: calc(((100% / 7) - 2px) - 6.25rem); 121 | 122 | &::before { 123 | content: ''; 124 | display: block; 125 | height: 0; 126 | padding-top: 100%; 127 | } 128 | 129 | &:first-of-type:not(.bc-calendar__day--pad) { 130 | border-left: 1px solid $gray; 131 | } 132 | 133 | &--weekdays { 134 | line-height: 2em; 135 | 136 | &::before { 137 | display: none; 138 | } 139 | } 140 | 141 | // add a left border to the first real day of the month 142 | &.bc-calendar__day--pad + .bc-calendar__day:not(.bc-calendar__day--pad) { 143 | &::after { 144 | background-color: $gray; 145 | content: ''; 146 | display: block; 147 | position: absolute; 148 | bottom: -1px; 149 | left: -1px; 150 | top: -1px; 151 | width: 1px; 152 | } 153 | } 154 | 155 | //