├── .gitattributes ├── .bowerrc ├── .gitignore ├── .jsbeautifyrc ├── lib ├── selectcomposite.constants.js ├── selectoption.typedef.js ├── constants.js ├── expiryselect.constants.js ├── monthselect.constants.js ├── yearselect.constants.js ├── main.js ├── yearselect.controller.js ├── monthselect.controller.js ├── monthselect.directive.js ├── yearselect.directive.js ├── selectcomposite.directive.js ├── genericselect.controller.js ├── expiryselect.directive.js └── genericselect.directive.js ├── .editorconfig ├── .jshintrc ├── example ├── expiryselect.html ├── selectcomposite.html ├── year.html └── month.html ├── LICENSE ├── bower.json ├── package.json ├── README.md ├── gulpfile.js └── date-elements.min.js /.gitattributes: -------------------------------------------------------------------------------- 1 | example/* linguist-documentation 2 | -------------------------------------------------------------------------------- /.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "json": "bower.json" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | /coverage 3 | *.log 4 | 5 | # Node 6 | /node_modules 7 | 8 | # Bower 9 | /bower_components 10 | 11 | # IDEs 12 | /.idea 13 | -------------------------------------------------------------------------------- /.jsbeautifyrc: -------------------------------------------------------------------------------- 1 | { 2 | "indent_char": " ", 3 | "indent_size": 2, 4 | "preserve_newlines": false, 5 | "jslint_happy": true, 6 | "wrap_line_length": 110 7 | } 8 | -------------------------------------------------------------------------------- /lib/selectcomposite.constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.constants.SelectComposite'); 4 | 5 | leodido.constants.SelectComposite = {}; 6 | /** @const */ 7 | leodido.constants.SelectComposite.DIRECTIVE_NAME = 'selectComposite'; 8 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | 5 | indent_style = space 6 | indent_size = 2 7 | 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.md] 14 | trim_trailing_whitespace = false 15 | -------------------------------------------------------------------------------- /lib/selectoption.typedef.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.typedef.SelectOption'); 4 | 5 | /** 6 | * A complex type representing each option of the select 7 | * 8 | * @typedef SelectOption 9 | * @property {!number} index 10 | * @property {!string} label 11 | * @property {!string} value 12 | */ 13 | var SelectOption; 14 | -------------------------------------------------------------------------------- /lib/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.constants'); 4 | 5 | goog.require('leodido.constants.MonthSelect'); 6 | goog.require('leodido.constants.YearSelect'); 7 | goog.require('leodido.constants.SelectComposite'); 8 | goog.require('leodido.constants.ExpirySelect'); 9 | 10 | leodido.constants.Module = {}; 11 | /** @const */ 12 | leodido.constants.Module.NAME = 'leodido.dateElements'; 13 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globalstrict": true, 3 | "expr": true, 4 | "globals": { 5 | "document": false, 6 | "console": false, 7 | "angular": false, 8 | "goog": false, 9 | "jasmine": false, 10 | "describe": false, 11 | "it": false, 12 | "beforeEach": false, 13 | "expect": false, 14 | "require": false, 15 | "process": false, 16 | "__dirname": false, 17 | "leodido": false 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/expiryselect.constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.constants.ExpirySelect'); 4 | 5 | leodido.constants.ExpirySelect = {}; 6 | /** @const */ 7 | leodido.constants.ExpirySelect.DIRECTIVE_NAME = 'expirySelect'; 8 | /** @const */ 9 | leodido.constants.ExpirySelect.DEFAULT_NAME = 'expiry'; 10 | /** @const */ 11 | leodido.constants.ExpirySelect.DEFAULT_MONTH_NAME = 'month'; 12 | /** @const */ 13 | leodido.constants.ExpirySelect.DEFAULT_YEAR_NAME = 'year'; 14 | -------------------------------------------------------------------------------- /lib/monthselect.constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.constants.MonthSelect'); 4 | 5 | leodido.constants.MonthSelect = {}; 6 | 7 | /** @const */ 8 | leodido.constants.MonthSelect.DIRECTIVE_NAME = 'monthSelect'; 9 | /** @const */ 10 | leodido.constants.MonthSelect.CONTROLLER_NAME = 'MonthSelectController'; 11 | /** @const */ 12 | leodido.constants.MonthSelect.FORMATS = [ 13 | 'MMMM', // Month in year (January-December) 14 | 'MMM', // Month in year (Jan-Dec) 15 | 'MM', // Month in year, padded (01-12) 16 | 'M' // Month in year (1-12) 17 | ]; 18 | /** @const */ 19 | leodido.constants.MonthSelect.DEFAULT_FORMAT = leodido.constants.MonthSelect.FORMATS[2]; 20 | /** @const */ 21 | leodido.constants.MonthSelect.DEFAULT_FIRST = '0'; 22 | /** @const */ 23 | leodido.constants.MonthSelect.DEFAULT_LAST = '11'; 24 | -------------------------------------------------------------------------------- /lib/yearselect.constants.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.constants.YearSelect'); 4 | 5 | leodido.constants.YearSelect = {}; 6 | 7 | /** @const */ 8 | leodido.constants.YearSelect.DIRECTIVE_NAME = 'yearSelect'; 9 | /** @const */ 10 | leodido.constants.YearSelect.CONTROLLER_NAME = 'YearSelectController'; 11 | /** @const */ 12 | leodido.constants.YearSelect.FORMATS = [ 13 | 'yyyy', // 4 digit representation of year (e.g. AD 1 => 0001, AD 2010 => 2010) 14 | 'yy', // 2 digit representation of year, padded (00-99). (e.g. AD 2001 => 01, AD 2010 => 10) 15 | 'y' // 1 digit representation of year, e.g. (AD 1 => 1, AD 199 => 199) 16 | ]; 17 | /** @const */ 18 | leodido.constants.YearSelect.DEFAULT_FORMAT = leodido.constants.YearSelect.FORMATS[0]; 19 | /** @const */ 20 | leodido.constants.YearSelect.DEFAULT_FIRST = '' + new Date().getFullYear(); 21 | /** @const */ 22 | leodido.constants.YearSelect.DEFAULT_RANGE = 10; 23 | -------------------------------------------------------------------------------- /example/expiryselect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | ExpirySelect Directive 7 | 8 | 9 | 15 | 16 | 17 |
23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /lib/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | //goog.provide('leodido.Main'); 4 | 5 | goog.require('leodido.constants'); 6 | goog.require('leodido.directive.MonthSelectFactory'); 7 | goog.require('leodido.controller.MonthSelect'); 8 | goog.require('leodido.directive.YearSelectFactory'); 9 | goog.require('leodido.controller.YearSelect'); 10 | goog.require('leodido.directive.SelectCompositeFactory'); 11 | goog.require('leodido.directive.ExpirySelectFactory'); 12 | 13 | angular 14 | .module(leodido.constants.Module.NAME, []) 15 | .directive(leodido.constants.ExpirySelect.DIRECTIVE_NAME, leodido.directive.ExpirySelectFactory) 16 | .directive(leodido.constants.SelectComposite.DIRECTIVE_NAME, leodido.directive.SelectCompositeFactory) 17 | .controller(leodido.constants.MonthSelect.CONTROLLER_NAME, leodido.controller.MonthSelect) 18 | .directive(leodido.constants.MonthSelect.DIRECTIVE_NAME, leodido.directive.MonthSelectFactory) 19 | .controller(leodido.constants.YearSelect.CONTROLLER_NAME, leodido.controller.YearSelect) 20 | .directive(leodido.constants.YearSelect.DIRECTIVE_NAME, leodido.directive.YearSelectFactory); 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Leonardo Di Donato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/selectcomposite.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SelectComposite Directive 7 | 8 | 9 | 12 | 13 | 14 | 15 |
19 |
25 |
32 |
33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /lib/yearselect.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.controller.YearSelect'); 4 | 5 | goog.require('leodido.controller.GenericSelect'); 6 | 7 | /** 8 | * YearSelect directive controller 9 | * 10 | * @param {!angular.Scope} $scope 11 | * @param {!angular.JQLite} $element 12 | * @param {!angular.Attributes} $attrs 13 | * @constructor 14 | * @extends {leodido.controller.GenericSelect} 15 | * @ngInject 16 | */ 17 | leodido.controller.YearSelect = function ($scope, $element, $attrs) { 18 | goog.DEBUG && console.log('YearSelectController::constructor', $scope); 19 | var date = $element.injector().get('dateFilter'); 20 | // Privileged methods 21 | /** 22 | * Format a year 23 | * Year have to be expressed as a 4 digit integer. 24 | * 25 | * @param {!number} year 26 | * @return {!string} 27 | */ 28 | this.formatYear = function (year) { 29 | /** @type {string} */ 30 | $scope.format; 31 | return date(new Date(year.toString()), $scope.format); 32 | }; 33 | // 34 | goog.base(this, $scope, $element, $attrs); 35 | }; 36 | goog.inherits(leodido.controller.YearSelect, leodido.controller.GenericSelect); 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | leodido.controller.YearSelect.prototype.getValue = function (index) { 42 | return index.toString(); 43 | }; 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | leodido.controller.YearSelect.prototype.getLabel = function (index) { 49 | return this.formatYear(index); 50 | }; 51 | -------------------------------------------------------------------------------- /lib/monthselect.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.controller.MonthSelect'); 4 | 5 | goog.require('leodido.controller.GenericSelect'); 6 | 7 | /** 8 | * MonthSelect directive controller 9 | * 10 | * @param {!angular.Scope} $scope 11 | * @param {!angular.JQLite} $element 12 | * @param {!angular.Attributes} $attrs 13 | * @constructor 14 | * @extends {leodido.controller.GenericSelect} 15 | * @ngInject 16 | */ 17 | leodido.controller.MonthSelect = function ($scope, $element, $attrs) { 18 | goog.DEBUG && console.log('MonthSelectController::constructor', $scope); 19 | var date = $element.injector().get('dateFilter'); 20 | // Privileged methods 21 | /** 22 | * Format a month 23 | * Input have to be expressed as a zero indexed integer. 24 | * 25 | * @param {!number} month 26 | * @return {!string} 27 | */ 28 | this.formatMonth = function (month) { 29 | /** @type {string} */ 30 | $scope.format; 31 | return date(new Date().setMonth(month), $scope.format); 32 | }; 33 | // 34 | goog.base(this, $scope, $element, $attrs); 35 | }; 36 | goog.inherits(leodido.controller.MonthSelect, leodido.controller.GenericSelect); 37 | 38 | /** 39 | * @inheritDoc 40 | */ 41 | leodido.controller.MonthSelect.prototype.getValue = function (index) { 42 | return (index + 1).toString(); 43 | }; 44 | 45 | /** 46 | * @inheritDoc 47 | */ 48 | leodido.controller.MonthSelect.prototype.getLabel = function (index) { 49 | return this.formatMonth(index); 50 | }; 51 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-date-elements", 3 | "description": "AngularJS directives for date elements", 4 | "version": "0.4.0", 5 | "authors": [ 6 | "Leo Di Donato (http://git.io/leodido)" 7 | ], 8 | "main": "date-elements.min.js", 9 | "license": "MIT", 10 | "ignore": [ 11 | "**/.*", 12 | "node_modules", 13 | "bower_components", 14 | "test", 15 | "coverage", 16 | "example", 17 | "gulpfile.js", 18 | "bower.json", 19 | "package.json", 20 | "lib" 21 | ], 22 | "dependencies": { 23 | "angular": "~1.3.0" 24 | }, 25 | "devDependencies": { 26 | "angular-mocks": "~1.3.0", 27 | "closure-library": "google/closure-library", 28 | "closure-compiler": "http://dl.google.com/closure-compiler/compiler-latest.zip", 29 | "closure-angularjs-externs": "https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/angular-1.3.js", 30 | "closure-angularjs-q_templated-externs": "https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/angular-1.3-q_templated.js", 31 | "closure-angularjs-http-promise_templated-externs": "https://raw.githubusercontent.com/google/closure-compiler/master/contrib/externs/angular-1.3-http-promise_templated.js" 32 | }, 33 | "keywords": [ 34 | "date", 35 | "select", 36 | "input", 37 | "day", 38 | "month", 39 | "year", 40 | "time", 41 | "datetime", 42 | "time", 43 | "angular", 44 | "closure", 45 | "gulp" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /example/year.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | YearSelect Directive 7 | 8 | 9 | 26 | 27 | 28 |
29 |
30 |
38 |
form.year01 = {{ form['year01'] | json }}
39 |
47 |
form.year02 = {{ form['year02'] | json }}
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /lib/monthselect.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.directive.MonthSelectFactory'); 4 | 5 | goog.require('leodido.directive.GenericSelect'); 6 | goog.require('leodido.constants.MonthSelect'); 7 | 8 | /** 9 | * MonthSelect directive 10 | * 11 | * @constructor 12 | * @extends {leodido.directive.GenericSelect} 13 | */ 14 | leodido.directive.MonthSelect = function () { 15 | goog.base(this); 16 | goog.DEBUG && console.log('MonthSelectDirective::constructor'); 17 | }; 18 | goog.inherits(leodido.directive.MonthSelect, leodido.directive.GenericSelect); 19 | 20 | /** 21 | * @inheritDoc 22 | */ 23 | leodido.directive.MonthSelect.prototype.prelink = function (scope, iElem, iAttrs) { 24 | /* jshint -W069 */ 25 | // Settings (attributes and related scope variables) checking 26 | iAttrs.$set('first', leodido.constants.MonthSelect.DEFAULT_FIRST); 27 | iAttrs.$set('last', leodido.constants.MonthSelect.DEFAULT_LAST); 28 | var filter = iElem.injector().get('filterFilter'); 29 | if (filter(leodido.constants.MonthSelect.FORMATS, iAttrs['format'], true).length !== 1) { 30 | iAttrs.$set('format', leodido.constants.MonthSelect.DEFAULT_FORMAT); 31 | } 32 | }; 33 | 34 | /** 35 | * MonthSelect directive factory 36 | * 37 | * @returns {!angular.Directive} Directive definition object 38 | * @constructor 39 | */ 40 | leodido.directive.MonthSelectFactory = function () { 41 | goog.DEBUG && console.log('MonthSelectFactory::constructor'); 42 | return (new leodido.directive.MonthSelect()) 43 | .setDirective(leodido.constants.MonthSelect.DIRECTIVE_NAME) 44 | .setController(leodido.constants.MonthSelect.CONTROLLER_NAME) 45 | .getDefinition(); 46 | }; 47 | -------------------------------------------------------------------------------- /example/month.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MonthSelect Directive 7 | 8 | 9 | 26 | 27 | 28 |
29 |
30 |
36 |
form.month01 = {{ form.month01 | json }}
37 |
43 |
form.month02 = {{ form.month02 | json }}
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ng-date-elements", 3 | "version": "0.4.0", 4 | "description": "AngularJS directives for date elements", 5 | "main": "date-elements.min.js", 6 | "author": { 7 | "name": "Leo Di Donato", 8 | "email": "leodidonato@gmail.com", 9 | "url": "http://git.io/leodido" 10 | }, 11 | "license": "MIT", 12 | "repository": { 13 | "type": "git", 14 | "url": "http://github.com/leodido/ng-date-elements.git" 15 | }, 16 | "homepage": "http://github.com/leodido/ng-date-elements", 17 | "devDependencies": { 18 | "bower": "~1.3.12", 19 | "del": "^1.0.0", 20 | "gulp": "~3.8.10", 21 | "gulp-bump": "^0.1.13", 22 | "gulp-closure-compiler": "~0.2.17", 23 | "gulp-connect": "~2.2.0", 24 | "gulp-debug": "^2.0.0", 25 | "gulp-help": "^1.3.1", 26 | "gulp-jshint": "~1.9.0", 27 | "gulp-util": "~3.0.0", 28 | "jasmine-spec-reporter": "~2.1.0", 29 | "karma": "~0.12.31", 30 | "karma-chrome-launcher": "~0.1.7", 31 | "karma-coverage": "~0.2.7", 32 | "karma-firefox-launcher": "~0.1.4", 33 | "karma-jasmine": "~0.3.5", 34 | "karma-phantomjs-launcher": "~0.1.4", 35 | "karma-spec-reporter": "~0.0.16", 36 | "minimist": "~1.1.0", 37 | "protractor": "^1.7.0", 38 | "run-sequence": "^1.0.2" 39 | }, 40 | "scripts": { 41 | "postinstall": "./node_modules/.bin/bower install", 42 | "production": "./node_modules/.bin/gulp build --env production --banner", 43 | "development": "./node_modules/.bin/gulp build --env development --banner", 44 | "unit": "./node_modules/karma/bin/karma start karma.conf.js", 45 | "e2e": "(./node_modules/protractor/bin/webdriver-manager update) && (./node_modules/protractor/bin/protractor protractor.conf.js)" 46 | }, 47 | "keywords": [ 48 | "date", 49 | "select", 50 | "input", 51 | "day", 52 | "month", 53 | "year", 54 | "time", 55 | "datetime", 56 | "time", 57 | "angular", 58 | "closure", 59 | "gulp" 60 | ], 61 | "directories": { 62 | "lib": "./lib", 63 | "example": "./example" 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /lib/yearselect.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.directive.YearSelectFactory'); 4 | 5 | goog.require('leodido.directive.GenericSelect'); 6 | goog.require('leodido.constants.YearSelect'); 7 | 8 | /** 9 | * YearSelect directive 10 | * 11 | * @constructor 12 | * @extends {leodido.directive.GenericSelect} 13 | */ 14 | leodido.directive.YearSelect = function () { 15 | goog.base(this); 16 | goog.DEBUG && console.log('YearSelectDirective::constructor'); 17 | }; 18 | goog.inherits(leodido.directive.YearSelect, leodido.directive.GenericSelect); 19 | 20 | /** 21 | * @inheritDoc 22 | */ 23 | leodido.directive.YearSelect.prototype.prelink = function (scope, iElem, iAttrs, controllers) { 24 | /* jshint -W069 */ 25 | // Settings (attributes and related scope variables) checking 26 | var first, 27 | last, 28 | filter = iElem.injector().get('filterFilter'); 29 | if (!/^\d{4}$/.test(iAttrs['first'])) { 30 | first = leodido.constants.YearSelect.DEFAULT_FIRST; 31 | iAttrs.$set('first', first); 32 | } else { 33 | first = iAttrs['first']; 34 | } 35 | first = + first; 36 | if (!/^\d{4}$/.test(iAttrs['last'])) { 37 | last = '' + (first + leodido.constants.YearSelect.DEFAULT_RANGE); 38 | iAttrs.$set('last', last); 39 | } else { 40 | last = iAttrs['last']; 41 | } 42 | last = + last; 43 | if (first > last) { 44 | throw new Error('Last year must be greather than the first year (i.e., ' + first + '); received: ' + last); 45 | } 46 | if (filter(leodido.constants.YearSelect.FORMATS, iAttrs['format'], true).length !== 1) { 47 | iAttrs.$set('format', leodido.constants.YearSelect.DEFAULT_FORMAT); 48 | } 49 | }; 50 | 51 | /** 52 | * YearSelect directive factory 53 | * 54 | * @returns {!angular.Directive} Directive definition object 55 | * @constructor 56 | */ 57 | leodido.directive.YearSelectFactory = function () { 58 | goog.DEBUG && console.log('YearSelectFactory::constructor'); 59 | return (new leodido.directive.YearSelect()) 60 | .setDirective(leodido.constants.YearSelect.DIRECTIVE_NAME) 61 | .setController(leodido.constants.YearSelect.CONTROLLER_NAME) 62 | .getDefinition(); 63 | }; 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Date elements 2 | ============= 3 | 4 | **AngularJS select directives for date elements**. 5 | 6 | [![Bower](https://img.shields.io/bower/v/ng-date-elements.svg?style=flat-square)]() 7 | 8 | ### Directives: 9 | 10 | - MonthSelect 11 | - YearSelect 12 | - SelectComposite 13 | - ExpirySelect 14 | 15 | *WIP* 16 | 17 | Usage 18 | ----- 19 | 20 | *WIP* 21 | 22 | Install 23 | ------- 24 | 25 | Install it via `bower`. 26 | 27 | ``` 28 | bower install ng-date-elements 29 | ``` 30 | 31 | Or, you can clone this repo and install it locally (you will need `npm`, of course). 32 | 33 | ``` 34 | $ git clone git@github.com:leodido/ng-date-elements.git 35 | $ cd ng-date-elements/ 36 | $ npm install 37 | ``` 38 | 39 | Otherwise you can simply grab `date-elements.min.js` file in the repository root, and include it together with angular (~1.3) in your HTML page. 40 | 41 | Build 42 | ----- 43 | 44 | Build is handled through [Gulp](https://github.com/gulpjs/gulp/) and performed mainly via [Google Closure Compiler](https://github.com/google/closure-compiler). 45 | 46 | Need help? Run `gulp help` ! 47 | 48 | ``` 49 | # Usage 50 | # gulp [task] 51 | # 52 | # Available tasks 53 | # build Build the library 54 | # --banner Prepend banner to the built file 55 | # --env=production|development Kind of build to perform, defaults to production 56 | # bump Bump version up for a new release 57 | # --level=major|minor|patch|prerelease Version level to bump 58 | # clean Clean build directory 59 | # help Display this help text 60 | # lint Lint JS source files 61 | # version Print the library version 62 | ``` 63 | 64 | To build a development version (logging not disabled, sourcemaps file) of JS lib: 65 | 66 | ``` 67 | $ gulp build --env dev 68 | ``` 69 | 70 | Or, also: 71 | 72 | ``` 73 | $ npm run development 74 | ``` 75 | 76 | Rather to build a production ready code: 77 | 78 | ``` 79 | $ npm run production 80 | ```` 81 | 82 | --- 83 | 84 | [![Analytics](https://ga-beacon.appspot.com/UA-49657176-1/ng-date-elements)](https://github.com/igrigorik/ga-beacon) 85 | -------------------------------------------------------------------------------- /lib/selectcomposite.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.directive.SelectCompositeFactory'); 4 | 5 | /** 6 | * A directive for composite selects 7 | * It allows the construction of a group of select directives 8 | * 9 | * @constructor 10 | */ 11 | leodido.directive.SelectComposite = function () { 12 | goog.DEBUG && console.log('SelectCompositeDirective::constructor'); 13 | }; 14 | 15 | /** 16 | * Retrieve the DDO 17 | * 18 | * @return {!angular.Directive} 19 | */ 20 | leodido.directive.SelectComposite.prototype.getDefinition = function () { 21 | return { 22 | restrict: 'AC', 23 | transclude: true, 24 | replace: false, 25 | template: '
', 26 | controller: this.controller 27 | }; 28 | }; 29 | 30 | /** 31 | * SelectComposite controller 32 | * 33 | * @param {!angular.Scope} $scope 34 | * @param {!angular.Attributes} $attrs 35 | * @ngInject 36 | */ 37 | leodido.directive.SelectComposite.prototype.controller = function ($scope, $attrs) { 38 | goog.DEBUG && console.log('SelectCompositeDirective::controller'); 39 | /** @type {string} */ 40 | $attrs.options; 41 | /** 42 | * Contains the value of the options attribute's expression 43 | * 44 | * @expose 45 | */ 46 | $scope.options = $scope.$eval($attrs.options); 47 | /** 48 | * Keeps track of the selects 49 | * 50 | * @type {!Array} 51 | */ 52 | this.selects = []; 53 | /** 54 | * Add a select to the select list 55 | * Use it from the select directive to add itself to the select list. 56 | * 57 | * @param {!angular.Scope} selectScope 58 | * @expose 59 | */ 60 | this.addSelect = function (selectScope) { 61 | var that = this; 62 | // 63 | this.selects.push(selectScope); 64 | // Handle destroy 65 | selectScope.$on('$destroy', function (event) { 66 | that.removeSelect(selectScope); 67 | }); 68 | }; 69 | /** 70 | * Remove a select from the select list 71 | * Called from the select directive when to remove itself (i.e., on destroy). 72 | * 73 | * @param {!angular.Scope} selectScope 74 | * @expose 75 | */ 76 | this.removeSelect = function (selectScope) { 77 | var index = this.selects.indexOf(selectScope); 78 | if (index !== -1) { 79 | this.selects.splice(index, 1); 80 | } 81 | }; 82 | }; 83 | 84 | /** 85 | * SelectComposite directive factory 86 | * 87 | * @returns {!angular.Directive} Directive definition object 88 | * @constructor 89 | */ 90 | leodido.directive.SelectCompositeFactory = function () { 91 | goog.DEBUG && console.log('SelectCompositeFactory::constructor'); 92 | return (new leodido.directive.SelectComposite()).getDefinition(); 93 | }; 94 | -------------------------------------------------------------------------------- /lib/genericselect.controller.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.controller.GenericSelect'); 4 | 5 | goog.require('leodido.typedef.SelectOption'); 6 | 7 | /** 8 | * Controller for GenericSelect directives 9 | * 10 | * @param {!angular.Scope=} $scope 11 | * @param {!angular.JQLite=} $element 12 | * @param {!angular.Attributes=} $attrs 13 | * @constructor 14 | * @ngInject 15 | */ 16 | leodido.controller.GenericSelect = function ($scope, $element, $attrs) { 17 | goog.DEBUG && console.log('GenericSelectController::constructor'); 18 | var self = this, 19 | filter = $element.injector().get('filterFilter'); 20 | // Privileged methods 21 | /** 22 | * Setup the initial state of the directive 23 | */ 24 | this.setup = function () { 25 | goog.DEBUG && console.log('GenericSelectController::setup', $scope); 26 | $scope.element = $scope.element || {}; 27 | /** 28 | * Options array 29 | * 30 | * @expose 31 | * @type {Array.} 32 | */ 33 | $scope.element.opts = []; 34 | for (var i = self.getFirst(), ii = self.getLast(); i <= ii; i++) { 35 | $scope.element.opts.push({ 36 | index: i, 37 | label: self.getLabel(i), 38 | value: self.getValue(i) 39 | }); 40 | } 41 | }; 42 | /** 43 | * Validate option 44 | * 45 | * @param {SelectOption} option 46 | * @return {!boolean} 47 | */ 48 | this.isValid = function (option) { 49 | return angular.isDefined(option) && 50 | option.index >= this.getFirst() && 51 | option.index <= this.getLast() && 52 | option.value === this.getValue(option.index) && 53 | option.label === this.getLabel(option.index); 54 | }; 55 | /** 56 | * Search options by pair key/value 57 | * 58 | * @param {String} key 59 | * @param {String} value 60 | * @return Array.{SelectOption} 61 | */ 62 | this.searchOptions = function (key, value) { 63 | var target = {}; 64 | target[key] = value; 65 | return filter($scope.element.opts, target, true); 66 | }; 67 | /** 68 | * Retrive the index of the first option 69 | * 70 | * @return {!number} 71 | */ 72 | this.getFirst = function () { 73 | /** @type {!string} first */ 74 | $scope.first; 75 | return + $scope.first; 76 | }; 77 | /** 78 | * Retrieve the index of the last option 79 | * 80 | * @return {!number} 81 | */ 82 | this.getLast = function () { 83 | /** @type {!string} last */ 84 | $scope.last; 85 | return + $scope.last; 86 | }; 87 | }; 88 | 89 | /** 90 | * Retrieve the label of an option 91 | * 92 | * @param {!number} index The option index 93 | * @returns {!string} 94 | */ 95 | leodido.controller.GenericSelect.prototype.getLabel = goog.abstractMethod; 96 | 97 | /** 98 | * Retrive the value of an option (i.e. the model value) 99 | * 100 | * @param {!number} index The option index 101 | * @returns {!string} 102 | */ 103 | leodido.controller.GenericSelect.prototype.getValue = goog.abstractMethod; 104 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'), 4 | debug = require('gulp-debug'), 5 | comp = require('gulp-closure-compiler'), 6 | jshint = require('gulp-jshint'), 7 | minimist = require('minimist'), 8 | gutil = require('gulp-util'), 9 | bump = require('gulp-bump'), 10 | del = require('del'), 11 | sequence = require('run-sequence'), 12 | connect = require('gulp-connect'), 13 | pack = require('./package.json'), 14 | banner = [ 15 | '/*', 16 | ' * Copyright (c) ' + new Date().getFullYear() + ', ' + pack.author.name + ' <' + pack.author.email + '>', 17 | ' * ' + pack.main + ' - ' + pack.description, 18 | ' * @version '+ pack.version, 19 | ' * @link ' + pack.homepage, 20 | ' * @license ' + pack.license, 21 | ' */' 22 | ].join('\n'); 23 | gulp = gulp = require('gulp-help')(gulp, { 24 | description: 'Display this help text' 25 | }); 26 | 27 | var knownFlags = { 28 | 'boolean': ['banner'], 29 | 'string': ['env', 'level'], 30 | 'default': { 31 | env: 'production', 32 | banner: false, 33 | level: 'patch' 34 | }, 35 | 'alias': { e: 'env', b: 'banner', l: 'level' } 36 | }, 37 | flags = minimist(process.argv.slice(2), knownFlags), 38 | productionBuild = function(value) { 39 | switch(value) { 40 | case 'prod': 41 | case 'production': 42 | return true; 43 | case 'dev': 44 | case 'development': 45 | return false; 46 | default: 47 | return true; 48 | } 49 | }, 50 | getVersionLevel = function() { 51 | if (['major', 'minor', 'patch', 'prerelease'].indexOf(flags.level) === -1) { 52 | flags.level = 'patch'; 53 | } 54 | return flags.level; 55 | }; 56 | 57 | var build = { 58 | compiler: './bower_components/closure-compiler/compiler.jar', 59 | filename: pack.main, 60 | externs: [ 61 | './bower_components/closure-angularjs-externs/index.js', 62 | './bower_components/closure-angularjs-q_templated-externs/index.js', 63 | './bower_components/closure-angularjs-http-promise_templated-externs/index.js' 64 | ], 65 | src: pack.directories.lib + '/*.js', 66 | deps: [ 67 | 'bower_components/closure-library/closure/goog/base.js' 68 | ] 69 | }; 70 | 71 | gulp.task('version', 'Print the library version', [], function() { 72 | return gutil.log('Library', gutil.colors.magenta(pack.name) + ',', gutil.colors.magenta(pack.version)); 73 | }); 74 | 75 | gulp.task('lint', 'Lint JS source files', [], function() { 76 | return gulp.src(build.src) 77 | .pipe(jshint('./.jshintrc')) 78 | .pipe(jshint.reporter('default')); 79 | }); 80 | 81 | gulp.task('minify', false, [], function() { 82 | var isProduction = productionBuild(flags.env), 83 | sourcemap = build.filename + '.map', 84 | wrapper = (flags.banner ? banner + '\n': '') + 85 | '(function(){%output%})();' + 86 | (isProduction ? '' : '\n//# sourceMappingURL=' + sourcemap); 87 | 88 | var compilerFlags = { 89 | // closure_entry_point: 'leodido.Main', 90 | // only_closure_dependencies: true, 91 | compilation_level: 'ADVANCED_OPTIMIZATIONS', 92 | language_in: 'ECMASCRIPT3', 93 | angular_pass: true, 94 | formatting: 'SINGLE_QUOTES', 95 | externs: build.externs, 96 | generate_exports: true, 97 | define: [ 98 | 'goog.DEBUG=' + (isProduction ? 'false' : 'true') 99 | ], 100 | output_wrapper: wrapper, 101 | warning_level: 'VERBOSE' 102 | }; 103 | if (!isProduction) { 104 | compilerFlags.create_source_map = sourcemap; 105 | } 106 | 107 | return gulp.src(build.deps.concat(build.src)) 108 | .pipe(debug({title: 'File: '})) 109 | .pipe(comp({ 110 | compilerPath: build.compiler, 111 | fileName: build.filename, 112 | compilerFlags: compilerFlags 113 | })); 114 | }); 115 | 116 | //gulp.task('prova', false, [], function () { 117 | // var isProduction = productionBuild(flags.env); 118 | // var wrapper = (flags.banner ? banner + '\n': ''); 119 | // wrapper += '(function(){'; 120 | // wrapper += '\n'; 121 | // wrapper += '%output%})();'; // + 122 | // // (isProduction ? '' : '\n//# sourceMappingURL=' + sourcemap); 123 | // 124 | // var compilerFlags = { 125 | //// closure_entry_point: 'leodido.Main', 126 | // compilation_level: 'SIMPLE_OPTIMIZATIONS', 127 | // language_in: 'ECMASCRIPT3', 128 | // angular_pass: true, 129 | // formatting: ['SINGLE_QUOTES', 'PRETTY_PRINT'], 130 | // externs: build.externs, 131 | // generate_exports: true, 132 | //// manage_closure_dependencies: true, 133 | //// only_closure_dependencies: true, 134 | // define: [ 135 | // 'goog.DEBUG=' + (isProduction ? 'false' : 'true') 136 | // ], 137 | // output_wrapper: wrapper 138 | //// warning_level: 'VERBOSE' 139 | // }; 140 | // 141 | // return gulp.src(build.src) // build.deps.concat(build.src)) 142 | // .pipe(debug({title: 'File: '})) 143 | // .pipe(comp({ 144 | // compilerPath: build.compiler, 145 | // fileName: build.directory + '/' + build.filename, 146 | // compilerFlags: compilerFlags 147 | // })); 148 | //}); 149 | 150 | gulp.task('bump', 'Bump version up for a new release', function () { 151 | return gulp.src(['./bower.json', 'package.json']) 152 | .pipe(bump({ type: getVersionLevel() })) 153 | .pipe(gulp.dest('./')); 154 | }, { 155 | options: { 156 | 'level=major|minor|patch|prerelease': 'Version level to bump' 157 | } 158 | }); 159 | 160 | gulp.task('clean', 'Clean build directory', function(cb) { 161 | del([build.filename, build.filename + '.map'], cb); 162 | }); 163 | 164 | gulp.task('build', 'Build the library', [], function(cb) { 165 | return sequence( 166 | 'clean', 167 | 'lint', 168 | 'minify', 169 | cb); 170 | }, { 171 | options: { 172 | 'env=production|development': 'Kind of build to perform, defaults to production', 173 | 'banner': 'Prepend banner to the built file' 174 | } 175 | }); 176 | 177 | gulp.task('connect', 'Create a server', function () { 178 | connect.server({ 179 | port: 8000, 180 | livereload: true 181 | }); 182 | }); 183 | 184 | gulp.task('default', false, ['help']); 185 | 186 | // TODO 187 | // [ ] - beautified release file 188 | -------------------------------------------------------------------------------- /lib/expiryselect.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.directive.ExpirySelectFactory'); 4 | 5 | goog.require('leodido.constants.ExpirySelect'); 6 | 7 | /** 8 | * Select directive for expiration data 9 | * 10 | * @constructor 11 | */ 12 | leodido.directive.ExpirySelect = function () { 13 | goog.DEBUG && console.log('ExpirySelectDirective::constructor'); 14 | }; 15 | 16 | /** 17 | * Retrieve the DDO 18 | * 19 | * @return {!angular.Directive} 20 | */ 21 | leodido.directive.ExpirySelect.prototype.getDefinition = function () { 22 | var self = this; 23 | return { 24 | restrict: 'AC', 25 | require: '?ngModel', 26 | replace: false, 27 | scope: { 28 | /** 29 | * @expose 30 | */ 31 | getOptions: '&options', 32 | /** 33 | * @expose 34 | */ 35 | name: '@' 36 | }, 37 | template: [ 38 | '
', 41 | '
', 49 | '
', 59 | '
' 60 | ].join(''), 61 | controller: this.controller, 62 | compile: goog.bind(self.build, self) 63 | // link: goog.bind(self.postlink, self) 64 | }; 65 | }; 66 | 67 | /** 68 | * ExpirySelect controller function 69 | * 70 | * @param {!angular.Scope=} $scope 71 | * @ngInject 72 | */ 73 | leodido.directive.ExpirySelect.prototype.controller = function ($scope) { 74 | goog.DEBUG && console.log('ExpirySelectDirective::controller', $scope); 75 | /** 76 | * @expose 77 | * @type {!Object} 78 | */ 79 | $scope.expiry = $scope.expiry || {}; 80 | /** @type {!Function} getOptions */ 81 | $scope.getOptions; 82 | var options = $scope.getOptions() || {}; 83 | /** @type {?string} name */ 84 | $scope.name; 85 | // Extend/override options 86 | angular.extend( 87 | $scope, 88 | { 89 | /** 90 | * @expose 91 | */ 92 | name: $scope.name || leodido.constants.ExpirySelect.DEFAULT_NAME, 93 | /** 94 | * @expose 95 | */ 96 | getOptions: function () { 97 | // Assure objects exist 98 | options.month = options.month || {}; 99 | options.year = options.year || {}; 100 | // Create child names 101 | var mName = options.month.name || leodido.constants.ExpirySelect.DEFAULT_MONTH_NAME, 102 | yName = options.year.name || leodido.constants.ExpirySelect.DEFAULT_YEAR_NAME; 103 | mName = $scope.name + '[' + mName + ']'; 104 | yName = $scope.name + '[' + yName + ']'; 105 | // Assign child names 106 | options.month.name = mName; 107 | options.year.name = yName; 108 | // Return options 109 | return options; 110 | } 111 | } 112 | ); 113 | // API 114 | this.setMonth = function (month) { 115 | $scope.expiry.month = month; 116 | return this; 117 | }; 118 | this.setYear = function (year) { 119 | $scope.expiry.year = year; 120 | return this; 121 | }; 122 | this.isPast = function (m, yyyy) { 123 | return Date.now() >= new Date(yyyy, m, 0); 124 | }; 125 | }; 126 | 127 | /** 128 | * ExpirySelect compile function 129 | * 130 | * @param {!angular.JQLite=} tElement Template element 131 | * @param {!angular.Attributes=} tAttrs Template attributes 132 | * @return {Function|angular.LinkingFunctions|undefined} 133 | * @ngInject 134 | */ 135 | leodido.directive.ExpirySelect.prototype.build = function (tElement, tAttrs) { 136 | goog.DEBUG && console.log('ExpirySelectDirective::compile'); 137 | // Force 'name' attribute (if it exists, also if empty) to be 'data-name' attribute 138 | if (angular.isDefined(tElement.attr('name'))) { 139 | tElement.attr('data-name', tElement.attr('name')); 140 | tAttrs.$attr.name = 'data-name'; // Needed to support interpolation that comes ... 141 | tElement.removeAttr('name'); 142 | } 143 | 144 | var self = this; 145 | return goog.bind(self.postlink, self); 146 | }; 147 | 148 | /** 149 | * ExpirySelect post-link function 150 | * 151 | * @param {!angular.Scope=} $scope 152 | * @param {!angular.JQLite=} $element 153 | * @param {!angular.Attributes=} $attrs 154 | * @param {!Object=} ngModelController 155 | * @ngInject 156 | */ 157 | leodido.directive.ExpirySelect.prototype.postlink = function ($scope, $element, $attrs, ngModelController) { 158 | goog.DEBUG && console.log('ExpirySelectDirective::postlink'); 159 | 160 | var controller = $element.data('$' + leodido.constants.ExpirySelect.DIRECTIVE_NAME + 'Controller'); 161 | // Capture selections 162 | $scope.$on($scope.options.month.name + '.selection', function (event, month) { 163 | controller.setMonth(month); 164 | }); 165 | $scope.$on($scope.options.year.name + '.selection', function (event, year) { 166 | controller.setYear(year); 167 | }); 168 | // Watch month and year values and check if date is future (so it is a valid one) or not 169 | if (ngModelController) { 170 | $scope.$watchCollection('[expiry.month, expiry.year]', function (values) { 171 | var m = values[0], 172 | y = values[1], 173 | validity = m && y ? !controller.isPast(m.value, y.value) : false; 174 | ngModelController.$setValidity(leodido.constants.ExpirySelect.DEFAULT_NAME, validity); 175 | }); 176 | } 177 | }; 178 | 179 | leodido.directive.ExpirySelectFactory = function () { 180 | goog.DEBUG && console.log('ExpirySelectFactory::constructor'); 181 | return (new leodido.directive.ExpirySelect()).getDefinition(); 182 | }; 183 | -------------------------------------------------------------------------------- /date-elements.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2015, Leo Di Donato 3 | * date-elements.min.js - AngularJS directives for date elements 4 | * @version 0.4.0 5 | * @link http://github.com/leodido/ng-date-elements 6 | * @license MIT 7 | */ 8 | (function(){function e(a,b,c){return a.call.apply(a.bind,arguments)}function g(a,b,c){if(!a)throw Error();if(2
',controller:this.controller, 11 | compile:h(this.b,this)}};p.prototype.controller=function(a){a.expiry=a.expiry||{};var b=a.getOptions()||{};angular.extend(a,{name:a.name||'expiry',getOptions:function(){b.month=b.month||{};b.year=b.year||{};var c=b.month.name||'month',d=b.year.name||'year',c=a.name+'['+c+']',d=a.name+'['+d+']';b.month.name=c;b.year.name=d;return b}});this.setMonth=function(c){a.expiry.month=c;return this};this.setYear=function(c){a.expiry.year=c;return this};this.r=function(a,b){return Date.now()>=new Date(b,a,0)}}; 12 | p.prototype.controller.$inject=['$scope'];p.prototype.b=function(a,b){angular.isDefined(a.attr('name'))&&(a.attr('data-name',a.attr('name')),b.$attr.name='data-name',a.removeAttr('name'));return h(this.j,this)};p.prototype.b.$inject=['tElement','tAttrs']; 13 | p.prototype.j=function(a,b,c,d){var f=b.data('$expirySelectController');a.$on(a.options.month.name+'.selection',function(a,c){f.setMonth(c)});a.$on(a.options.year.name+'.selection',function(a,c){f.setYear(c)});d&&a.$watchCollection('[expiry.month, expiry.year]',function(a){var c=a[0];a=a[1];d.$setValidity('expiry',c&&a?!f.r(c.value,a.value):!1)})};p.prototype.j.$inject=['$scope','$element','$attrs','ngModelController'];var q=['MMMM','MMM','MM','M'],r=q[2];function t(){}t.prototype.a=function(){return{restrict:'AC',transclude:!0,replace:!1,template:'
',controller:this.controller}};t.prototype.controller=function(a,b){a.options=a.$eval(b.options);this.f=[];this.addSelect=function(a){var b=this;this.f.push(a);a.$on('$destroy',function(){b.removeSelect(a)})};this.removeSelect=function(a){a=this.f.indexOf(a);-1!==a&&this.f.splice(a,1)}};t.prototype.controller.$inject=['$scope','$attrs'];function u(a,b){var c=this,d=b.injector().get('filterFilter');this.t=function(){a.element=a.element||{};a.element.opts=[];for(var b=c.h(),d=c.i();b<=d;b++)a.element.opts.push({index:b,label:c.c(b),value:c.d(b)})};this.s=function(a){return angular.isDefined(a)&&a.index>=this.h()&&a.index<=this.i()&&a.value===this.d(a.index)&&a.label===this.c(a.index)};this.k=function(b,c){var l={};l[b]=c;return d(a.element.opts,l,!0)};this.h=function(){return+a.first};this.i=function(){return+a.last}} 14 | u.$inject=['$scope','$element','$attrs'];function v(){var a='',b='',c='';this.g=function(){return b};this.q=function(){return c};this.p=function(){return a};this.m=function(a){if(/[^a-zA-Z]/.test(a))throw new TypeError('alpha string required; received "'+typeof a+'"');b=a;c=b.match(/([A-Z]?[^A-Z]*)/g).shift();return this};this.l=function(b){if(!angular.isString(b))throw new TypeError('string required; received "'+typeof b+'"');a=b;return this}} 15 | v.prototype.a=function(){return{restrict:'AC',replace:!0,require:[this.g(),'ngModel','?^^selectComposite'],scope:{emptyOption:'@',format:'@',first:'@',last:'@'},controller:this.p(),compile:h(this.b,this),template:''}}; 16 | v.prototype.b=function(a,b){angular.isDefined(a.attr('data-name'))&&(a.attr('name',a.attr('data-name')),b.$attr.name='name',a.removeAttr('data-name'));return{pre:h(this.pre,this),post:h(this.post,this)}};v.prototype.b.$inject=['tElement','tAttrs'];v.prototype.pre=function(a,b,c){this.e(a,b,c)};v.prototype.pre.$inject=['scope','iElem','iAttrs'];v.prototype.e=function(){}; 17 | v.prototype.post=function(a,b,c,d){if(!(2>d.length)){var f=d[0],k=d[1];b=d[2];d=this.g();var l=this.q(),m=function(b){k.$selection=b;k.$name&&a.$emit(k.$name+'.selection',b)},x=function(a){var b=f.s(a);k.$setValidity(l,b);if(b)return m(a),a;m(null)};b&&b.addSelect(a);f.t();c.$observe(d,function(b){b=f.k('label',b);1===b.length&&(a.model=b[0])});c.$observe('value',function(b){b=f.k('value',b);1===b.length&&angular.isUndefined(a.model)&&(a.model=b[0])});k.$parsers.unshift(x);k.$formatters.unshift(x)}}; 18 | v.prototype.post.$inject=['$scope','$element','$attrs','controllers'];function w(a,b){var c=b.injector().get('dateFilter');this.n=function(b){return c((new Date).setMonth(b),a.format)};u.call(this,a,b)}n(w,u);w.$inject=['$scope','$element','$attrs'];w.prototype.d=function(a){return(a+1).toString()};w.prototype.c=function(a){return this.n(a)};function y(){v.call(this)}n(y,v);y.prototype.e=function(a,b,c){c.$set('first','0');c.$set('last','11');1!==b.injector().get('filterFilter')(q,c.format,!0).length&&c.$set('format',r)};var z=['yyyy','yy','y'],A=z[0],B=''+(new Date).getFullYear();function C(a,b){var c=b.injector().get('dateFilter');this.o=function(b){return c(new Date(b.toString()),a.format)};u.call(this,a,b)}n(C,u);C.$inject=['$scope','$element','$attrs'];C.prototype.d=function(a){return a.toString()};C.prototype.c=function(a){return this.o(a)};function D(){v.call(this)}n(D,v);D.prototype.e=function(a,b,c){var d=b.injector().get('filterFilter');/^\d{4}$/.test(c.first)?a=c.first:(a=B,c.$set('first',a));a=+a;/^\d{4}$/.test(c.last)?b=c.last:(b=''+(a+10),c.$set('last',b));b=+b;if(a>b)throw Error('Last year must be greather than the first year (i.e., '+a+'); received: '+b);1!==d(z,c.format,!0).length&&c.$set('format',A)};angular.module('leodido.dateElements',[]).directive('expirySelect',function(){return(new p).a()}).directive('selectComposite',function(){return(new t).a()}).controller('MonthSelectController',w).directive('monthSelect',function(){return(new y).m('monthSelect').l('MonthSelectController').a()}).controller('YearSelectController',C).directive('yearSelect',function(){return(new D).m('yearSelect').l('YearSelectController').a()});})(); 19 | -------------------------------------------------------------------------------- /lib/genericselect.directive.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | goog.provide('leodido.directive.GenericSelect'); 4 | 5 | goog.require('leodido.typedef.SelectOption'); 6 | 7 | /** 8 | * A generic directive 9 | * It allows the construction of other select directives for specific date elements (e.g. month) 10 | * 11 | * @constructor 12 | */ 13 | leodido.directive.GenericSelect = function () { 14 | // Privates 15 | /** 16 | * @type {!string} 17 | */ 18 | var ctrlName = ''; 19 | /** 20 | * @type {!string} 21 | */ 22 | var directName = ''; 23 | /** 24 | * @type {!string} 25 | */ 26 | var elementName = ''; 27 | // Privileged methods 28 | /** 29 | * Retrive the directive name 30 | * 31 | * @return {!string} 32 | */ 33 | this.getDirective = function () { 34 | return directName; 35 | }; 36 | /** 37 | * Retrieve the element name 38 | * 39 | * @return {!string} 40 | */ 41 | this.getElement = function () { 42 | return elementName; 43 | }; 44 | /** 45 | * Retrieve the controller name 46 | * 47 | * @return {!string} 48 | */ 49 | this.getController = function () { 50 | return ctrlName; 51 | }; 52 | /** 53 | * Set the directive name 54 | * 55 | * @param {!string} directiveName 56 | * @return {leodido.directive.GenericSelect} 57 | */ 58 | this.setDirective = function (directiveName) { 59 | if (/[^a-zA-Z]/.test(directiveName)) { 60 | throw new TypeError('alpha string required; received "' + typeof directiveName + '"'); 61 | } 62 | directName = directiveName; 63 | elementName = directName.match(/([A-Z]?[^A-Z]*)/g).shift(); 64 | return this; 65 | }; 66 | /** 67 | * Set the controller name 68 | * 69 | * @param {!string} controllerName 70 | * @return {leodido.directive.GenericSelect} 71 | */ 72 | this.setController = function (controllerName) { 73 | if (!angular.isString(controllerName)) { 74 | throw new TypeError('string required; received "' + typeof controllerName + '"'); 75 | } 76 | ctrlName = controllerName; 77 | return this; 78 | }; 79 | }; 80 | 81 | /** 82 | * Retrieve the DDO 83 | * 84 | * @return {!angular.Directive} 85 | */ 86 | leodido.directive.GenericSelect.prototype.getDefinition = function () { 87 | var self = this; 88 | return { 89 | restrict: 'AC', 90 | replace: true, 91 | require: [ 92 | self.getDirective(), 93 | 'ngModel', 94 | '?^^' + leodido.constants.SelectComposite.DIRECTIVE_NAME 95 | ], 96 | scope: { 97 | /** 98 | * Empty option 99 | * @expose 100 | * @type {!string} 101 | */ 102 | emptyOption: '@', 103 | /** 104 | * Label format 105 | * @expose 106 | * @type {!string} 107 | */ 108 | format: '@', 109 | /** 110 | * First index 111 | * @expose 112 | * @type {!string} 113 | */ 114 | first: '@', 115 | /** 116 | * Last index 117 | * @expose 118 | * @type {!string} 119 | */ 120 | last: '@' 121 | }, 122 | controller: self.getController(), 123 | compile: goog.bind(self.build, self), 124 | // link: goog.bind(self.link, self), 125 | template: [ 126 | '' 132 | ].join('') 133 | }; 134 | }; 135 | 136 | /** 137 | * GenericSelect compile function 138 | * 139 | * @param {!angular.JQLite=} tElement Template element 140 | * @param {!angular.Attributes=} tAttrs Template attributes 141 | * @return {Function|angular.LinkingFunctions|undefined} 142 | * @ngInject 143 | */ 144 | leodido.directive.GenericSelect.prototype.build = function (tElement, tAttrs) { 145 | goog.DEBUG && console.log('GenericSelectDirective::compile'); 146 | // Force 'data-name' attribute (if it exists, also if empty) to be 'name' attribute 147 | if (angular.isDefined(tElement.attr('data-name'))) { 148 | tElement.attr('name', tElement.attr('data-name')); 149 | tAttrs.$attr.name = 'name'; // Needed to support interpolation that comes ... 150 | tElement.removeAttr('data-name'); 151 | } 152 | 153 | var self = this; 154 | return { 155 | pre: goog.bind(self.pre, self), 156 | post: goog.bind(self.post, self) 157 | }; 158 | }; 159 | 160 | /** 161 | * GenericSelect pre-link function 162 | * 163 | * @param {!angular.Scope=} scope 164 | * @param {!angular.JQLite=} iElem 165 | * @param {!angular.Attributes=} iAttrs 166 | * @ngInject 167 | */ 168 | leodido.directive.GenericSelect.prototype.pre = function (scope, iElem, iAttrs) { 169 | /* jshint -W069 */ 170 | this.prelink(scope, iElem, iAttrs); 171 | }; 172 | 173 | /** 174 | * GenericSelect custom pre-link function 175 | * 176 | * @param {!angular.Scope=} scope 177 | * @param {!angular.JQLite=} iElem 178 | * @param {!angular.Attributes=} iAttrs 179 | */ 180 | leodido.directive.GenericSelect.prototype.prelink = function (scope, iElem, iAttrs) { 181 | }; 182 | 183 | /** 184 | * GenericSelect post-link function 185 | * 186 | * @param {!angular.Scope} $scope 187 | * @param {!angular.JQLite} $element 188 | * @param {!angular.Attributes} $attrs 189 | * @param {Array} controllers 190 | * @ngInject 191 | */ 192 | leodido.directive.GenericSelect.prototype.post = function ($scope, $element, $attrs, controllers) { 193 | goog.DEBUG && console.log('GenericSelectDirective::link', $scope, controllers); 194 | if (controllers.length < 2) { 195 | return; 196 | } 197 | // Privates 198 | var directiveController = controllers[0]; 199 | var modelController = controllers[1]; 200 | var aggregateController = controllers[2]; 201 | var directiveName = this.getDirective(); 202 | var elementName = this.getElement(); 203 | /** 204 | * Notify selected option 205 | * 206 | * @param {?SelectOption} option 207 | */ 208 | var notify = function (option) { 209 | /** 210 | * @expose 211 | */ 212 | modelController.$selection = option; 213 | modelController.$name && $scope.$emit(modelController.$name + '.selection', option); 214 | }; 215 | /** 216 | * Handle selection change 217 | * 218 | * @param {?SelectOption} option 219 | * @return {?SelectOption} 220 | */ 221 | var change = function (option) { 222 | var flag = directiveController.isValid(option); 223 | modelController.$setValidity(elementName, flag); 224 | if (flag) { 225 | notify(option); 226 | return option; 227 | } 228 | notify(null); 229 | return undefined; 230 | }; 231 | // Setup 232 | aggregateController && aggregateController.addSelect($scope); 233 | directiveController.setup(); 234 | // Attribute handling 235 | $attrs.$observe(directiveName, function (label) { 236 | var opts = directiveController.searchOptions('label', label); 237 | var wellFormatted = opts.length === 1; 238 | if (wellFormatted) { 239 | /** 240 | * @expose 241 | */ 242 | $scope.model = opts[0]; 243 | } 244 | }); 245 | $attrs.$observe('value', function (value) { 246 | var opts = directiveController.searchOptions('value', value); 247 | var found = opts.length === 1; 248 | if (found && angular.isUndefined($scope.model)) { 249 | /** 250 | * @expose 251 | */ 252 | $scope.model = opts[0]; 253 | } 254 | }); 255 | // Behaviour 256 | modelController.$parsers.unshift(change); 257 | modelController.$formatters.unshift(change); 258 | }; 259 | --------------------------------------------------------------------------------