34 |
You can configure this directive to accept input in any format understood by moment.js, however, when you leave
35 | the field it will be displayed in
36 | the specified format.
37 |
38 |
The background of the input box will be red whenever the input is an invalid date.
39 |
40 |
58 |
59 |
83 |
84 |
112 |
113 |
139 |
Any invalid dates will have a ng-invalid class assigned by Angular
140 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /* globals require, __dirname */
2 | /* jshint node:true */
3 | 'use strict'
4 |
5 | var gulp = require('gulp')
6 | var lodash = require('lodash')
7 | var path = require('path')
8 | var paths = require('./paths')
9 | var plato = require('plato')
10 | var Server = require('karma').Server
11 | var standard = require('gulp-standard')
12 |
13 | var karmaConfig = path.join(__dirname, 'karma.conf.js')
14 |
15 | gulp.task('clean', function () {
16 | var del = require('del')
17 | return del([
18 | 'build'
19 | ])
20 | })
21 |
22 | gulp.task('complexity', function (done) {
23 | var callback = function () {
24 | done()
25 | }
26 |
27 | plato.inspect(paths.lint, 'build/complexity', {title: 'prerender', recurse: true}, callback)
28 | })
29 |
30 | gulp.task('lint', function () {
31 | return gulp
32 | .src(paths.lint)
33 | .pipe(standard())
34 | .pipe(standard.reporter('default', {
35 | breakOnError: true,
36 | quiet: true
37 | }))
38 | })
39 |
40 | gulp.task('tdd', function (done) {
41 | gulp.watch(paths.all, ['jscs', 'lint'])
42 |
43 | var config = testConfig(
44 | {
45 | autoWatch: true,
46 | browsers: ['PhantomJS'],
47 | configFile: karmaConfig,
48 | singleRun: false
49 | }
50 | )
51 |
52 | var server = new Server(config, done)
53 | server.start()
54 | })
55 |
56 | gulp.task('test', ['lint'], function (done) {
57 | var config = testConfig(
58 | {
59 | configFile: karmaConfig,
60 | singleRun: true,
61 | reporters: ['progress', 'coverage', 'threshold']
62 | }
63 | )
64 |
65 | var server = new Server(config, done)
66 | server.start()
67 | })
68 |
69 | gulp.task('default', ['complexity', 'test'])
70 |
71 | var testConfig = function (options) {
72 | var travisDefaultOptions = {
73 | browsers: ['Firefox'],
74 | reporters: ['dots', 'coverage', 'threshold']
75 | }
76 |
77 | var travisOptions = process.env.TRAVIS && travisDefaultOptions
78 |
79 | return lodash.assign(options, travisOptions)
80 | }
81 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | /* globals require */
2 | /* jshint node:true */
3 |
4 | /**
5 | * @license angular-bootstrap-datetimepicker
6 | * (c) 2013 Knight Rider Consulting, Inc. http://www.knightrider.com
7 | * License: MIT
8 | */
9 |
10 | /**
11 | *
12 | * @author Dale "Ducky" Lotts
13 | * @since 7/21/13
14 | */
15 |
16 | var paths = require('./paths')
17 |
18 | module.exports = function (config) {
19 | 'use strict'
20 |
21 | config.set({
22 |
23 | frameworks: ['jasmine'],
24 |
25 | plugins: [
26 | 'karma-jasmine',
27 | 'karma-chrome-launcher',
28 | 'karma-firefox-launcher',
29 | 'karma-phantomjs-launcher',
30 | 'karma-coverage',
31 | 'karma-threshold-reporter'
32 | ],
33 |
34 | files: paths.all,
35 |
36 | // list of files to exclude
37 | exclude: [],
38 |
39 | preprocessors: {
40 | 'src/**/*.js': ['coverage']
41 | },
42 |
43 | // optionally, configure the reporter
44 | coverageReporter: {
45 | reporters: [
46 | {type: 'lcov', dir: 'build/coverage', subdir: '.'},
47 | {type: 'json', dir: 'build/coverage'},
48 | {type: 'html', dir: 'build/coverage'}
49 | ]
50 | },
51 |
52 | // test results reporter to use
53 | // possible values: 'dots', 'progress', 'junit'
54 | reporters: ['progress', 'coverage'],
55 |
56 | // the configure thresholds
57 | thresholdReporter: {
58 | statements: 100,
59 | branches: 100,
60 | functions: 100,
61 | lines: 100
62 | },
63 |
64 | // web server port
65 | port: 9876,
66 |
67 | // cli runner port
68 | runnerPort: 9100,
69 |
70 | // enable / disable colors in the output (reporters and logs)
71 | colors: true,
72 |
73 | // level of logging
74 | // possible values: LOG_DISABLE || LOG_ERROR || LOG_WARN || LOG_INFO || LOG_DEBUG
75 | logLevel: config.LOG_INFO,
76 |
77 | // enable / disable watching file and executing tests whenever any file changes
78 | autoWatch: false,
79 |
80 | // Start these browsers, currently available:
81 | // - Chrome
82 | // - ChromeCanary
83 | // - Firefox
84 | // - Opera
85 | // - Safari (only Mac)
86 | // - PhantomJS
87 | // - IE (only Windows)
88 | browsers: ['PhantomJS'],
89 |
90 | // If browser does not capture in given timeout [ms], kill it
91 | captureTimeout: 60000,
92 |
93 | // Continuous Integration mode
94 | // if true, it capture browsers, run tests and exit
95 | singleRun: false
96 | })
97 | }
98 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "angular-date-time-input",
3 | "description": "This angular directive allows users to manually enter date-time values in a variety of formats but displays the date value in the specified format.",
4 | "author": "https://github.com/dalelotts/angular-date-time-input/graphs/contributors",
5 | "license": "MIT",
6 | "homepage": "https://github.com/dalelotts/angular-date-time-input",
7 | "main": "src/dateTimeInput.js",
8 | "keywords": [
9 | "angular",
10 | "date input",
11 | "date",
12 | "directive",
13 | "moment",
14 | "time input",
15 | "time"
16 | ],
17 | "dependencies": {
18 | "angular": "^1.x",
19 | "moment": "^2.15.x"
20 | },
21 | "devDependencies": {
22 | "angular-mocks": "^1.x",
23 | "coveralls": "^2.11.14",
24 | "cz-conventional-changelog": "^1.2.0",
25 | "eslint": "^3.8.1",
26 | "gulp": "^3.9.1",
27 | "gulp-htmlmin": "^3.0.0",
28 | "gulp-jscs": "^4.0.0",
29 | "gulp-jshint": "^2.0.1",
30 | "gulp-standard": "^8.0.2",
31 | "jasmine-core": "^2.5.2",
32 | "jquery": "^3.1.1",
33 | "jshint": "^2.9.4",
34 | "jshint-stylish": "^2.2.1",
35 | "karma": "^1.3.0",
36 | "karma-chrome-launcher": "^2.0.0",
37 | "karma-coverage": "^1.1.1",
38 | "karma-firefox-launcher": "^1.0.0",
39 | "karma-jasmine": "^1.0.2",
40 | "karma-phantomjs-launcher": "^1.0.2",
41 | "karma-threshold-reporter": "^0.1.15",
42 | "karma-webpack": "^1.8.0",
43 | "lodash": "^4.16.4",
44 | "phantomjs-prebuilt": "^2.1.13",
45 | "plato": "^1.7.0",
46 | "run-browser": "^2.0.2",
47 | "semantic-release": "^6.3.0",
48 | "standard": "^8.4.0",
49 | "tape": "^4.6.2",
50 | "webpack": "^1.13.2"
51 | },
52 | "scripts": {
53 | "coverage:upload": "cat build/coverage/lcov.info | coveralls",
54 | "semantic-release": "semantic-release pre && npm publish && semantic-release post",
55 | "test": "npm run test-browserify && npm run test-webpack && gulp",
56 | "test-browserify": "run-browser test/commonjs/browserify.test.js -b",
57 | "test-webpack": "karma start test/webpack/karma.conf.js"
58 | },
59 | "repository": {
60 | "type": "git",
61 | "url": "https://github.com/dalelotts/angular-date-time-input.git"
62 | },
63 | "config": {
64 | "commitizen": {
65 | "path": "./node_modules/cz-conventional-changelog"
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/paths.js:
--------------------------------------------------------------------------------
1 | /* jshint node:true */
2 |
3 | var nodeModules = [
4 | 'node_modules/jquery/dist/jquery.js',
5 | 'node_modules/moment/moment.js',
6 | 'node_modules/angular/angular.js',
7 | 'node_modules/angular-mocks/angular-mocks.js'
8 | ]
9 | var bumpFiles = ['package.json', 'bower.json', 'README.md', 'src/js/*.js']
10 | var miscFiles = ['GruntFile.js', 'gulpfile.js', 'karma.conf.js', 'paths.js']
11 | var demoFiles = []
12 | var sourceFiles = ['src/**/*.js']
13 | var testFiles = ['test/**/*.spec.js']
14 |
15 | module.exports = {
16 | all: nodeModules.concat(sourceFiles).concat(testFiles).concat(demoFiles),
17 | app: sourceFiles,
18 | bump: bumpFiles,
19 | lint: miscFiles.concat(sourceFiles).concat(testFiles).concat(miscFiles),
20 | src: sourceFiles,
21 | test: testFiles
22 | }
23 |
--------------------------------------------------------------------------------
/src/dateTimeInput.js:
--------------------------------------------------------------------------------
1 | /* globals define, module, require, angular, moment */
2 | /* jslint vars:true */
3 |
4 | /**
5 | * @license angular-date-time-input
6 | * (c) 2013-2015 Knight Rider Consulting, Inc. http://www.knightrider.com
7 | * License: MIT
8 | *
9 | * @author Dale "Ducky" Lotts
10 | * @since 2013-Sep-23
11 | */
12 |
13 | ;(function (root, factory) {
14 | 'use strict'
15 | /* istanbul ignore if */
16 | if (typeof module !== 'undefined' && module.exports) {
17 | var ng = typeof angular === 'undefined' ? require('angular') : angular
18 | var mt = typeof moment === 'undefined' ? require('moment') : moment
19 | factory(ng, mt)
20 | module.exports = 'ui.bootstrap.datetimepicker'
21 | /* istanbul ignore next */
22 | } else if (typeof define === 'function' && /* istanbul ignore next */ define.amd) {
23 | define(['angular', 'moment'], factory)
24 | } else {
25 | factory(root.angular, root.moment)
26 | }
27 | }(this, function (angular, moment) {
28 | 'use strict'
29 | angular.module('ui.dateTimeInput', [])
30 | .service('dateTimeParserFactory', DateTimeParserFactoryService)
31 | .directive('dateTimeInput', DateTimeInputDirective)
32 |
33 | DateTimeParserFactoryService.$inject = []
34 |
35 | function DateTimeParserFactoryService () {
36 | return function ParserFactory (modelType, inputFormats, dateParseStrict) {
37 | var result
38 | // Behaviors
39 | switch (modelType) {
40 | case 'Date':
41 | result = handleEmpty(dateParser)
42 | break
43 | case 'moment':
44 | result = handleEmpty(momentParser)
45 | break
46 | case 'milliseconds':
47 | result = handleEmpty(millisecondParser)
48 | break
49 | default: // It is assumed that the modelType is a formatting string.
50 | result = handleEmpty(stringParserFactory(modelType))
51 | }
52 |
53 | return result
54 |
55 | function handleEmpty (delegate) {
56 | return function (viewValue) {
57 | if (angular.isUndefined(viewValue) || viewValue === '' || viewValue === null) {
58 | return null
59 | } else {
60 | return delegate(viewValue)
61 | }
62 | }
63 | }
64 |
65 | function dateParser (viewValue) {
66 | return momentParser(viewValue).toDate()
67 | }
68 |
69 | function momentParser (viewValue) {
70 | return moment(viewValue, inputFormats, moment.locale(), dateParseStrict)
71 | }
72 |
73 | function millisecondParser (viewValue) {
74 | return moment.utc(viewValue, inputFormats, moment.locale(), dateParseStrict).valueOf()
75 | }
76 |
77 | function stringParserFactory (modelFormat) {
78 | return function stringParser (viewValue) {
79 | return momentParser(viewValue).format(modelFormat)
80 | }
81 | }
82 | }
83 | }
84 |
85 | DateTimeInputDirective.$inject = ['dateTimeParserFactory']
86 |
87 | function DateTimeInputDirective (dateTimeParserFactory) {
88 | return {
89 | require: 'ngModel',
90 | restrict: 'A',
91 | scope: {
92 | 'dateFormats': '='
93 | },
94 | link: linkFunction
95 | }
96 |
97 | function linkFunction (scope, element, attrs, controller) {
98 | // validation
99 | if (angular.isDefined(scope.dateFormats) && !angular.isString(scope.dateFormats) && !angular.isArray(scope.dateFormats)) {
100 | throw new Error('date-formats must be a single string or an array of strings i.e. date-formats="[\'YYYY-MM-DD\']" ')
101 | }
102 |
103 | if (angular.isDefined(attrs.modelType) && (!angular.isString(attrs.modelType) || attrs.modelType.length === 0)) {
104 | throw new Error('model-type must be "Date", "moment", "milliseconds", or a moment format string')
105 | }
106 |
107 | // variables
108 | var displayFormat = attrs.dateTimeInput || moment.defaultFormat
109 |
110 | var dateParseStrict = (attrs.dateParseStrict === undefined || attrs.dateParseStrict === 'true')
111 |
112 | var modelType = (attrs.modelType || 'Date')
113 |
114 | var inputFormats = [attrs.dateTimeInput, modelType].concat(scope.dateFormats).concat([moment.ISO_8601]).filter(unique)
115 | var formatterFormats = [modelType].concat(inputFormats).filter(unique)
116 |
117 | // Behaviors
118 | controller.$parsers.unshift(dateTimeParserFactory(modelType, inputFormats, dateParseStrict))
119 |
120 | controller.$formatters.push(formatter)
121 |
122 | controller.$validators.dateTimeInput = validator
123 |
124 | element.bind('blur', applyFormatters)
125 |
126 | // Implementation
127 |
128 | function unique (value, index, self) {
129 | return ['Date', 'moment', 'milliseconds', undefined].indexOf(value) === -1 &&
130 | self.indexOf(value) === index
131 | }
132 |
133 | function validator (modelValue, viewValue) {
134 | if (angular.isUndefined(viewValue) || viewValue === '' || viewValue === null) {
135 | return true
136 | }
137 | return moment(viewValue, inputFormats, moment.locale(), dateParseStrict).isValid()
138 | }
139 |
140 | function formatter (modelValue) {
141 | if (angular.isUndefined(modelValue) || modelValue === '' || modelValue === null) {
142 | return null
143 | }
144 |
145 | if (angular.isDate(modelValue)) {
146 | return moment(modelValue).format(displayFormat)
147 | } else if (angular.isNumber(modelValue)) {
148 | return moment.utc(modelValue).format(displayFormat)
149 | }
150 | return moment(modelValue, formatterFormats, moment.locale(), dateParseStrict).format(displayFormat)
151 | }
152 |
153 | function applyFormatters () {
154 | controller.$viewValue = controller.$formatters.filter(keepAll).reverse().reduce(applyFormatter, controller.$modelValue)
155 | controller.$render()
156 |
157 | function keepAll () {
158 | return true
159 | }
160 |
161 | function applyFormatter (memo, formatter) {
162 | return formatter(memo)
163 | }
164 | }
165 | }
166 | }
167 | })); // eslint-disable-line semi
168 |
169 |
--------------------------------------------------------------------------------
/test/commonjs/browserify.test.js:
--------------------------------------------------------------------------------
1 | /*globals require */
2 | /**
3 | * @license angular-bootstrap-datetimepicker
4 | * Copyright 2015 Knight Rider Consulting, Inc. http://www.knightrider.com
5 | * License: MIT
6 | */
7 |
8 | /** This file is intentionally named browserify.test.js so that it is not picked up by the karma runner **/
9 |
10 | var angular = require('angular')
11 | var tapeTest = require('tape')
12 |
13 | tapeTest('can load module after requiring', function (t) {
14 | 'use strict'
15 |
16 | function loadModule() {
17 | angular.module('ui.dateTimeInput')
18 | }
19 |
20 | t.throws(loadModule)
21 | require('../../')
22 | t.doesNotThrow(loadModule)
23 | t.end()
24 | })
25 |
--------------------------------------------------------------------------------
/test/en/dateTimeInput.spec.js:
--------------------------------------------------------------------------------
1 | /* globals moment, module, describe, it, expect, beforeEach, inject */
2 | /**
3 | * @license angular-date-time-input
4 | * (c) 2015 Knight Rider Consulting, Inc. http://www.knightrider.com
5 | * License: MIT
6 | *
7 | * @author Dale "Ducky" Lotts
8 | * @since 2013-Sep-23
9 | */
10 |
11 | describe('date-time-input', function () {
12 | 'use strict'
13 | var compiler
14 | var rootScope
15 |
16 | beforeEach(module('ui.dateTimeInput'))
17 |
18 | beforeEach(inject(function ($compile, $rootScope) {
19 | moment.locale('en')
20 | rootScope = $rootScope
21 | compiler = $compile
22 | }))
23 |
24 | describe('valid configuration', function () {
25 | it('requires ngModel', function () {
26 | var compile = function () {
27 | compiler('