├── .bowerrc ├── .esformatter ├── .eslintignore ├── .eslintrc ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bower.json ├── demo └── index.html ├── dist ├── date.js └── date.js.map ├── karma.conf.js ├── package.json ├── src ├── date.js └── date.spec.js └── webpack.config.js /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "demo/bower_components" 3 | } 4 | -------------------------------------------------------------------------------- /.esformatter: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "esformatter-semicolons", 4 | "esformatter-add-trailing-commas", 5 | "esformatter-quotes", 6 | "esformatter-braces", 7 | "esformatter-spaced-lined-comment" 8 | ], 9 | "quotes": { 10 | "type": "single" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | ./dist/*.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "ecmaFeatures": { 4 | "arrowFunctions": true, 5 | "blockBindings": true, 6 | "classes": true, 7 | "defaultParams": true, 8 | "destructuring": true, 9 | "forOf": true, 10 | "generators": false, 11 | "modules": true, 12 | "objectLiteralComputedProperties": true, 13 | "objectLiteralDuplicateProperties": false, 14 | "objectLiteralShorthandMethods": true, 15 | "objectLiteralShorthandProperties": true, 16 | "restParams": true, 17 | "spread": true, 18 | "superInFunctions": true, 19 | "templateStrings": true, 20 | "jsx": true 21 | }, 22 | 23 | "env": { 24 | "jasmine": true, 25 | "node": true, 26 | "mocha": true, 27 | "browser": true, 28 | "builtin": true 29 | }, 30 | "globals": { 31 | "angular": true, 32 | "$": true, 33 | "inject": true, 34 | }, 35 | "rules": { 36 | "block-scoped-var": 2, 37 | "camelcase": 2, 38 | "comma-style": [ 39 | 2, 40 | "last" 41 | ], 42 | "curly": [ 43 | 2, 44 | "all" 45 | ], 46 | "dot-notation": [ 47 | 2, 48 | { 49 | "allowKeywords": true 50 | } 51 | ], 52 | "eqeqeq": [ 53 | 2, 54 | "allow-null" 55 | ], 56 | "guard-for-in": 2, 57 | "new-cap": 2, 58 | "no-bitwise": 2, 59 | "no-caller": 2, 60 | "no-cond-assign": [ 61 | 2, 62 | "except-parens" 63 | ], 64 | "no-debugger": 2, 65 | "no-empty": 2, 66 | "no-eval": 2, 67 | "no-extend-native": 2, 68 | "no-extra-parens": 2, 69 | "no-irregular-whitespace": 2, 70 | "no-iterator": 2, 71 | "no-loop-func": 2, 72 | "no-multi-str": 2, 73 | "no-new": 2, 74 | "no-plusplus": 2, 75 | "no-proto": 2, 76 | "no-script-url": 2, 77 | "no-sequences": 2, 78 | "no-shadow": 2, 79 | "no-undef": 2, 80 | "no-unused-vars": [2, 81 | {"vars": "all", "varsIgnorePattern": "^_"} 82 | ], 83 | "no-with": 2, 84 | "quotes": [ 85 | 2, 86 | "single" 87 | ], 88 | "semi": [ 89 | 0, 90 | "never" 91 | ], 92 | "strict": 2, 93 | "valid-typeof": 2, 94 | "wrap-iife": [ 95 | 2, 96 | "inside" 97 | ] 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 1. Describe the problem and the steps to reproduce 4 | 2. Describe the expected result 5 | 3. Please create a jsbin/plunkr/etc and preferably non-anonymous so that it does not expire 6 | 4. Which version of ui-date/angular are you using? 7 | 5. Describe the debugging that has been performed and where in the code you suspect the issue may be 8 | 6. Any other useful information? 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | components/ 2 | node_modules/ 3 | *bower_components/ 4 | demo/assets/date.js 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.12" 4 | 5 | before_install: 6 | - export DISPLAY=:99.0 7 | - sh -e /etc/init.d/xvfb start 8 | #- npm install -g bower grunt-cli 9 | #- npm install 10 | #- bower install 11 | 12 | #script: "grunt" 13 | sudo: false 14 | 15 | cache: 16 | directories: 17 | - node_modules 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## [1.1.1](https://github.com/angular-ui/ui-date/compare/1.0.1...v1.1.1) (2016-03-31) 3 | 4 | ### Bug Fixes 5 | 6 | * **ui-date:** Fix for ie10/11 focus issues 7 | 8 | 9 | 10 | ## [1.0.1](https://github.com/angular-ui/ui-date/compare/1.0.0...v1.0.1) (2016-03-31) 11 | 12 | 13 | 14 | # [1.0.0](https://github.com/angular-ui/ui-date/compare/1.0.0-beta.3...v1.0.0) (2016-02-18) 15 | 16 | 17 | ### Bug Fixes 18 | 19 | * **ui-date:** Validator is ok for blank or null dates ([93e6141](https://github.com/angular-ui/ui-date/commit/93e6141)) 20 | 21 | 22 | 23 | 24 | # [1.0.0-beta.0](https://github.com/angular-ui/ui-date/compare/0.0.11...v1.0.0-beta.0) (2015-12-09) 25 | 26 | Update build to support umd and webpack. 27 | 28 | 29 | 30 | ## [0.0.11](https://github.com/angular-ui/ui-date/compare/0.0.10...v0.0.11) (2015-12-03) 31 | 32 | 33 | ### Bug Fixes 34 | 35 | * **date:** Prevent jquery datepicker formatDate exception ([10c0e54](https://github.com/angular-ui/ui-date/commit/10c0e54)), closes [#48](https://github.com/angular-ui/ui-date/issues/48) 36 | 37 | 38 | 39 | 40 | ## [0.0.10](https://github.com/angular-ui/ui-date/compare/0.0.8...v0.0.10) (2015-11-26) 41 | 42 | 43 | ### Features 44 | 45 | * **date:** common js support ([7e94737](https://github.com/angular-ui/ui-date/commit/7e94737)) 46 | 47 | 48 | 49 | 50 | ## [0.0.8](https://github.com/angular-ui/ui-date/compare/0.0.7...0.0.8) (2015-06-11) 51 | 52 | 53 | ### Bug Fixes 54 | 55 | * **setViewValue:** Don't lose time info on update ([af19760](https://github.com/angular-ui/ui-date/commit/af19760)) 56 | 57 | 58 | 59 | 60 | ## [0.0.7](https://github.com/angular-ui/ui-date/compare/0.0.6...0.0.7) (2015-01-08) 61 | 62 | 63 | ### Bug Fixes 64 | 65 | * **date:** destroy datepicker when element is removed ([26f36d2](https://github.com/angular-ui/ui-date/commit/26f36d2)), closes [#23](https://github.com/angular-ui/ui-date/issues/23) [#61](https://github.com/angular-ui/ui-date/issues/61) [#79](https://github.com/angular-ui/ui-date/issues/79) 66 | 67 | 68 | 69 | 70 | ## [0.0.6](https://github.com/angular-ui/ui-date/compare/0.0.5...0.0.6) (2014-11-12) 71 | 72 | 73 | 74 | 75 | 76 | ## [0.0.5](https://github.com/angular-ui/ui-date/compare/0.0.4...0.0.5) (2014-11-10) 77 | 78 | 79 | 80 | 81 | 82 | ## [0.0.4](https://github.com/angular-ui/ui-date/compare/0.0.3...0.0.4) (2014-08-06) 83 | 84 | 85 | ### Bug Fixes 86 | 87 | * **setViewValue:** only update the model with the real date on blur ([5f24c7c](https://github.com/angular-ui/ui-date/commit/5f24c7c)), closes [#18](https://github.com/angular-ui/ui-date/issues/18) 88 | 89 | 90 | 91 | 92 | ## [0.0.3](https://github.com/angular-ui/ui-date/compare/0.0.2...0.0.3) (2013-06-19) 93 | 94 | 95 | 96 | 97 | 98 | ## [0.0.2](https://github.com/angular-ui/ui-date/compare/0.0.1...0.0.2) (2013-03-25) 99 | 100 | 101 | ### Bug Fixes 102 | 103 | * **date-format:** should return null not undefined if viewValue is empty string ([3768719](https://github.com/angular-ui/ui-date/commit/3768719)) 104 | 105 | 106 | 107 | 108 | ## [0.0.1](https://github.com/angular-ui/ui-date/compare/bcb1fd9...0.0.1) (2013-03-16) 109 | 110 | 111 | ### Bug Fixes 112 | 113 | * **component:** Corrected path to main js file ([8947cfd](https://github.com/angular-ui/ui-date/commit/8947cfd)) 114 | * **date:** ensure date picker doesn't pop up again on IE ([2c62c92](https://github.com/angular-ui/ui-date/commit/2c62c92)) 115 | * **test:** remove unwanted dump() statement ([e8586e4](https://github.com/angular-ui/ui-date/commit/e8586e4)) 116 | * **tests:** Attempted to fix most tests and added new angular-module component ([b4d243d](https://github.com/angular-ui/ui-date/commit/b4d243d)) 117 | * **tests:** Forgot .travis.yml ([0bb9ed7](https://github.com/angular-ui/ui-date/commit/0bb9ed7)) 118 | * **tests:** Made build status clickable ([e77ca50](https://github.com/angular-ui/ui-date/commit/e77ca50)) 119 | * **ui.date module:** add [] to create module ([8581ede](https://github.com/angular-ui/ui-date/commit/8581ede)) 120 | * **uiDateConfig:** add directive specific config ([bcb1fd9](https://github.com/angular-ui/ui-date/commit/bcb1fd9)) 121 | 122 | ### Features 123 | 124 | * **tests:** Added image for build status ([aad7b9f](https://github.com/angular-ui/ui-date/commit/aad7b9f)) 125 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Got a question or problem? 2 | 3 | Please, do not open issues for the general support questions as we want to keep GitHub issues for bug reports and feature requests. You've got much better chances of getting your question answered on [StackOverflow](http://stackoverflow.com/). 4 | 5 | StackOverflow is a much better place to ask questions since: 6 | * there are hundreds of people willing to help on StackOverflow 7 | * questions and answers stay available for public viewing so your question / answer might help someone else 8 | * the StackOverflow voting system assures that the best answers are prominently visible. 9 | 10 | To save your and our time we will be systematically closing all the issues that are requests for general support and redirecting people to StackOverflow. 11 | 12 | ## You think you've found a bug? 13 | 14 | Oh, we are ashamed and want to fix it asap! But before fixing a bug we need to reproduce and confirm it. In order to reproduce bugs we will systematically ask you to provide a _minimal_ reproduce scenario using http://plnkr.co/ or [jsbin](http://jsbin.com). Having a live reproduce scenario gives us wealth of important information without going back & forth to you with additional questions like: 15 | * version of AngularJS used 16 | * version of this library that you are using 17 | * 3rd-party libraries used, if any 18 | * and most importantly - a use-case that fails 19 | 20 | A minimal reproduce scenario using http://plnkr.co/ allows us to quickly confirm a bug (or point out coding problem) as well as confirm that we are fixing the right problem. 21 | 22 | We will be insisting on a minimal reproduce scenario in order to save maintainers time and ultimately be able to fix more bugs. Interestingly, from our experience users often find coding problems themselves while preparing a minimal plunk. We understand that sometimes it might be hard to extract essentials bits of code from a larger code-base but we really need to isolate the problem before we can fix it. 23 | 24 | Unfortunately we are not able to investigate / fix bugs without a minimal reproduce scenario using http://plnkr.co/, so if we don't hear back from you we are going to close an issue that don't have enough info to be reproduced. 25 | 26 | ## You want to contribute some code? 27 | 28 | Fantastic, we are always looking for the quality contributions and will be happy to accept your Pull Requests as long as those adhere to some basic rules: 29 | 30 | * Please assure that you are submitting quality code, specifically make sure that: 31 | * your contribution has accompanying tests and all the tests are passing 32 | * your PR doesn't break the build; check the Travis-CI build status after opening a PR and push corrective commits if anything goes wrong 33 | * your commit messages conform to the conventions established [here](https://github.com/angular/angular.js/blob/master/CONTRIBUTING.md#commit) 34 | 35 | [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/) 36 | 37 | # Testing 38 | 39 | We use [karma](http://karma-runner.github.io/) and jshint to ensure the quality of the code. The easiest way to run these checks is the following 40 | 41 | npm install 42 | npm test 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2012 the AngularUI Team, http://angular-ui.github.com 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ui-date directive [![Build Status](https://travis-ci.org/angular-ui/ui-date.svg)](https://travis-ci.org/angular-ui/ui-date) 2 | 3 | This directive allows you to add a date-picker to your form elements. 4 | 5 | # Alternatives 6 | 7 | We recommend using the excellent [ui-bootstrap](https://angular-ui.github.io/bootstrap/) date-picker which is maintained by a larger team. 8 | 9 | WARNING: Support for this module may eventually be phased out as angular 2.0 arrives as there are no plans to move this to angular 2 at this time. 10 | 11 | # Requirements 12 | 13 | - JQuery 14 | - JQueryUI 15 | - AngularJS 16 | 17 | # Bower Usage 18 | 19 | You may use [bower](http://bower.io/) for dependency management but would recommend using webpack or browserify for modules. 20 | 21 | Install and save to bower.json by running: 22 | 23 | bower install angular-ui-date --save 24 | 25 | This will copy the ui-date files into your `bower_components` folder, along with its dependencies. 26 | 27 | Add the css: 28 | 29 | ```html 30 | 31 | ``` 32 | 33 | Load the script files in your application: 34 | 35 | ```html 36 | 37 | 38 | 39 | 40 | ``` 41 | 42 | Add the date module as a dependency to your application module: 43 | 44 | ```js 45 | angular.module('MyApp', ['ui.date']) 46 | ``` 47 | 48 | Apply the directive to your form elements: 49 | 50 | ```html 51 | 52 | ``` 53 | 54 | ## Options 55 | 56 | All the [jQueryUI DatePicker options](http://api.jqueryui.com/datepicker/) can be passed through the directive including minDate, maxDate, yearRange etc. 57 | 58 | ```js 59 | myAppModule.controller('MyController', function($scope) { 60 | $scope.dateOptions = { 61 | changeYear: true, 62 | changeMonth: true, 63 | yearRange: '1900:-0', 64 | }; 65 | }); 66 | ``` 67 | 68 | then pass through your options: 69 | 70 | ```html 71 | 72 | ``` 73 | 74 | ## Static Inline Picker 75 | 76 | If you want a static picker then simply apply the directive to a div rather than an input element. 77 | 78 | ```html 79 |
80 | ``` 81 | 82 | ## Working with ng-model 83 | 84 | The ui-date directive plays nicely with ng-model and validation directives such as ng-required. 85 | 86 | If you add the ng-model directive to same the element as ui-date then the picked date is automatically synchronized with the model value. 87 | 88 | _The ui-date directive stores and expects the model value to be a standard javascript Date object._ 89 | 90 | ## ui-date-format directive 91 | 92 | The ui-date directive only works with Date objects. 93 | If you want to pass date strings to and from the date directive via ng-model then you must use the ui-date-format directive. 94 | This directive specifies the format of the date string that will be expected in the ng-model. 95 | The format string syntax is that defined by the JQueryUI Date picker. For example 96 | 97 | ```html 98 | 99 | ``` 100 | 101 | Now you can set myDate in the controller. 102 | 103 | ```js 104 | $scope.myDate = "Thursday, 11 October, 2012"; 105 | ``` 106 | 107 | ## ng-required directive 108 | 109 | If you apply the required directive to element then the form element is invalid until a date is picked. 110 | 111 | Note: Remember that the ng-required directive must be explictly set, i.e. to "true". This is especially true on divs: 112 | 113 | ```html 114 |
115 | ``` 116 | 117 | ## focusing the next element for tabbing 118 | 119 | There is a problem with IE that re-opens the datepicker on focus(). However, this breaks tabbing. If tabbing is more 120 | important than IE for your use cases, pass in the onClose option. 121 | 122 | ```javascript 123 | myAppModule.controller('MyController', function($scope) { 124 | $scope.dateOptions = { 125 | onClose: (value, picker, $element) => { 126 | $element.focus() 127 | } 128 | }; 129 | }); 130 | ``` 131 | 132 | ## Usage with webpack 133 | 134 | Install with npm: 135 | 136 | npm install --save-dev jquery jquery-ui angular angular-ui-date 137 | 138 | Use in your app: 139 | 140 | ```javascript 141 | import angular from 'angular'; 142 | import uiDate from 'angular-ui-date'; 143 | 144 | require('jquery-ui/themes/base/minified/jquery-ui.min.css'); 145 | 146 | angular.module('MyTest', [uiDate.name]) 147 | .controller('MyCtrl', ['$scope', function($scope) { 148 | $scope.myDate = new Date('2015-11-17'); 149 | }]); 150 | ``` 151 | 152 | It is also good to ensure that jQuery is available so that angular and jquery ui can attach to it. 153 | 154 | ```javascript 155 | webpack: { 156 | plugins: [ 157 | new webpack.ProvidePlugin({ 158 | 'window.jQuery': 'jquery', 159 | }), 160 | ] 161 | } 162 | ``` 163 | 164 | another method of making jQuery recognized is to use the webpack expose-loader to expose it both as $ and jQuery 165 | 166 | ```javascript 167 | webpack: { 168 | module: { 169 | loaders: [ 170 | // it helps angular to have jQuery exposed so that it uses $ instead of jqLite 171 | { 172 | test: require.resolve('jquery'), 173 | loader: 'expose?$!expose?jQuery', 174 | }, 175 | ] 176 | } 177 | } 178 | ``` 179 | ## Need help? 180 | Need help using UI date? 181 | 182 | * Ask a question in [StackOverflow](http://stackoverflow.com/) under the [angular-ui-date](http://stackoverflow.com/questions/tagged/angular-ui-date) tag. 183 | 184 | **Please do not create new issues in this repository to ask questions about using UI date** 185 | 186 | ## Found a bug? 187 | Please take a look at [CONTRIBUTING.md](CONTRIBUTING.md#you-think-youve-found-a-bug). 188 | 189 | # Contributing to the project 190 | 191 | We are always looking for the quality contributions! Please check the [CONTRIBUTING.md](CONTRIBUTING.md) for the contribution guidelines. 192 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-ui-date", 3 | "version": "1.1.1", 4 | "description": "This directive allows you to add a date-picker to your form elements.", 5 | "author": "https://github.com/angular-ui/ui-date/graphs/contributors", 6 | "license": "MIT", 7 | "homepage": "http://angular-ui.github.com/ui-date", 8 | "main": "./dist/date.js", 9 | "ignore": [ 10 | "**/.*", 11 | "node_modules", 12 | "bower_components", 13 | "test*", 14 | "demo*", 15 | "gruntFile.js", 16 | "package.json" 17 | ], 18 | "dependencies": { 19 | "angular": "^1.3.x", 20 | "jquery-ui": "^1.9" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | AngularUI - Date Picker Demo 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 |
{{ aDate }}
19 |
20 | Read only 21 |
22 | 23 | 24 |
25 | Required date: 26 |
27 | The required date field is required. 28 |
29 |
30 |
31 | 32 |
33 | 34 | 35 | 36 | 37 | 38 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /dist/date.js: -------------------------------------------------------------------------------- 1 | (function webpackUniversalModuleDefinition(root, factory) { 2 | if(typeof exports === 'object' && typeof module === 'object') 3 | module.exports = factory(require("jquery"), require("angular"), require("jquery-ui/datepicker")); 4 | else if(typeof define === 'function' && define.amd) 5 | define(["jquery", "angular", "jquery-ui/datepicker"], factory); 6 | else if(typeof exports === 'object') 7 | exports["angularUiDate"] = factory(require("jquery"), require("angular"), require("jquery-ui/datepicker")); 8 | else 9 | root["angularUiDate"] = factory(root["jQuery"], root["angular"], root["jquery-ui/datepicker"]); 10 | })(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_3__) { 11 | return /******/ (function(modules) { // webpackBootstrap 12 | /******/ // The module cache 13 | /******/ var installedModules = {}; 14 | 15 | /******/ // The require function 16 | /******/ function __webpack_require__(moduleId) { 17 | 18 | /******/ // Check if module is in cache 19 | /******/ if(installedModules[moduleId]) 20 | /******/ return installedModules[moduleId].exports; 21 | 22 | /******/ // Create a new module (and put it into the cache) 23 | /******/ var module = installedModules[moduleId] = { 24 | /******/ exports: {}, 25 | /******/ id: moduleId, 26 | /******/ loaded: false 27 | /******/ }; 28 | 29 | /******/ // Execute the module function 30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 31 | 32 | /******/ // Flag the module as loaded 33 | /******/ module.loaded = true; 34 | 35 | /******/ // Return the exports of the module 36 | /******/ return module.exports; 37 | /******/ } 38 | 39 | 40 | /******/ // expose the modules object (__webpack_modules__) 41 | /******/ __webpack_require__.m = modules; 42 | 43 | /******/ // expose the module cache 44 | /******/ __webpack_require__.c = installedModules; 45 | 46 | /******/ // __webpack_public_path__ 47 | /******/ __webpack_require__.p = "assets"; 48 | 49 | /******/ // Load entry module and return exports 50 | /******/ return __webpack_require__(0); 51 | /******/ }) 52 | /************************************************************************/ 53 | /******/ ([ 54 | /* 0 */ 55 | /***/ function(module, exports, __webpack_require__) { 56 | 57 | 'use strict'; 58 | 59 | Object.defineProperty(exports, "__esModule", { 60 | value: true 61 | }); 62 | 63 | var _jquery = __webpack_require__(1); 64 | 65 | var _jquery2 = _interopRequireDefault(_jquery); 66 | 67 | var _angular = __webpack_require__(2); 68 | 69 | var _angular2 = _interopRequireDefault(_angular); 70 | 71 | var _datepicker = __webpack_require__(3); 72 | 73 | var _datepicker2 = _interopRequireDefault(_datepicker); 74 | 75 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 76 | 77 | // sets up jQuery with the datepicker plugin 78 | 79 | exports.default = _angular2.default.module('ui.date', []).constant('uiDateConfig', {}).constant('uiDateFormatConfig', '').factory('uiDateConverter', ['uiDateFormatConfig', function (uiDateFormatConfig) { 80 | return { 81 | stringToDate: stringToDate, 82 | dateToString: dateToString 83 | }; 84 | 85 | //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207 86 | function timezoneToOffset(timezone, fallback) { 87 | timezone = timezone.replace(/:/g, ''); 88 | var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 89 | return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 90 | } 91 | 92 | function addDateMinutes(date, minutes) { 93 | date = new Date(date.getTime()); 94 | date.setMinutes(date.getMinutes() + minutes); 95 | return date; 96 | } 97 | 98 | function convertTimezoneToLocal(date, timezone, reverse) { 99 | reverse = reverse ? -1 : 1; 100 | var dateTimezoneOffset = date.getTimezoneOffset(); 101 | var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 102 | return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 103 | } 104 | 105 | function doTZ(date, timezone, reverse) { 106 | return timezone ? convertTimezoneToLocal(date, timezone, reverse) : date; 107 | } 108 | 109 | function dateToString(uiDateFormat, value) { 110 | var dateFormat = uiDateFormat || uiDateFormatConfig; 111 | if (value) { 112 | if (dateFormat) { 113 | try { 114 | return _jquery2.default.datepicker.formatDate(dateFormat, value); 115 | } catch (formatException) { 116 | return undefined; 117 | } 118 | } 119 | 120 | if (value.toISOString) { 121 | return value.toISOString(); 122 | } 123 | } 124 | return null; 125 | } 126 | 127 | function stringToDate(dateFormat, valueToParse, timezone) { 128 | dateFormat = dateFormat || uiDateFormatConfig; 129 | 130 | if (_angular2.default.isDate(valueToParse) && !isNaN(valueToParse)) { 131 | return doTZ(valueToParse, timezone); 132 | } 133 | 134 | if (_angular2.default.isString(valueToParse)) { 135 | if (dateFormat) { 136 | return doTZ(_jquery2.default.datepicker.parseDate(dateFormat, valueToParse), timezone); 137 | } 138 | 139 | var isoDate = new Date(valueToParse); 140 | return isNaN(isoDate.getTime()) ? null : doTZ(isoDate, timezone); 141 | } 142 | 143 | if (_angular2.default.isNumber(valueToParse)) { 144 | // presumably timestamp to date object 145 | return doTZ(new Date(valueToParse), timezone); 146 | } 147 | 148 | return null; 149 | } 150 | }]).directive('uiDate', ['uiDateConfig', 'uiDateConverter', function uiDateDirective(uiDateConfig, uiDateConverter) { 151 | 152 | return { 153 | require: '?ngModel', 154 | link: function link(scope, element, attrs, controller) { 155 | 156 | var $element = (0, _jquery2.default)(element); 157 | 158 | var getOptions = function getOptions() { 159 | return _angular2.default.extend({}, uiDateConfig, scope.$eval(attrs.uiDate)); 160 | }; 161 | var initDateWidget = function initDateWidget() { 162 | var showing = false; 163 | var opts = getOptions(); 164 | var timezone = controller ? controller.$options.getOption('timezone') : null; 165 | 166 | function setVal(forcedUpdate) { 167 | var keys = ['Hours', 'Minutes', 'Seconds', 'Milliseconds']; 168 | var isDate = _angular2.default.isDate(controller.$modelValue); 169 | var preserve = {}; 170 | 171 | if (!forcedUpdate && isDate && controller.$modelValue.toDateString() === $element.datepicker('getDate').toDateString()) { 172 | return; 173 | } 174 | 175 | if (isDate) { 176 | _angular2.default.forEach(keys, function (key) { 177 | preserve[key] = controller.$modelValue['get' + key](); 178 | }); 179 | } 180 | 181 | var newViewValue = $element.datepicker('getDate'); 182 | 183 | if (isDate) { 184 | _angular2.default.forEach(keys, function (key) { 185 | newViewValue['set' + key](preserve[key]); 186 | }); 187 | } 188 | 189 | controller.$setViewValue(newViewValue); 190 | } 191 | 192 | // If we have a controller (i.e. ngModelController) then wire it up 193 | if (controller) { 194 | // Set the view value in a $apply block when users selects 195 | // (calling directive user's function too if provided) 196 | var _onSelect = opts.onSelect || _angular2.default.noop; 197 | opts.onSelect = function (value, picker) { 198 | scope.$apply(function () { 199 | showing = true; 200 | setVal(); 201 | $element.blur(); 202 | _onSelect(value, picker, $element); 203 | }); 204 | }; 205 | 206 | var _beforeShow = opts.beforeShow || _angular2.default.noop; 207 | opts.beforeShow = function (input, picker) { 208 | showing = true; 209 | _beforeShow(input, picker, $element); 210 | }; 211 | 212 | var _onClose = opts.onClose || _angular2.default.noop; 213 | opts.onClose = function (value, picker) { 214 | showing = false; 215 | _onClose(value, picker, $element); 216 | }; 217 | 218 | element.on('focus', function (focusEvent) { 219 | if (attrs.readonly) { 220 | focusEvent.stopImmediatePropagation(); 221 | } 222 | }); 223 | 224 | $element.off('blur.datepicker').on('blur.datepicker', function () { 225 | if (!showing) { 226 | scope.$apply(function () { 227 | $element.datepicker('setDate', $element.datepicker('getDate')); 228 | setVal(); 229 | }); 230 | } 231 | }); 232 | 233 | controller.$validators.uiDateValidator = function uiDateValidator(modelValue, viewValue) { 234 | return viewValue === null || viewValue === '' || _angular2.default.isDate(uiDateConverter.stringToDate(attrs.uiDateFormat, viewValue)); 235 | }; 236 | 237 | controller.$parsers.push(function uiDateParser(valueToParse) { 238 | return uiDateConverter.stringToDate(attrs.uiDateFormat, valueToParse, timezone); 239 | }); 240 | 241 | // Update the date picker when the model changes 242 | controller.$render = function () { 243 | // Force a render to override whatever is in the input text box 244 | if (_angular2.default.isDate(controller.$modelValue) === false && _angular2.default.isString(controller.$modelValue)) { 245 | controller.$modelValue = uiDateConverter.stringToDate(attrs.uiDateFormat, controller.$modelValue, timezone); 246 | } 247 | $element.datepicker('setDate', controller.$modelValue); 248 | }; 249 | } 250 | // Check if the $element already has a datepicker. 251 | // 252 | 253 | if ($element.data('datepicker')) { 254 | // Updates the datepicker options 255 | $element.datepicker('option', opts); 256 | $element.datepicker('refresh'); 257 | } else { 258 | // Creates the new datepicker widget 259 | $element.datepicker(opts); 260 | 261 | // Cleanup on destroy, prevent memory leaking 262 | $element.on('$destroy', function () { 263 | $element.datepicker('hide'); 264 | $element.datepicker('destroy'); 265 | }); 266 | } 267 | 268 | if (controller) { 269 | controller.$render(); 270 | // Update the model with the value from the datepicker after parsed 271 | setVal(true); 272 | } 273 | }; 274 | 275 | // Watch for changes to the directives options 276 | scope.$watch(getOptions, initDateWidget, true); 277 | } 278 | }; 279 | }]).directive('uiDateFormat', ['uiDateConverter', function (uiDateConverter) { 280 | return { 281 | require: 'ngModel', 282 | link: function link(scope, element, attrs, modelCtrl) { 283 | var dateFormat = attrs.uiDateFormat; 284 | 285 | // Use the datepicker with the attribute value as the dateFormat string to convert to and from a string 286 | modelCtrl.$formatters.unshift(function (value) { 287 | return uiDateConverter.stringToDate(dateFormat, value); 288 | }); 289 | 290 | modelCtrl.$parsers.push(function (value) { 291 | return uiDateConverter.dateToString(dateFormat, value); 292 | }); 293 | } 294 | }; 295 | }]); 296 | 297 | /***/ }, 298 | /* 1 */ 299 | /***/ function(module, exports) { 300 | 301 | module.exports = __WEBPACK_EXTERNAL_MODULE_1__; 302 | 303 | /***/ }, 304 | /* 2 */ 305 | /***/ function(module, exports) { 306 | 307 | module.exports = __WEBPACK_EXTERNAL_MODULE_2__; 308 | 309 | /***/ }, 310 | /* 3 */ 311 | /***/ function(module, exports) { 312 | 313 | module.exports = __WEBPACK_EXTERNAL_MODULE_3__; 314 | 315 | /***/ } 316 | /******/ ]) 317 | }); 318 | ; 319 | //# sourceMappingURL=date.js.map -------------------------------------------------------------------------------- /dist/date.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"date.js","sources":["webpack:///webpack/universalModuleDefinition","webpack:///webpack/bootstrap 84f149d5c6fc85eedd54","webpack:///./src/date.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"jquery\"), require(\"angular\"), require(\"jquery-ui/datepicker\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"jquery\", \"angular\", \"jquery-ui/datepicker\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"angularUiDate\"] = factory(require(\"jquery\"), require(\"angular\"), require(\"jquery-ui/datepicker\"));\n\telse\n\t\troot[\"angularUiDate\"] = factory(root[\"jQuery\"], root[\"angular\"], root[\"jquery-ui/datepicker\"]);\n})(this, function(__WEBPACK_EXTERNAL_MODULE_1__, __WEBPACK_EXTERNAL_MODULE_2__, __WEBPACK_EXTERNAL_MODULE_3__) {\nreturn \n\n\n/** WEBPACK FOOTER **\n ** webpack/universalModuleDefinition\n **/"," \t// The module cache\n \tvar installedModules = {};\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId])\n \t\t\treturn installedModules[moduleId].exports;\n\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\texports: {},\n \t\t\tid: moduleId,\n \t\t\tloaded: false\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.loaded = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"assets\";\n\n \t// Load entry module and return exports\n \treturn __webpack_require__(0);\n\n\n\n/** WEBPACK FOOTER **\n ** webpack/bootstrap 84f149d5c6fc85eedd54\n **/","'use strict';\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\n\nvar _jquery = require('jquery');\n\nvar _jquery2 = _interopRequireDefault(_jquery);\n\nvar _angular = require('angular');\n\nvar _angular2 = _interopRequireDefault(_angular);\n\nvar _datepicker = require('jquery-ui/datepicker');\n\nvar _datepicker2 = _interopRequireDefault(_datepicker);\n\nfunction _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }\n\n// sets up jQuery with the datepicker plugin\n\nexports.default = _angular2.default.module('ui.date', []).constant('uiDateConfig', {}).constant('uiDateFormatConfig', '').factory('uiDateConverter', ['uiDateFormatConfig', function (uiDateFormatConfig) {\n return {\n stringToDate: stringToDate,\n dateToString: dateToString\n };\n\n //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207\n function timezoneToOffset(timezone, fallback) {\n timezone = timezone.replace(/:/g, '');\n var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000;\n return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset;\n }\n\n function addDateMinutes(date, minutes) {\n date = new Date(date.getTime());\n date.setMinutes(date.getMinutes() + minutes);\n return date;\n }\n\n function convertTimezoneToLocal(date, timezone, reverse) {\n reverse = reverse ? -1 : 1;\n var dateTimezoneOffset = date.getTimezoneOffset();\n var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset);\n return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset));\n }\n\n function doTZ(date, timezone, reverse) {\n return timezone ? convertTimezoneToLocal(date, timezone, reverse) : date;\n }\n\n function dateToString(uiDateFormat, value) {\n var dateFormat = uiDateFormat || uiDateFormatConfig;\n if (value) {\n if (dateFormat) {\n try {\n return _jquery2.default.datepicker.formatDate(dateFormat, value);\n } catch (formatException) {\n return undefined;\n }\n }\n\n if (value.toISOString) {\n return value.toISOString();\n }\n }\n return null;\n }\n\n function stringToDate(dateFormat, valueToParse, timezone) {\n dateFormat = dateFormat || uiDateFormatConfig;\n\n if (_angular2.default.isDate(valueToParse) && !isNaN(valueToParse)) {\n return doTZ(valueToParse, timezone);\n }\n\n if (_angular2.default.isString(valueToParse)) {\n if (dateFormat) {\n return doTZ(_jquery2.default.datepicker.parseDate(dateFormat, valueToParse), timezone);\n }\n\n var isoDate = new Date(valueToParse);\n return isNaN(isoDate.getTime()) ? null : doTZ(isoDate, timezone);\n }\n\n if (_angular2.default.isNumber(valueToParse)) {\n // presumably timestamp to date object\n return doTZ(new Date(valueToParse), timezone);\n }\n\n return null;\n }\n}]).directive('uiDate', ['uiDateConfig', 'uiDateConverter', function uiDateDirective(uiDateConfig, uiDateConverter) {\n\n return {\n require: '?ngModel',\n link: function link(scope, element, attrs, controller) {\n\n var $element = (0, _jquery2.default)(element);\n\n var getOptions = function getOptions() {\n return _angular2.default.extend({}, uiDateConfig, scope.$eval(attrs.uiDate));\n };\n var initDateWidget = function initDateWidget() {\n var showing = false;\n var opts = getOptions();\n var timezone = controller ? controller.$options.getOption('timezone') : null;\n\n function setVal(forcedUpdate) {\n var keys = ['Hours', 'Minutes', 'Seconds', 'Milliseconds'];\n var isDate = _angular2.default.isDate(controller.$modelValue);\n var preserve = {};\n\n if (!forcedUpdate && isDate && controller.$modelValue.toDateString() === $element.datepicker('getDate').toDateString()) {\n return;\n }\n\n if (isDate) {\n _angular2.default.forEach(keys, function (key) {\n preserve[key] = controller.$modelValue['get' + key]();\n });\n }\n\n var newViewValue = $element.datepicker('getDate');\n\n if (isDate) {\n _angular2.default.forEach(keys, function (key) {\n newViewValue['set' + key](preserve[key]);\n });\n }\n\n controller.$setViewValue(newViewValue);\n }\n\n // If we have a controller (i.e. ngModelController) then wire it up\n if (controller) {\n // Set the view value in a $apply block when users selects\n // (calling directive user's function too if provided)\n var _onSelect = opts.onSelect || _angular2.default.noop;\n opts.onSelect = function (value, picker) {\n scope.$apply(function () {\n showing = true;\n setVal();\n $element.blur();\n _onSelect(value, picker, $element);\n });\n };\n\n var _beforeShow = opts.beforeShow || _angular2.default.noop;\n opts.beforeShow = function (input, picker) {\n showing = true;\n _beforeShow(input, picker, $element);\n };\n\n var _onClose = opts.onClose || _angular2.default.noop;\n opts.onClose = function (value, picker) {\n showing = false;\n _onClose(value, picker, $element);\n };\n\n element.on('focus', function (focusEvent) {\n if (attrs.readonly) {\n focusEvent.stopImmediatePropagation();\n }\n });\n\n $element.off('blur.datepicker').on('blur.datepicker', function () {\n if (!showing) {\n scope.$apply(function () {\n $element.datepicker('setDate', $element.datepicker('getDate'));\n setVal();\n });\n }\n });\n\n controller.$validators.uiDateValidator = function uiDateValidator(modelValue, viewValue) {\n return viewValue === null || viewValue === '' || _angular2.default.isDate(uiDateConverter.stringToDate(attrs.uiDateFormat, viewValue));\n };\n\n controller.$parsers.push(function uiDateParser(valueToParse) {\n return uiDateConverter.stringToDate(attrs.uiDateFormat, valueToParse, timezone);\n });\n\n // Update the date picker when the model changes\n controller.$render = function () {\n // Force a render to override whatever is in the input text box\n if (_angular2.default.isDate(controller.$modelValue) === false && _angular2.default.isString(controller.$modelValue)) {\n controller.$modelValue = uiDateConverter.stringToDate(attrs.uiDateFormat, controller.$modelValue, timezone);\n }\n $element.datepicker('setDate', controller.$modelValue);\n };\n }\n // Check if the $element already has a datepicker.\n //\n\n if ($element.data('datepicker')) {\n // Updates the datepicker options\n $element.datepicker('option', opts);\n $element.datepicker('refresh');\n } else {\n // Creates the new datepicker widget\n $element.datepicker(opts);\n\n // Cleanup on destroy, prevent memory leaking\n $element.on('$destroy', function () {\n $element.datepicker('hide');\n $element.datepicker('destroy');\n });\n }\n\n if (controller) {\n controller.$render();\n // Update the model with the value from the datepicker after parsed\n setVal(true);\n }\n };\n\n // Watch for changes to the directives options\n scope.$watch(getOptions, initDateWidget, true);\n }\n };\n}]).directive('uiDateFormat', ['uiDateConverter', function (uiDateConverter) {\n return {\n require: 'ngModel',\n link: function link(scope, element, attrs, modelCtrl) {\n var dateFormat = attrs.uiDateFormat;\n\n // Use the datepicker with the attribute value as the dateFormat string to convert to and from a string\n modelCtrl.$formatters.unshift(function (value) {\n return uiDateConverter.stringToDate(dateFormat, value);\n });\n\n modelCtrl.$parsers.push(function (value) {\n return uiDateConverter.dateToString(dateFormat, value);\n });\n }\n };\n}]);\n\n\n/*****************\n ** WEBPACK FOOTER\n ** ./src/date.js\n ** module id = 0\n ** module chunks = 0\n **/"],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;ACVA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;ACtCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;","sourceRoot":""} -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // 3 | var webpack = require('webpack'); 4 | module.exports = function(config) { 5 | config.set({ 6 | // base path that will be used to resolve all patterns (eg. files, exclude) 7 | basePath: '', 8 | frameworks: ['jasmine'], 9 | 10 | files: [ 11 | 'src/*.spec.js', 12 | ], 13 | 14 | preprocessors: { 15 | // add webpack as preprocessor 16 | 'src/*.spec.js': ['webpack'], 17 | }, 18 | 19 | webpack: { 20 | plugins: [ 21 | new webpack.ProvidePlugin({ 22 | // $: 'jquery', 23 | // jQuery: 'jquery', 24 | 'window.jQuery': 'jquery', 25 | }), 26 | ], 27 | module: { 28 | loaders: [ 29 | // it helps angular to have jQuery exposed so that it uses $ instead of jqLite 30 | // this is an alternative to using a webpack provide plugin 31 | // { 32 | // test: require.resolve('jquery'), 33 | // loader: 'expose?$!expose?jQuery', 34 | // }, 35 | { 36 | test: /(\.js$)/, 37 | exclude: /(node_modules|bower_components)/, 38 | loader: 'babel', 39 | query: { 40 | presets: ['es2015'], 41 | }, 42 | }, 43 | ], 44 | }, 45 | }, 46 | 47 | // test results reporter to use 48 | // possible values: 'dots', 'progress' 49 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 50 | reporters: ['progress'], 51 | 52 | reportSlowerThan: 200, 53 | 54 | // web server port 55 | port: 9876, 56 | 57 | // enable / disable colors in the output (reporters and logs) 58 | colors: true, 59 | 60 | // level of logging 61 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 62 | logLevel: config.LOG_INFO, 63 | 64 | // enable / disable watching file and executing tests whenever any file changes 65 | autoWatch: true, 66 | 67 | // start these browsers 68 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 69 | browsers: ['Chrome'], 70 | 71 | // Continuous Integration mode 72 | // if true, Karma captures browsers, runs the tests and exits 73 | singleRun: false, 74 | 75 | 76 | }); 77 | 78 | if (process.env.TRAVIS) { 79 | config.set({ 80 | browsers: ['Firefox'], 81 | reporters: 'dots', 82 | autoWatch: false, 83 | singleRun: true, 84 | }); 85 | } 86 | 87 | }; 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-ui-date", 3 | "version": "1.1.1", 4 | "description": "This directive allows you to add a date-picker to your form elements.", 5 | "main": "dist/date.js", 6 | "scripts": { 7 | "build": "npm run test && webpack", 8 | "changelog": "conventional-changelog -p angular -i CHANGELOG.md --same-file", 9 | "commit": "git-cz", 10 | "ghpages": "cp dist/date.js demo/assets/date.js && gh-pages -d demo", 11 | "lint": "eslint src/** --ext .js", 12 | "update": "bower update", 13 | "start": "webpack-dev-server", 14 | "test": "npm run lint && karma start --single-run", 15 | "test:watch": "karma start", 16 | "test:single": "karma start --single-run", 17 | "test:ci": "TRAVIS=true npm run test" 18 | }, 19 | "repository": { 20 | "type": "git", 21 | "url": "git@github.com:angular-ui/ui-date.git" 22 | }, 23 | "author": "https://github.com/angular-ui/ui-date/graphs/contributors", 24 | "license": "MIT", 25 | "homepage": "http://angular-ui.github.com/ui-date", 26 | "bugs": { 27 | "url": "https://github.com/angular-ui/ui-date/issues" 28 | }, 29 | "keywords": [ 30 | "AngularJS", 31 | "directive", 32 | "datepicker" 33 | ], 34 | "devDependencies": { 35 | "angular": "^1.4.0", 36 | "angular-mocks": "^1.4.0", 37 | "babel-core": "6.9.1", 38 | "babel-loader": "6.2.4", 39 | "babel-preset-es2015": "6.9.0", 40 | "bower": "^1.7.0", 41 | "commitizen": "^2.4.6", 42 | "conventional-changelog": "^1.1.0", 43 | "conventional-changelog-cli": "1.2.0", 44 | "cz-conventional-changelog": "^1.1.5", 45 | "eslint": "^1.10.3", 46 | "exports-loader": "0.6.2", 47 | "expose-loader": "0.7.1", 48 | "gh-pages": "0.11.0", 49 | "jasmine-core": "^2.4.1", 50 | "jquery": "2.1.4", 51 | "jquery-ui": "1.10.5", 52 | "karma": "^0.13.15", 53 | "karma-chrome-launcher": "^1.0.1", 54 | "karma-firefox-launcher": "^1.0.0", 55 | "karma-jasmine": "^1.0.2", 56 | "karma-webpack": "1.7.0", 57 | "webpack": "1.13.1", 58 | "webpack-dev-server": "1.14.1" 59 | }, 60 | "config": { 61 | "commitizen": { 62 | "path": "./node_modules/cz-conventional-changelog" 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/date.js: -------------------------------------------------------------------------------- 1 | import jQuery from 'jquery'; 2 | import angular from 'angular'; 3 | import _datePicker from 'jquery-ui/datepicker'; // sets up jQuery with the datepicker plugin 4 | 5 | export default angular.module('ui.date', []) 6 | .constant('uiDateConfig', {}) 7 | .constant('uiDateFormatConfig', '') 8 | .factory('uiDateConverter', ['uiDateFormatConfig', function(uiDateFormatConfig) { 9 | return { 10 | stringToDate: stringToDate, 11 | dateToString: dateToString, 12 | }; 13 | 14 | //https://github.com/angular/angular.js/blob/622c42169699ec07fc6daaa19fe6d224e5d2f70e/src/Angular.js#L1207 15 | function timezoneToOffset(timezone, fallback) { 16 | timezone = timezone.replace(/:/g, ''); 17 | var requestedTimezoneOffset = Date.parse('Jan 01, 1970 00:00:00 ' + timezone) / 60000; 18 | return isNaN(requestedTimezoneOffset) ? fallback : requestedTimezoneOffset; 19 | } 20 | 21 | function addDateMinutes(date, minutes) { 22 | date = new Date(date.getTime()); 23 | date.setMinutes(date.getMinutes() + minutes); 24 | return date; 25 | } 26 | 27 | function convertTimezoneToLocal(date, timezone, reverse) { 28 | reverse = reverse ? -1 : 1; 29 | var dateTimezoneOffset = date.getTimezoneOffset(); 30 | var timezoneOffset = timezoneToOffset(timezone, dateTimezoneOffset); 31 | return addDateMinutes(date, reverse * (timezoneOffset - dateTimezoneOffset)); 32 | } 33 | 34 | function doTZ(date,timezone,reverse) { 35 | return timezone ? convertTimezoneToLocal(date, timezone, reverse) : date; 36 | } 37 | 38 | function dateToString(uiDateFormat, value) { 39 | var dateFormat = uiDateFormat || uiDateFormatConfig; 40 | if (value) { 41 | if (dateFormat) { 42 | try { 43 | return jQuery.datepicker.formatDate(dateFormat, value); 44 | } catch (formatException) { 45 | return undefined; 46 | } 47 | } 48 | 49 | if (value.toISOString) { 50 | return value.toISOString(); 51 | } 52 | 53 | } 54 | return null; 55 | } 56 | 57 | function stringToDate(dateFormat, valueToParse, timezone) { 58 | dateFormat = dateFormat || uiDateFormatConfig; 59 | 60 | if (angular.isDate(valueToParse) && !isNaN(valueToParse)) { 61 | return doTZ(valueToParse, timezone); 62 | } 63 | 64 | if (angular.isString(valueToParse)) { 65 | if (dateFormat) { 66 | return doTZ(jQuery.datepicker.parseDate(dateFormat, valueToParse), timezone); 67 | } 68 | 69 | var isoDate = new Date(valueToParse); 70 | return isNaN(isoDate.getTime()) ? null : doTZ(isoDate, timezone); 71 | 72 | } 73 | 74 | if (angular.isNumber(valueToParse)) { 75 | // presumably timestamp to date object 76 | return doTZ(new Date(valueToParse), timezone); 77 | } 78 | 79 | return null; 80 | } 81 | }]) 82 | 83 | .directive('uiDate', ['uiDateConfig', 'uiDateConverter', function uiDateDirective(uiDateConfig, uiDateConverter) { 84 | 85 | return { 86 | require: '?ngModel', 87 | link: function link(scope, element, attrs, controller) { 88 | 89 | var $element = jQuery(element); 90 | 91 | var getOptions = function() { 92 | return angular.extend({}, uiDateConfig, scope.$eval(attrs.uiDate)); 93 | }; 94 | var initDateWidget = function() { 95 | var showing = false; 96 | var opts = getOptions(); 97 | var timezone = controller ? controller.$options.getOption('timezone') : null; 98 | 99 | function setVal(forcedUpdate) { 100 | var keys = ['Hours', 'Minutes', 'Seconds', 'Milliseconds']; 101 | var isDate = angular.isDate(controller.$modelValue); 102 | var preserve = {}; 103 | 104 | if (!forcedUpdate && isDate && controller.$modelValue.toDateString() === $element.datepicker('getDate').toDateString()) { 105 | return; 106 | } 107 | 108 | if (isDate) { 109 | angular.forEach(keys, function(key) { 110 | preserve[key] = controller.$modelValue['get' + key](); 111 | }); 112 | } 113 | 114 | var newViewValue = $element.datepicker('getDate'); 115 | 116 | if (isDate) { 117 | angular.forEach(keys, (key) => { 118 | newViewValue['set' + key](preserve[key]); 119 | }); 120 | } 121 | 122 | controller.$setViewValue(newViewValue); 123 | } 124 | 125 | // If we have a controller (i.e. ngModelController) then wire it up 126 | if (controller) { 127 | // Set the view value in a $apply block when users selects 128 | // (calling directive user's function too if provided) 129 | var _onSelect = opts.onSelect || angular.noop; 130 | opts.onSelect = function(value, picker) { 131 | scope.$apply(function() { 132 | showing = true; 133 | setVal(); 134 | $element.blur(); 135 | _onSelect(value, picker, $element); 136 | }); 137 | }; 138 | 139 | var _beforeShow = opts.beforeShow || angular.noop; 140 | opts.beforeShow = function(input, picker) { 141 | showing = true; 142 | _beforeShow(input, picker, $element); 143 | }; 144 | 145 | var _onClose = opts.onClose || angular.noop; 146 | opts.onClose = function(value, picker) { 147 | showing = false; 148 | _onClose(value, picker, $element); 149 | }; 150 | 151 | element.on('focus', function(focusEvent) { 152 | if (attrs.readonly) { 153 | focusEvent.stopImmediatePropagation(); 154 | } 155 | }); 156 | 157 | $element.off('blur.datepicker').on('blur.datepicker', function() { 158 | if (!showing) { 159 | scope.$apply(function() { 160 | $element.datepicker('setDate', $element.datepicker('getDate')); 161 | setVal(); 162 | }); 163 | } 164 | }); 165 | 166 | controller.$validators.uiDateValidator = function uiDateValidator(modelValue, viewValue) { 167 | return viewValue === null 168 | || viewValue === '' 169 | || angular.isDate(uiDateConverter.stringToDate(attrs.uiDateFormat, viewValue)); 170 | }; 171 | 172 | controller.$parsers.push(function uiDateParser(valueToParse) { 173 | return uiDateConverter.stringToDate(attrs.uiDateFormat, valueToParse, timezone); 174 | }); 175 | 176 | // Update the date picker when the model changes 177 | controller.$render = function() { 178 | // Force a render to override whatever is in the input text box 179 | if (angular.isDate(controller.$modelValue) === false && angular.isString(controller.$modelValue)) { 180 | controller.$modelValue = uiDateConverter.stringToDate(attrs.uiDateFormat, controller.$modelValue, timezone); 181 | } 182 | $element.datepicker('setDate', controller.$modelValue); 183 | }; 184 | } 185 | // Check if the $element already has a datepicker. 186 | // 187 | 188 | if ($element.data('datepicker')) { 189 | // Updates the datepicker options 190 | $element.datepicker('option', opts); 191 | $element.datepicker('refresh'); 192 | } else { 193 | // Creates the new datepicker widget 194 | $element.datepicker(opts); 195 | 196 | // Cleanup on destroy, prevent memory leaking 197 | $element.on('$destroy', function() { 198 | $element.datepicker('hide'); 199 | $element.datepicker('destroy'); 200 | }); 201 | } 202 | 203 | if (controller) { 204 | controller.$render(); 205 | // Update the model with the value from the datepicker after parsed 206 | setVal(true); 207 | } 208 | }; 209 | 210 | // Watch for changes to the directives options 211 | scope.$watch(getOptions, initDateWidget, true); 212 | }, 213 | }; 214 | }]) 215 | 216 | .directive('uiDateFormat', ['uiDateConverter', function(uiDateConverter) { 217 | return { 218 | require: 'ngModel', 219 | link: function(scope, element, attrs, modelCtrl) { 220 | var dateFormat = attrs.uiDateFormat; 221 | 222 | // Use the datepicker with the attribute value as the dateFormat string to convert to and from a string 223 | modelCtrl.$formatters.unshift(function(value) { 224 | return uiDateConverter.stringToDate(dateFormat, value); 225 | }); 226 | 227 | modelCtrl.$parsers.push(function(value) { 228 | return uiDateConverter.dateToString(dateFormat, value); 229 | }); 230 | }, 231 | }; 232 | }]); 233 | -------------------------------------------------------------------------------- /src/date.spec.js: -------------------------------------------------------------------------------- 1 | import $ from 'jquery'; 2 | import angular from 'angular'; 3 | import _angularMocks from 'angular-mocks'; 4 | import uiDate from './date.js'; 5 | 6 | const module = angular.mock.module; 7 | const inject = angular.mock.inject; 8 | 9 | describe('uiDate', function() { 10 | 11 | function selectDate(element, date) { 12 | element.datepicker('setDate', date); 13 | $.datepicker._selectDate(element); 14 | } 15 | 16 | beforeEach(module(uiDate.name)); 17 | describe('simple use on input element', function() { 18 | it('should have a date picker attached', function() { 19 | inject(function($compile, $rootScope) { 20 | var element; 21 | element = $compile('')($rootScope); 22 | expect(element.datepicker()).toBeDefined(); 23 | }); 24 | }); 25 | it('should be able to get the date from the model', function() { 26 | inject(function($compile, $rootScope) { 27 | var aDate, element; 28 | aDate = new Date(2010, 12, 1); 29 | element = $compile('')($rootScope); 30 | $rootScope.$apply(function() { 31 | $rootScope.x = aDate; 32 | }); 33 | expect(element.datepicker('getDate')).toEqual(aDate); 34 | }); 35 | }); 36 | it('should handle the "readonly" attribute', function() { 37 | inject(function($compile, $rootScope) { 38 | var element; 39 | element = $compile('')($rootScope); 40 | $rootScope.$apply(); 41 | $(document.body).append(element); 42 | // On initial load the datepicker is attached to the DOM but its style (i.e. 43 | // display: none) has not been applied -- it will be invisible but the 44 | // ":visible" will fail which would be solved for checking ":empty". However, 45 | // this doesn't handle the case where the readonly attribute is dynamically 46 | // toggled -- so for this test to pass properly, we must first initialize the 47 | // datepicker by toggling it before checking visibility. 48 | element.datepicker('show'); 49 | element.datepicker('hide'); 50 | element.focus(); 51 | expect(angular.element('#ui-datepicker-div').is(':visible')).toBeFalsy(); 52 | element.remove(); 53 | }); 54 | }); 55 | it('should put the date in the model', function() { 56 | inject(function($compile, $rootScope) { 57 | var aDate, element; 58 | aDate = new Date(2010, 12, 1); 59 | element = $compile('')($rootScope); 60 | $rootScope.$apply(); 61 | selectDate(element, aDate); 62 | expect($rootScope.x).toEqual(aDate); 63 | }); 64 | }); 65 | it('should not trigger a ng-change when nothing changes', function() { 66 | inject(function($compile, $rootScope) { 67 | var aDate, element; 68 | aDate = new Date(2010, 12, 1); 69 | element = $compile('')($rootScope); 70 | $rootScope.$apply(function() { 71 | $rootScope.x = aDate; 72 | }); 73 | var cur = $rootScope.x; 74 | element.focus(); 75 | element.blur(); 76 | 77 | expect($rootScope.x).toEqual(cur); 78 | // cur = $rootScope.x; 79 | // element.focus(); 80 | // element.blur(); 81 | // expect($rootScope.x).toBe(cur); 82 | }); 83 | }); 84 | // this test passes on chrome 47 but dies on firefox 42. Travis on firefox 31 is ok 85 | // it('should hide the date picker after selecting a date', function() { 86 | // inject(function($compile, $rootScope, $document) { 87 | // var aDate, element; 88 | // aDate = new Date(2010, 12, 1); 89 | // element = $compile('')($rootScope); 90 | // $rootScope.$apply(); 91 | // //angular.element(document).find('body').append(element); // Need to add it to the document so that it can get focus 92 | // $($document[0].body).append(element); 93 | // element.focus(); 94 | // expect(angular.element('#ui-datepicker-div').is(':visible')).toBeTruthy(); 95 | // selectDate(element, aDate); 96 | // expect(angular.element('#ui-datepicker-div').is(':visible')).toBeFalsy(); 97 | // element.remove(); // And then remove it again! 98 | // }); 99 | // }); 100 | }); 101 | describe('when model is not a Date', function() { 102 | var element; 103 | var scope; 104 | beforeEach(inject(function($compile, $rootScope) { 105 | element = $compile('')($rootScope); 106 | scope = $rootScope; 107 | })); 108 | it('should not freak out when the model is null', function() { 109 | scope.$apply(function() { 110 | scope.x = null; 111 | }); 112 | expect(element.datepicker('getDate')).toBeNull(); 113 | }); 114 | it('should not freak out when the model is undefined', function() { 115 | scope.$apply(function() { 116 | scope.x = undefined; 117 | }); 118 | expect(element.datepicker('getDate')).toBeNull(); 119 | }); 120 | it('should throw an error if you try to pass in a boolean when the model is false', function() { 121 | expect(function() { 122 | scope.$apply(function() { 123 | scope.x = false; 124 | }); 125 | }).toThrow(); 126 | }); 127 | }); 128 | 129 | it('should update the input field correctly on a manual update', function() { 130 | inject(function($compile, $rootScope) { 131 | var dateString = '2012-08-17'; 132 | var dateObj = $.datepicker.parseDate('yy-mm-dd', dateString); 133 | var element = $compile('')($rootScope); 134 | $rootScope.$apply(function() { 135 | $rootScope.x = dateObj; 136 | }); 137 | 138 | dateString = '2012-7-01'; 139 | dateObj = $.datepicker.parseDate('yy-mm-dd', dateString); 140 | 141 | // Now change the date in the input box 142 | element.val(dateString); 143 | element.trigger('input'); 144 | // expect($rootScope.x).toEqual(element.val()); 145 | expect(element.datepicker('getDate')).toEqual(dateObj); 146 | 147 | // Now blur the input and expect the input to be re-formatted 148 | // and the model to get converted to a Date object 149 | element.trigger('blur'); 150 | expect(element.val()).toEqual('2012-07-01'); 151 | $rootScope.$digest(); 152 | expect($rootScope.x).toEqual(dateObj); 153 | }); 154 | }); 155 | 156 | it('should preserve time', function() { 157 | inject(function($compile, $rootScope) { 158 | $rootScope.x = new Date(2012, 9, 12, 15); 159 | var element = $compile('')($rootScope); 160 | $rootScope.$apply(); 161 | selectDate(element, $rootScope.x); 162 | expect($rootScope.x.getHours()).toBe(15); 163 | }); 164 | }); 165 | 166 | 167 | it('should observe timezone option', function() { 168 | inject(function($compile, $rootScope) { 169 | var zone, expected; 170 | $rootScope.x = new Date(2012, 9, 12, 12); 171 | zone = $rootScope.x.getTimezoneOffset(); 172 | if (zone === 0) { 173 | zone = '+0400'; 174 | expected = 8; 175 | } else { 176 | expected = 12 - zone / 60; 177 | zone = 'UTC'; 178 | } 179 | var element = $compile('')($rootScope); 180 | $rootScope.$apply(); 181 | selectDate(element, $rootScope.x); 182 | expect($rootScope.x.getHours()).toBe(expected); 183 | }); 184 | }); 185 | 186 | it('should convert a model string to a Date immediately after applied', function() { 187 | inject(function($compile, $rootScope) { 188 | $rootScope.x = '2015-09-13'; 189 | var element = $compile('')($rootScope); 190 | $rootScope.$apply(); 191 | selectDate(element, $rootScope.x); 192 | expect($rootScope.x).toEqual(new Date('2015-09-13')); 193 | }); 194 | }); 195 | 196 | describe('jQuery widget', function() { 197 | var element; 198 | 199 | beforeEach(inject(function($compile, $rootScope) { 200 | element = $compile('
{{5+7}}
')($rootScope); 201 | $(document.body).append(element); 202 | $rootScope.$digest(); 203 | })); 204 | 205 | it('should not stop following elements from linking', function() { 206 | expect(element.find('span').text()).toEqual('12'); 207 | }); 208 | 209 | afterEach(function() { 210 | element.remove(); 211 | }); 212 | }); 213 | describe('use with user events', function() { 214 | it('should call the user onSelect event within a scope.$apply context', function() { 215 | inject(function($compile, $rootScope) { 216 | var watched = false; 217 | $rootScope.myDateSelected = function() { 218 | $rootScope.watchMe = true; 219 | }; 220 | $rootScope.$watch('watchMe', function(watchMe) { 221 | if (watchMe) { 222 | watched = true; 223 | } 224 | }); 225 | var aDate = new Date(2012, 9, 11); 226 | var element = $compile('')($rootScope); 227 | $rootScope.$apply(); 228 | selectDate(element, aDate); 229 | expect(watched).toBeTruthy(); 230 | }); 231 | }); 232 | 233 | it('should call the user beforeShow event listener', function() { 234 | inject(function($compile, $rootScope) { 235 | var element; 236 | $rootScope.beforeShowFn = function() {}; 237 | spyOn($rootScope, 'beforeShowFn'); 238 | spyOn($.datepicker, '_findPos').and.returnValue([0, 0]); 239 | element = $compile('')($rootScope); 240 | $rootScope.$apply(); 241 | 242 | expect($rootScope.beforeShowFn).not.toHaveBeenCalled(); 243 | 244 | element.datepicker('show'); 245 | $rootScope.$apply(); 246 | 247 | expect($rootScope.beforeShowFn).toHaveBeenCalled(); 248 | }); 249 | }); 250 | 251 | it('should call the user onClose event listener', function() { 252 | inject(function($compile, $rootScope) { 253 | var element; 254 | $rootScope.onCloseFn = function() {}; 255 | spyOn($rootScope, 'onCloseFn'); 256 | spyOn($.datepicker, '_findPos').and.returnValue([0, 0]); 257 | element = $compile('')($rootScope); 258 | $rootScope.$apply(); 259 | 260 | expect($rootScope.onCloseFn).not.toHaveBeenCalled(); 261 | 262 | element.datepicker('show'); 263 | element.datepicker('hide'); 264 | $rootScope.$apply(); 265 | 266 | expect($rootScope.onCloseFn).toHaveBeenCalled(); 267 | }); 268 | }); 269 | }); 270 | 271 | describe('use with ng-required directive', function() { 272 | it('should be invalid initially', function() { 273 | inject(function($compile, $rootScope) { 274 | var element; 275 | element = $compile('')($rootScope); 276 | $rootScope.$apply(); 277 | expect(element.hasClass('ng-invalid')).toBeTruthy(); 278 | }); 279 | }); 280 | it('should be valid if model has been specified', function() { 281 | inject(function($compile, $rootScope) { 282 | var aDate, element; 283 | aDate = new Date(2010, 12, 1); 284 | element = $compile('')($rootScope); 285 | $rootScope.$apply(function() { 286 | $rootScope.x = aDate; 287 | }); 288 | expect(element.hasClass('ng-valid')).toBeTruthy(); 289 | }); 290 | }); 291 | it('should be valid after the date has been picked', function() { 292 | inject(function($compile, $rootScope) { 293 | var aDate, element; 294 | aDate = new Date(2010, 12, 1); 295 | element = $compile('')($rootScope); 296 | $rootScope.$apply(); 297 | selectDate(element, aDate); 298 | expect(element.hasClass('ng-valid')).toBeTruthy(); 299 | }); 300 | }); 301 | }); 302 | describe('simple use on a div element', function() { 303 | it('should have a date picker attached', function() { 304 | inject(function($compile, $rootScope) { 305 | var element; 306 | element = $compile('
')($rootScope); 307 | expect(element.datepicker()).toBeDefined(); 308 | }); 309 | }); 310 | it('should be able to get the date from the model', function() { 311 | inject(function($compile, $rootScope) { 312 | var aDate, element; 313 | aDate = new Date(2010, 12, 1); 314 | element = $compile('
')($rootScope); 315 | $rootScope.$apply(function() { 316 | $rootScope.x = aDate; 317 | }); 318 | expect(element.datepicker('getDate')).toEqual(aDate); 319 | }); 320 | }); 321 | it('should put the date in the model', function() { 322 | inject(function($compile, $rootScope) { 323 | var aDate, element; 324 | aDate = new Date(2010, 12, 1); 325 | element = $compile('
')($rootScope); 326 | $rootScope.$apply(); 327 | selectDate(element, aDate); 328 | expect($rootScope.x).toEqual(aDate); 329 | }); 330 | }); 331 | 332 | it('should cleanup when the datepicker parent is removed', function() { 333 | inject(function($compile, $rootScope) { 334 | var element; 335 | element = $compile('
')($rootScope); 336 | expect(element.data('datepicker')).toBeUndefined(); 337 | $rootScope.$apply(); 338 | expect(element.children().length).toBe(1); 339 | element.remove(); 340 | $rootScope.$apply(); 341 | expect(element.children().length).toBe(0); 342 | }); 343 | }); 344 | 345 | // it('should focus after selecting a date', function() { 346 | // inject(function($compile, $rootScope) { 347 | // var element = $compile(`
348 | // 349 | // 350 | //
`)($rootScope); 351 | // $rootScope.$apply(); 352 | // $(document.body).append(element); 353 | // var datePickerElement = element.find('#focusDate'); 354 | // $rootScope.$apply(); 355 | // datePickerElement.datepicker('show'); 356 | // datePickerElement.datepicker('hide'); 357 | // expect($('#focusDate').is(':focus')).toBe(true); 358 | // element.remove(); 359 | // }); 360 | // }); 361 | 362 | }); 363 | describe('use with ng-required directive', function() { 364 | it('should be invalid initially', function() { 365 | inject(function($compile, $rootScope) { 366 | var element = $compile('
')($rootScope); 367 | $rootScope.$apply(); 368 | expect(element.hasClass('ng-invalid')).toBeTruthy(); 369 | }); 370 | }); 371 | it('should be valid if model has been specified', function() { 372 | inject(function($compile, $rootScope) { 373 | var aDate, element; 374 | aDate = new Date(2010, 12, 1); 375 | element = $compile('
')($rootScope); 376 | $rootScope.$apply(function() { 377 | $rootScope.x = aDate; 378 | }); 379 | expect(element.hasClass('ng-valid')).toBeTruthy(); 380 | }); 381 | }); 382 | it('should be valid after the date has been picked', function() { 383 | inject(function($compile, $rootScope) { 384 | var aDate, element; 385 | aDate = new Date(2010, 12, 1); 386 | element = $compile('
')($rootScope); 387 | $rootScope.$apply(); 388 | selectDate(element, aDate); 389 | expect(element.hasClass('ng-valid')).toBeTruthy(); 390 | }); 391 | }); 392 | }); 393 | describe('when attribute options change', function() { 394 | it('should watch attribute and update date widget accordingly', function() { 395 | inject(function($compile, $rootScope) { 396 | var element; 397 | $rootScope.config = { 398 | minDate: 5, 399 | }; 400 | element = $compile('')($rootScope); 401 | $rootScope.$apply(); 402 | expect(element.datepicker('option', 'minDate')).toBe(5); 403 | $rootScope.$apply(function() { 404 | $rootScope.config.minDate = 10; 405 | }); 406 | expect(element.datepicker('option', 'minDate')).toBe(10); 407 | }); 408 | }); 409 | }); 410 | }); 411 | 412 | describe('uiDateFormat', function() { 413 | beforeEach(module('ui.date')); 414 | 415 | describe('$formatting', function() { 416 | it('should parse the date correctly from an ISO string', function() { 417 | inject(function($compile, $rootScope) { 418 | var aDate, aDateString, element; 419 | aDate = new Date(2012, 8, 17); 420 | aDateString = aDate.toISOString(); 421 | 422 | element = $compile('')($rootScope); 423 | $rootScope.x = aDateString; 424 | $rootScope.$digest(); 425 | 426 | // Check that the model has not been altered 427 | expect($rootScope.x).toEqual(aDateString); 428 | // Check that the viewValue has been parsed correctly 429 | expect(element.controller('ngModel').$viewValue).toEqual(aDate); 430 | }); 431 | }); 432 | it('should parse the date correctly from a custom string', function() { 433 | inject(function($compile, $rootScope) { 434 | var aDate = new Date(2012, 9, 11); 435 | var aDateString = 'Thursday, 11 October, 2012'; 436 | 437 | var element = $compile('')($rootScope); 438 | $rootScope.x = aDateString; 439 | $rootScope.$digest(); 440 | 441 | // Check that the model has not been altered 442 | expect($rootScope.x).toEqual(aDateString); 443 | // Check that the viewValue has been parsed correctly 444 | expect(element.controller('ngModel').$viewValue).toEqual(aDate); 445 | }); 446 | }); 447 | it('should handle unusual model values', function() { 448 | inject(function($compile, $rootScope) { 449 | var element = $compile('')($rootScope); 450 | 451 | $rootScope.x = false; 452 | $rootScope.$digest(); 453 | // Check that the model has not been altered 454 | expect($rootScope.x).toEqual(false); 455 | // Check that the viewValue has been parsed correctly 456 | expect(element.controller('ngModel').$viewValue).toEqual(null); 457 | 458 | $rootScope.x = undefined; 459 | $rootScope.$digest(); 460 | // Check that the model has not been altered 461 | expect($rootScope.x).toBeUndefined(); 462 | // Check that the viewValue has been parsed correctly 463 | expect(element.controller('ngModel').$viewValue).toEqual(null); 464 | 465 | $rootScope.x = null; 466 | $rootScope.$digest(); 467 | // Check that the model has not been altered 468 | expect($rootScope.x).toBeNull(); 469 | // Check that the viewValue has been parsed correctly 470 | expect(element.controller('ngModel').$viewValue).toEqual(null); 471 | }); 472 | }); 473 | 474 | it('should validate null and blank dates as valid', function() { 475 | inject(function($compile, $rootScope) { 476 | 477 | var element = $compile('
')($rootScope); 478 | var ngModel = element.find('input').controller('ngModel'); 479 | ngModel.$setViewValue(''); 480 | 481 | expect($rootScope.testForm.$error).toEqual({}); 482 | }) 483 | }); 484 | 485 | // it('should validate null and blank dates as valid', function() { 486 | // inject(function($compile, $rootScope) { 487 | // $rootScope.x = null; 488 | // var element = $compile('
')($rootScope); 489 | // expect($rootScope.testForm.$error).toEqual({}); 490 | // 491 | // element.find('input').val('09-99-99').triggerHandler('input'); 492 | // expect($rootScope.testForm.$error).toEqual({}); 493 | // }) 494 | // }); 495 | 496 | }); 497 | 498 | describe('$parsing', function() { 499 | it('should format a selected date correctly to an ISO string', function() { 500 | inject(function($compile, $rootScope) { 501 | var aDate = new Date(2012, 8, 17); 502 | var aDateString = aDate.toISOString(); 503 | var element = $compile('')($rootScope); 504 | $rootScope.$digest(); 505 | 506 | element.controller('ngModel').$setViewValue(aDate); 507 | // Check that the model is updated correctly 508 | expect($rootScope.x).toEqual(aDateString); 509 | // Check that the $viewValue has not been altered 510 | expect(element.controller('ngModel').$viewValue).toEqual(aDate); 511 | }); 512 | }); 513 | 514 | it('should not throw when a user types in an incomplete value', function() { 515 | inject(function($compile, $rootScope) { 516 | var element = $compile('')($rootScope); 517 | var ngModel = element.controller('ngModel'); 518 | expect(function incompleteValue() { 519 | ngModel.$setViewValue('2015-'); 520 | }).not.toThrow(); 521 | expect(ngModel.$modelValue).toBeUndefined(); 522 | }); 523 | }); 524 | 525 | it('should convert empty strings to null', inject(function($compile, $rootScope) { 526 | var element = $compile('')($rootScope); 527 | element.controller('ngModel').$setViewValue(''); 528 | $rootScope.$digest(); 529 | expect($rootScope.x).toBeNull(); 530 | 531 | element = $compile('')($rootScope); 532 | element.controller('ngModel').$setViewValue(''); 533 | $rootScope.$digest(); 534 | expect($rootScope.x).toBeNull(); 535 | })); 536 | 537 | it('should not freak out on invalid values', function() { 538 | inject(function($compile, $rootScope) { 539 | var element = $compile('')($rootScope); 540 | $rootScope.$digest(); 541 | 542 | element.controller('ngModel').$setViewValue('abcdef'); 543 | }); 544 | }); 545 | 546 | it('should format a selected date correctly to a custom string', function() { 547 | inject(function($compile, $rootScope) { 548 | var format = 'DD, d MM, yy'; 549 | var aDate = new Date(2012, 9, 11); 550 | var aDateString = 'Thursday, 11 October, 2012'; 551 | var element = $compile('')($rootScope); 552 | $rootScope.$digest(); 553 | 554 | element.controller('ngModel').$setViewValue(aDate); 555 | // Check that the model is updated correctly 556 | expect($rootScope.x).toEqual(aDateString); 557 | // Check that the $viewValue has not been altered 558 | expect(element.controller('ngModel').$viewValue).toEqual(aDate); 559 | }); 560 | }); 561 | 562 | it('should parse the date correctly from a UNIX timestamp in milliseconds', function() { 563 | inject(function($compile, $rootScope) { 564 | var aDate = new Date(2012, 9, 11); 565 | var aDateTimestamp = aDate.getTime(); 566 | 567 | var element = $compile('')($rootScope); 568 | $rootScope.x = aDateTimestamp; 569 | $rootScope.$digest(); 570 | 571 | // Check that the model has not been altered 572 | expect($rootScope.x).toEqual(aDateTimestamp); 573 | // Check that the viewValue has been parsed correctly 574 | expect(element.controller('ngModel').$viewValue).toEqual(aDate); 575 | }); 576 | }); 577 | }); 578 | 579 | describe('with uiDateConfig', function() { 580 | var element, scope; 581 | 582 | beforeEach(function() { 583 | module('ui.date'); 584 | }); 585 | 586 | it('use ISO if not config value', function() { 587 | inject(['$compile', '$rootScope', function($compile, $rootScope) { 588 | element = $compile('')($rootScope); 589 | scope = $rootScope; 590 | }]); 591 | 592 | var aDate = new Date(2012, 9, 11); 593 | var aISODateString = aDate.toISOString(); 594 | scope.x = aISODateString; 595 | scope.$digest(); 596 | expect(element.controller('ngModel').$viewValue).toEqual(aDate); 597 | }); 598 | 599 | it('use format value if config given', function() { 600 | var format = 'yy DD, d MM'; 601 | module(function($provide) { 602 | $provide.constant('uiDateFormatConfig', format); 603 | }); 604 | 605 | inject(['$compile', '$rootScope', function($compile, $rootScope) { 606 | element = $compile('')($rootScope); 607 | scope = $rootScope; 608 | }]); 609 | 610 | var aDateString = '2012 Friday, 12 October'; 611 | var expectedDate = new Date('2012-10-12'); 612 | 613 | scope.x = aDateString; 614 | scope.$digest(); 615 | 616 | var pickerDate = element.controller('ngModel').$viewValue; 617 | expect(pickerDate.getDate()).toEqual(expectedDate.getUTCDate()); 618 | expect(pickerDate.getUTCMonth()).toEqual(expectedDate.getUTCMonth()); 619 | expect(pickerDate.getUTCFullYear()).toEqual(expectedDate.getUTCFullYear()); 620 | }); 621 | }); 622 | 623 | }); 624 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // const HtmlWebpackPlugin = require('html-webpack-plugin') 2 | 3 | module.exports = { 4 | 5 | entry: { 6 | date: './src/date.js', 7 | }, 8 | 9 | output: { 10 | path: __dirname + '/dist', 11 | filename: '[name].js', 12 | libraryTarget: 'umd', 13 | library: 'angularUiDate', 14 | publicPath: 'assets', 15 | }, 16 | 17 | module: { 18 | loaders: [ 19 | { 20 | test: /(\.js$)/, 21 | exclude: /(node_modules|bower_components)/, 22 | loader: 'babel', 23 | query: { 24 | presets: ['es2015'], 25 | }, 26 | }, 27 | ], 28 | }, 29 | 30 | externals: { 31 | angular: 'angular', 32 | 'jquery-ui/datepicker': 'jquery-ui/datepicker', 33 | jquery: { 34 | root: 'jQuery', 35 | commonjs: 'jquery', 36 | commonjs2: 'jquery', 37 | amd: 'jquery', 38 | }, 39 | }, 40 | // plugins: [ 41 | // new HtmlWebpackPlugin({ // Also generate a test.html 42 | // filename: 'index.html', 43 | // template: 'src/index.html', 44 | // inject: 'body' 45 | // }), 46 | // ], 47 | 48 | devtool: '#cheap-source-map', 49 | devServer: { 50 | contentBase: './demo', 51 | }, 52 | }; 53 | --------------------------------------------------------------------------------