diff --git a/Gruntfile.js b/Gruntfile.js index 72d0c35..66de93f 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -7,22 +7,22 @@ module.exports = function(grunt) { compile: { files: { "spec/build/specs.js": ["spec/*.coffee"], - "build/ng-quick-date.js": ["src/*.coffee"] + "dist/ng-quick-date.js": ["src/*.coffee"] } } }, uglify: { my_target: { files: { - "build/ng-quick-date.min.js": "build/ng-quick-date.js" + "dist/ng-quick-date.min.js": "dist/ng-quick-date.js" } } }, stylus: { compile: { files: { - "build/ng-quick-date.css": ["src/ng-quick-date.styl"], - "build/ng-quick-date-default-theme.css": ["src/ng-quick-date-default-theme.styl"] + "dist/ng-quick-date.css": ["src/ng-quick-date.styl"], + "dist/ng-quick-date-default-theme.css": ["src/ng-quick-date-default-theme.styl"] } } }, diff --git a/README.md b/README.md index 4ef86a9..daae964 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,23 @@ ngQuickDate is an [Angular.js](http://angularjs.org/) Date/Time picker directive ## Download -* [Version 1.0.0-alpha.1](https://github.com/adamalbrecht/ngQuickDate/releases/download/v1.0.0-alpha.1/ng-quick-date.zip) - Compatible with Angular 1.0.x +* [Version 1.0.0-alpha.2](https://github.com/adamalbrecht/ngQuickDate/releases/download/v1.0.0-alpha.2/ng-quick-date.zip) - Compatible with Angular 1.0.x * Version 1.2 - Coming soon. Compatible with Angular 1.2.x +You can also install the package using [Bower](http://bower.io). + +```sh +bower install ngQuickDate +``` + +Or add it to your bower.json file: + +```javascript +dependencies: { + "ngQuickDate": "~1.0.0" +} +``` + *No dependencies (besides Angular) are required, but its date parsing capabilities can be improved by 3rd party libraries. See [Smarter Date/Time Parsing](#smarter-datetime-parsing) for more info. And it's styling can be improved by using a font icon library like [Font Awesome](http://fontawesome.io/). See the [Styling](#styling) and [Configuration](#configuration-options) sections.* ## Demo diff --git a/bower.json b/bower.json index a20bbbe..de2436a 100644 --- a/bower.json +++ b/bower.json @@ -1,11 +1,17 @@ { "name": "ngQuickDate", - "version": "1.0.0-alpha", - "main": "build/src/ng-quick-date.js", + "version": "1.0.0-alpha.2", + "main": ["dist/ng-quick-date.js", "dist/ng-quick-date.css", "dist/ng-quick-date-default-theme.css"], "ignore": [ - ".jshintrc", - "**/*.txt", - "demo" + "bower_components", + "node_modules", + "spec", + "src", + "vendor", + "Gruntfile.js", + "karma.conf.coffee", + "package.json", + "screenshot.png" ], "dependencies": {}, "devDependencies": { diff --git a/dist/ng-quick-date-default-theme.css b/dist/ng-quick-date-default-theme.css new file mode 100644 index 0000000..ae45b51 --- /dev/null +++ b/dist/ng-quick-date-default-theme.css @@ -0,0 +1,20 @@ +.quickdate{display:inline-block;vertical-align:bottom;font-size:15px;font-family:'Helvetica Neue',Helvetica,Ariel,sans-serif;} +.quickdate input,.quickdate select{font-size:13px} +.quickdate-button{background:#fff;color:#333;border:solid 1px #ccc;box-shadow:outset 0 1px 1px rgba(0,0,0,0.075);border-radius:4px;padding:4px 8px;display:inline-block;text-decoration:none;} +.quickdate-button:hover{text-decoration:underline;} +.quickdate-button:hover i{text-decoration:none} +.quickdate-button i{padding-right:4px} +.quickdate-popup{color:#333;font-size:15px;background-color:#fafafa;border:solid 1px #ddd;border-radius:3px;-webkit-box-shadow:0 10px 30px rgba(25,25,25,0.92);-moz-box-shadow:0 10px 30px rgba(25,25,25,0.92);box-shadow:0 10px 30px rgba(25,25,25,0.92)} +.quickdate-action-link:visited,.quickdate-action-link:hover{color:#333} +.quickdate-next-month i{padding-left:10px} +.quickdate-prev-month i{padding-right:10px} +table.quickdate-calendar{border:solid 1px #ccc;background-color:#fff;} +table.quickdate-calendar th,table.quickdate-calendar td{border-right:1px solid #ccc;border-bottom:1px solid #ccc} +table.quickdate-calendar td:hover{background-color:#e6e6e6} +table.quickdate-calendar td.other-month{background-color:#dcdcdc;color:#707070;} +table.quickdate-calendar td.other-month:hover{background-color:#c8c8c8} +table.quickdate-calendar td.selected{background-color:#b0ccde;font-weight:bold} +table.quickdate-calendar td.is-today{color:#b58922;font-weight:bold} +.quickdate-popup-footer{margin:3px 1px 0} +.quickdate-clear{display:inline-block;padding:2px 4px;background-color:#fff;color:#333;border:solid 1px #ccc;box-shadow:outset 0 1px 1px rgba(0,0,0,0.075);border-radius:4px;text-decoration:none;} +.quickdate-clear:hover{background-color:#f2f2f2} diff --git a/dist/ng-quick-date.css b/dist/ng-quick-date.css new file mode 100644 index 0000000..dff98f8 --- /dev/null +++ b/dist/ng-quick-date.css @@ -0,0 +1,19 @@ +.quickdate{display:inline-block;position:relative} +.quickdate-button div,.quickdate-action-link div{display:inline} +.quickdate-popup{z-index:10;background-color:#fff;border:solid 1px #000;text-align:center;width:250px;display:none;position:absolute;padding:5px;} +.quickdate-popup.open{display:block} +.quickdate-close{position:absolute;top:5px;right:5px;color:#333;} +.quickdate-close:hover,.quickdate-close:visited{color:#333} +.quickdate-calendar-header{display:block;padding:2px 0;margin-bottom:5px;text-align:center} +.quickdate-month{display:inline-block} +a.quickdate-prev-month{float:left} +a.quickdate-next-month{float:right} +.quickdate-text-inputs{text-align:left;margin-bottom:5px} +.quickdate-input-wrapper{width:48%;display:inline-block} +input.quickdate-date-input,input.quickdate-time-input{width:100px;margin:0;height:auto;padding:2px 3px} +table.quickdate-calendar{border-collapse:collapse;border-spacing:0;width:100%;margin-top:5px;} +table.quickdate-calendar th,table.quickdate-calendar td{padding:5px} +table.quickdate-calendar td:hover{cursor:pointer} +.quickdate-popup-footer{text-align:right;display:block} +.quickdate-error{border:1px solid #dd3b30 !important} +.quickdate-error:focus{outline-color:#dd3b30 !important} diff --git a/dist/ng-quick-date.js b/dist/ng-quick-date.js new file mode 100644 index 0000000..8bf9905 --- /dev/null +++ b/dist/ng-quick-date.js @@ -0,0 +1,324 @@ +(function() { + var app; + + app = angular.module("ngQuickDate", []); + + app.provider("ngQuickDateDefaults", function() { + this.options = { + dateFormat: 'M/d/yyyy', + timeFormat: 'h:mm a', + labelFormat: null, + placeholder: 'Click to Set Date', + hoverText: null, + buttonIconHtml: null, + closeButtonHtml: 'X', + nextLinkHtml: 'Next', + prevLinkHtml: 'Prev', + disableTimepicker: false, + disableClearButton: false, + dayAbbreviations: ["Su", "M", "Tu", "W", "Th", "F", "Sa"], + parseDateFunction: function(str) { + var seconds; + seconds = Date.parse(str); + if (isNaN(seconds)) { + return null; + } else { + return new Date(seconds); + } + } + }; + this.$get = function() { + return this.options; + }; + return this.set = function(keyOrHash, value) { + var k, v, _results; + if (typeof keyOrHash === 'object') { + _results = []; + for (k in keyOrHash) { + v = keyOrHash[k]; + _results.push(this.options[k] = v); + } + return _results; + } else { + return this.options[keyOrHash] = value; + } + }; + }); + + app.directive("datepicker", [ + 'ngQuickDateDefaults', '$filter', function(ngQuickDateDefaults, $filter) { + return { + restrict: "E", + require: "ngModel", + scope: { + ngModel: "=", + onChange: "&" + }, + replace: true, + link: function(scope, element, attrs, ngModel) { + var dateToString, datepickerClicked, datesAreEqual, debug, getDaysInMonth, initialize, parseDateString, setCalendarDateFromModel, setCalendarRows, setConfigOptions, setInputDateFromModel; + debug = attrs.debug && attrs.debug.length; + initialize = function() { + scope.toggleCalendar(false); + scope.weeks = []; + scope.inputDate = null; + if (typeof scope.ngModel === 'string') { + scope.ngModel = parseDateString(scope.ngModel); + } + setConfigOptions(); + setInputDateFromModel(); + return setCalendarDateFromModel(); + }; + setConfigOptions = function() { + var key, value; + for (key in ngQuickDateDefaults) { + value = ngQuickDateDefaults[key]; + if (!key.match(/html/) && attrs[key] && attrs[key].length) { + scope[key] = attrs[key]; + } else { + scope[key] = ngQuickDateDefaults[key]; + } + } + if (!scope.labelFormat) { + scope.labelFormat = scope.dateFormat; + if (!scope.disableTimepicker) { + scope.labelFormat += " " + scope.timeFormat; + } + } + if (attrs.iconClass && attrs.iconClass.length) { + return scope.buttonIconHtml = ""; + } + }; + datepickerClicked = false; + window.document.addEventListener('click', function(event) { + if (!datepickerClicked) { + scope.toggleCalendar(false); + scope.$apply(); + } + return datepickerClicked = false; + }); + angular.element(element[0])[0].addEventListener('click', function(event) { + return datepickerClicked = true; + }); + setInputDateFromModel = function() { + if (scope.ngModel) { + scope.inputDate = $filter('date')(scope.ngModel, ngQuickDateDefaults.dateFormat); + return scope.inputTime = $filter('date')(scope.ngModel, ngQuickDateDefaults.timeFormat); + } else { + scope.inputDate = null; + return scope.inputTime = null; + } + }; + setCalendarDateFromModel = function() { + var d; + d = scope.ngModel ? new Date(scope.ngModel) : new Date(); + if (d.toString() === "Invalid Date") { + d = new Date(); + } + d.setDate(1); + return scope.calendarDate = new Date(d); + }; + setCalendarRows = function() { + var curDate, d, day, daysInMonth, numRows, offset, row, selected, today, weeks, _i, _j, _ref; + offset = scope.calendarDate.getDay(); + daysInMonth = getDaysInMonth(scope.calendarDate.getFullYear(), scope.calendarDate.getMonth()); + numRows = Math.ceil((offset + daysInMonth) / 7); + weeks = []; + curDate = new Date(scope.calendarDate); + curDate.setDate(curDate.getDate() + (offset * -1)); + for (row = _i = 0, _ref = numRows - 1; 0 <= _ref ? _i <= _ref : _i >= _ref; row = 0 <= _ref ? ++_i : --_i) { + weeks.push([]); + for (day = _j = 0; _j <= 6; day = ++_j) { + d = new Date(curDate); + selected = scope.ngModel && d && datesAreEqual(d, scope.ngModel); + today = datesAreEqual(d, new Date()); + weeks[row].push({ + date: d, + selected: selected, + other: d.getMonth() !== scope.calendarDate.getMonth(), + today: today + }); + curDate.setDate(curDate.getDate() + 1); + } + } + return scope.weeks = weeks; + }; + dateToString = function(date, format) { + return $filter('date')(date, format); + }; + parseDateString = ngQuickDateDefaults.parseDateFunction; + datesAreEqual = function(d1, d2, compareTimes) { + if (compareTimes == null) { + compareTimes = false; + } + if (compareTimes) { + return (d1 - d2) === 0; + } else { + return d1 && d2 && (d1.getYear() === d2.getYear()) && (d1.getMonth() === d2.getMonth()) && (d1.getDate() === d2.getDate()); + } + }; + getDaysInMonth = function(year, month) { + return [31, ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0 ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }; + scope.$watch('ngModel', function(newVal, oldVal) { + if (newVal !== oldVal) { + setInputDateFromModel(); + return setCalendarDateFromModel(); + } + }); + scope.$watch('calendarDate', function(newVal, oldVal) { + if (newVal !== oldVal) { + return setCalendarRows(); + } + }); + scope.$watch('calendarShown', function(newVal, oldVal) { + var dateInput; + dateInput = angular.element(element[0].querySelector(".quickdate-date-input"))[0]; + return dateInput.select(); + }); + scope.mainButtonStr = function() { + if (scope.ngModel) { + return $filter('date')(scope.ngModel, scope.labelFormat); + } else { + return scope.placeholder; + } + }; + scope.toggleCalendar = function(show) { + if (isFinite(show)) { + return scope.calendarShown = show; + } else { + return scope.calendarShown = !scope.calendarShown; + } + }; + scope.setDate = function(date, closeCalendar) { + var changed; + if (closeCalendar == null) { + closeCalendar = true; + } + changed = (!scope.ngModel && date) || (scope.ngModel && !date) || (date.getTime() !== scope.ngModel.getTime()); + scope.ngModel = date; + scope.toggleCalendar(false); + if (changed && scope.onChange) { + return scope.onChange(); + } + }; + scope.setDateFromInput = function(closeCalendar) { + var err, tmpDate, tmpDateAndTime, tmpTime; + if (closeCalendar == null) { + closeCalendar = false; + } + try { + tmpDate = parseDateString(scope.inputDate); + if (!tmpDate) { + throw 'Invalid Date'; + } + if (!scope.disableTimepicker && scope.inputTime && scope.inputTime.length && tmpDate) { + tmpTime = scope.disableTimepicker ? '00:00:00' : scope.inputTime; + tmpDateAndTime = parseDateString("" + scope.inputDate + " " + tmpTime); + if (!tmpDateAndTime) { + throw 'Invalid Time'; + } + scope.setDate(tmpDateAndTime, false); + } else { + scope.setDate(tmpDate, false); + } + if (closeCalendar) { + scope.toggleCalendar(false); + } + scope.inputDateErr = false; + return scope.inputTimeErr = false; + } catch (_error) { + err = _error; + if (err === 'Invalid Date') { + return scope.inputDateErr = true; + } else if (err === 'Invalid Time') { + return scope.inputTimeErr = true; + } + } + }; + scope.onDateInputTab = function(param) { + if (scope.disableTimepicker) { + scope.toggleCalendar(false); + } + return true; + }; + scope.onTimeInputTab = function(param) { + scope.toggleCalendar(false); + return true; + }; + scope.nextMonth = function() { + return scope.calendarDate = new Date(new Date(scope.calendarDate).setMonth(scope.calendarDate.getMonth() + 1)); + }; + scope.prevMonth = function() { + return scope.calendarDate = new Date(new Date(scope.calendarDate).setMonth(scope.calendarDate.getMonth() - 1)); + }; + scope.clear = function() { + scope.ngModel = null; + return scope.toggleCalendar(false); + }; + initialize(); + setCalendarRows(); + if (debug) { + return console.log("quick date scope:", scope); + } + }, + template: "