angular-strap-rails 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +17 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +45 -0
  7. data/Rakefile +1 -0
  8. data/angular-strap-rails.gemspec +15 -0
  9. data/lib/angular-strap-rails.rb +8 -0
  10. data/lib/angular-strap-rails/version.rb +5 -0
  11. data/vendor/.DS_Store +0 -0
  12. data/vendor/assets/.DS_Store +0 -0
  13. data/vendor/assets/javascripts/.DS_Store +0 -0
  14. data/vendor/assets/javascripts/angular-strap.coffee +0 -0
  15. data/vendor/assets/javascripts/angular-strap/.DS_Store +0 -0
  16. data/vendor/assets/javascripts/angular-strap/datepicker.coffee +11 -0
  17. data/vendor/assets/javascripts/angular-strap/modal.coffee +9 -0
  18. data/vendor/assets/javascripts/dist/angular-strap.js +3682 -0
  19. data/vendor/assets/javascripts/dist/angular-strap.tpl.js +100 -0
  20. data/vendor/assets/javascripts/dist/modules/affix.js +191 -0
  21. data/vendor/assets/javascripts/dist/modules/alert.js +114 -0
  22. data/vendor/assets/javascripts/dist/modules/alert.tpl.js +14 -0
  23. data/vendor/assets/javascripts/dist/modules/aside.js +96 -0
  24. data/vendor/assets/javascripts/dist/modules/aside.tpl.js +14 -0
  25. data/vendor/assets/javascripts/dist/modules/button.js +141 -0
  26. data/vendor/assets/javascripts/dist/modules/date-parser.js +150 -0
  27. data/vendor/assets/javascripts/dist/modules/datepicker.js +583 -0
  28. data/vendor/assets/javascripts/dist/modules/datepicker.tpl.js +14 -0
  29. data/vendor/assets/javascripts/dist/modules/debounce.js +60 -0
  30. data/vendor/assets/javascripts/dist/modules/dimensions.js +142 -0
  31. data/vendor/assets/javascripts/dist/modules/dropdown.js +124 -0
  32. data/vendor/assets/javascripts/dist/modules/dropdown.tpl.js +14 -0
  33. data/vendor/assets/javascripts/dist/modules/modal.js +282 -0
  34. data/vendor/assets/javascripts/dist/modules/modal.tpl.js +14 -0
  35. data/vendor/assets/javascripts/dist/modules/navbar.js +55 -0
  36. data/vendor/assets/javascripts/dist/modules/parse-options.js +51 -0
  37. data/vendor/assets/javascripts/dist/modules/popover.js +100 -0
  38. data/vendor/assets/javascripts/dist/modules/popover.tpl.js +14 -0
  39. data/vendor/assets/javascripts/dist/modules/raf.js +45 -0
  40. data/vendor/assets/javascripts/dist/modules/scrollspy.js +229 -0
  41. data/vendor/assets/javascripts/dist/modules/select.js +281 -0
  42. data/vendor/assets/javascripts/dist/modules/select.tpl.js +14 -0
  43. data/vendor/assets/javascripts/dist/modules/tab.js +69 -0
  44. data/vendor/assets/javascripts/dist/modules/tab.tpl.js +14 -0
  45. data/vendor/assets/javascripts/dist/modules/timepicker.js +430 -0
  46. data/vendor/assets/javascripts/dist/modules/timepicker.tpl.js +14 -0
  47. data/vendor/assets/javascripts/dist/modules/tooltip.js +405 -0
  48. data/vendor/assets/javascripts/dist/modules/tooltip.tpl.js +14 -0
  49. data/vendor/assets/javascripts/dist/modules/typeahead.js +225 -0
  50. data/vendor/assets/javascripts/dist/modules/typeahead.tpl.js +14 -0
  51. data/vendor/assets/stylesheets/angular-strap.css +564 -0
  52. metadata +94 -0
@@ -0,0 +1,14 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
4
+ * @link http://mgcrea.github.io/angular-strap
5
+ * @author Olivier Louvignes (olivier@mg-crea.com)
6
+ * @license MIT License, http://www.opensource.org/licenses/MIT
7
+ */
8
+ 'use strict';
9
+ angular.module('mgcrea.ngStrap.aside').run([
10
+ '$templateCache',
11
+ function ($templateCache) {
12
+ $templateCache.put('aside/aside.tpl.html', '<div class="aside" tabindex="-1" role="dialog"><div class="aside-dialog"><div class="aside-content"><div class="aside-header" ng-show="title"><button type="button" class="close" ng-click="$hide()">&times;</button><h4 class="aside-title" ng-bind="title"></h4></div><div class="aside-body" ng-bind="content"></div><div class="aside-footer"><button type="button" class="btn btn-default" ng-click="$hide()">Close</button></div></div></div></div>');
13
+ }
14
+ ]);
@@ -0,0 +1,141 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
4
+ * @link http://mgcrea.github.io/angular-strap
5
+ * @author Olivier Louvignes (olivier@mg-crea.com)
6
+ * @license MIT License, http://www.opensource.org/licenses/MIT
7
+ */
8
+ 'use strict';
9
+ angular.module('mgcrea.ngStrap.button', []).provider('$button', function () {
10
+ var defaults = this.defaults = {
11
+ activeClass: 'active',
12
+ toggleEvent: 'click'
13
+ };
14
+ this.$get = function () {
15
+ return { defaults: defaults };
16
+ };
17
+ }).directive('bsCheckboxGroup', function () {
18
+ return {
19
+ restrict: 'A',
20
+ require: 'ngModel',
21
+ compile: function postLink(element, attr) {
22
+ element.attr('data-toggle', 'buttons');
23
+ element.removeAttr('ng-model');
24
+ var children = element[0].querySelectorAll('input[type="checkbox"]');
25
+ angular.forEach(children, function (child) {
26
+ var childEl = angular.element(child);
27
+ childEl.attr('bs-checkbox', '');
28
+ childEl.attr('ng-model', attr.ngModel + '.' + childEl.attr('value'));
29
+ });
30
+ }
31
+ };
32
+ }).directive('bsCheckbox', [
33
+ '$button',
34
+ '$$rAF',
35
+ function ($button, $$rAF) {
36
+ var defaults = $button.defaults;
37
+ var constantValueRegExp = /^(true|false|\d+)$/;
38
+ return {
39
+ restrict: 'A',
40
+ require: 'ngModel',
41
+ link: function postLink(scope, element, attr, controller) {
42
+ var options = defaults;
43
+ // Support label > input[type="checkbox"]
44
+ var isInput = element[0].nodeName === 'INPUT';
45
+ var activeElement = isInput ? element.parent() : element;
46
+ var trueValue = angular.isDefined(attr.trueValue) ? attr.trueValue : true;
47
+ if (constantValueRegExp.test(attr.trueValue)) {
48
+ trueValue = scope.$eval(attr.trueValue);
49
+ }
50
+ var falseValue = angular.isDefined(attr.falseValue) ? attr.falseValue : false;
51
+ if (constantValueRegExp.test(attr.falseValue)) {
52
+ falseValue = scope.$eval(attr.falseValue);
53
+ }
54
+ // Parse exotic values
55
+ var hasExoticValues = typeof trueValue !== 'boolean' || typeof falseValue !== 'boolean';
56
+ if (hasExoticValues) {
57
+ controller.$parsers.push(function (viewValue) {
58
+ // console.warn('$parser', element.attr('ng-model'), 'viewValue', viewValue);
59
+ return viewValue ? trueValue : falseValue;
60
+ });
61
+ // Fix rendering for exotic values
62
+ scope.$watch(attr.ngModel, function (newValue, oldValue) {
63
+ controller.$render();
64
+ });
65
+ }
66
+ // model -> view
67
+ controller.$render = function () {
68
+ // console.warn('$render', element.attr('ng-model'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
69
+ var isActive = angular.equals(controller.$modelValue, trueValue);
70
+ $$rAF(function () {
71
+ if (isInput)
72
+ element[0].checked = isActive;
73
+ activeElement.toggleClass(options.activeClass, isActive);
74
+ });
75
+ };
76
+ // view -> model
77
+ element.bind(options.toggleEvent, function () {
78
+ scope.$apply(function () {
79
+ // console.warn('!click', element.attr('ng-model'), 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue, 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue);
80
+ if (!isInput) {
81
+ controller.$setViewValue(!activeElement.hasClass('active'));
82
+ }
83
+ if (!hasExoticValues) {
84
+ controller.$render();
85
+ }
86
+ });
87
+ });
88
+ }
89
+ };
90
+ }
91
+ ]).directive('bsRadioGroup', function () {
92
+ return {
93
+ restrict: 'A',
94
+ require: 'ngModel',
95
+ compile: function postLink(element, attr) {
96
+ element.attr('data-toggle', 'buttons');
97
+ element.removeAttr('ng-model');
98
+ var children = element[0].querySelectorAll('input[type="radio"]');
99
+ angular.forEach(children, function (child) {
100
+ angular.element(child).attr('bs-radio', '');
101
+ angular.element(child).attr('ng-model', attr.ngModel);
102
+ });
103
+ }
104
+ };
105
+ }).directive('bsRadio', [
106
+ '$button',
107
+ '$$rAF',
108
+ function ($button, $$rAF) {
109
+ var defaults = $button.defaults;
110
+ var constantValueRegExp = /^(true|false|\d+)$/;
111
+ return {
112
+ restrict: 'A',
113
+ require: 'ngModel',
114
+ link: function postLink(scope, element, attr, controller) {
115
+ var options = defaults;
116
+ // Support `label > input[type="radio"]` markup
117
+ var isInput = element[0].nodeName === 'INPUT';
118
+ var activeElement = isInput ? element.parent() : element;
119
+ var value = constantValueRegExp.test(attr.value) ? scope.$eval(attr.value) : attr.value;
120
+ // model -> view
121
+ controller.$render = function () {
122
+ // console.warn('$render', element.attr('value'), 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue, 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue);
123
+ var isActive = angular.equals(controller.$modelValue, value);
124
+ $$rAF(function () {
125
+ if (isInput)
126
+ element[0].checked = isActive;
127
+ activeElement.toggleClass(options.activeClass, isActive);
128
+ });
129
+ };
130
+ // view -> model
131
+ element.bind(options.toggleEvent, function () {
132
+ scope.$apply(function () {
133
+ // console.warn('!click', element.attr('value'), 'controller.$viewValue', typeof controller.$viewValue, controller.$viewValue, 'controller.$modelValue', typeof controller.$modelValue, controller.$modelValue);
134
+ controller.$setViewValue(value);
135
+ controller.$render();
136
+ });
137
+ });
138
+ }
139
+ };
140
+ }
141
+ ]);
@@ -0,0 +1,150 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
4
+ * @link http://mgcrea.github.io/angular-strap
5
+ * @author Olivier Louvignes (olivier@mg-crea.com)
6
+ * @license MIT License, http://www.opensource.org/licenses/MIT
7
+ */
8
+ 'use strict';
9
+ angular.module('mgcrea.ngStrap.helpers.dateParser', []).provider('$dateParser', [
10
+ '$localeProvider',
11
+ function ($localeProvider) {
12
+ var proto = Date.prototype;
13
+ function isNumeric(n) {
14
+ return !isNaN(parseFloat(n)) && isFinite(n);
15
+ }
16
+ var defaults = this.defaults = {
17
+ format: 'shortDate',
18
+ strict: false
19
+ };
20
+ this.$get = [
21
+ '$locale',
22
+ function ($locale) {
23
+ var DateParserFactory = function (config) {
24
+ var options = angular.extend({}, defaults, config);
25
+ var $dateParser = {};
26
+ var regExpMap = {
27
+ 'sss': '[0-9]{3}',
28
+ 'ss': '[0-5][0-9]',
29
+ 's': options.strict ? '[1-5]?[0-9]' : '[0-9]|[0-5][0-9]',
30
+ 'mm': '[0-5][0-9]',
31
+ 'm': options.strict ? '[1-5]?[0-9]' : '[0-9]|[0-5][0-9]',
32
+ 'HH': '[01][0-9]|2[0-3]',
33
+ 'H': options.strict ? '1?[0-9]|2[0-3]' : '[01]?[0-9]|2[0-3]',
34
+ 'hh': '[0][1-9]|[1][012]',
35
+ 'h': options.strict ? '[1-9]|1[012]' : '0?[1-9]|1[012]',
36
+ 'a': 'AM|PM',
37
+ 'EEEE': $locale.DATETIME_FORMATS.DAY.join('|'),
38
+ 'EEE': $locale.DATETIME_FORMATS.SHORTDAY.join('|'),
39
+ 'dd': '0[1-9]|[12][0-9]|3[01]',
40
+ 'd': options.strict ? '[1-9]|[1-2][0-9]|3[01]' : '0?[1-9]|[1-2][0-9]|3[01]',
41
+ 'MMMM': $locale.DATETIME_FORMATS.MONTH.join('|'),
42
+ 'MMM': $locale.DATETIME_FORMATS.SHORTMONTH.join('|'),
43
+ 'MM': '0[1-9]|1[012]',
44
+ 'M': options.strict ? '[1-9]|1[012]' : '0?[1-9]|1[012]',
45
+ 'yyyy': '[1]{1}[0-9]{3}|[2]{1}[0-9]{3}',
46
+ 'yy': '[0-9]{2}',
47
+ 'y': options.strict ? '-?(0|[1-9][0-9]{0,3})' : '-?0*[0-9]{1,4}'
48
+ };
49
+ var setFnMap = {
50
+ 'sss': proto.setMilliseconds,
51
+ 'ss': proto.setSeconds,
52
+ 's': proto.setSeconds,
53
+ 'mm': proto.setMinutes,
54
+ 'm': proto.setMinutes,
55
+ 'HH': proto.setHours,
56
+ 'H': proto.setHours,
57
+ 'hh': proto.setHours,
58
+ 'h': proto.setHours,
59
+ 'dd': proto.setDate,
60
+ 'd': proto.setDate,
61
+ 'a': function (value) {
62
+ var hours = this.getHours();
63
+ return this.setHours(value.match(/pm/i) ? hours + 12 : hours);
64
+ },
65
+ 'MMMM': function (value) {
66
+ return this.setMonth($locale.DATETIME_FORMATS.MONTH.indexOf(value));
67
+ },
68
+ 'MMM': function (value) {
69
+ return this.setMonth($locale.DATETIME_FORMATS.SHORTMONTH.indexOf(value));
70
+ },
71
+ 'MM': function (value) {
72
+ return this.setMonth(1 * value - 1);
73
+ },
74
+ 'M': function (value) {
75
+ return this.setMonth(1 * value - 1);
76
+ },
77
+ 'yyyy': proto.setFullYear,
78
+ 'yy': function (value) {
79
+ return this.setFullYear(2000 + 1 * value);
80
+ },
81
+ 'y': proto.setFullYear
82
+ };
83
+ var regex, setMap;
84
+ $dateParser.init = function () {
85
+ $dateParser.$format = $locale.DATETIME_FORMATS[options.format] || options.format;
86
+ regex = regExpForFormat($dateParser.$format);
87
+ setMap = setMapForFormat($dateParser.$format);
88
+ };
89
+ $dateParser.isValid = function (date) {
90
+ if (angular.isDate(date))
91
+ return !isNaN(date.getTime());
92
+ return regex.test(date);
93
+ };
94
+ $dateParser.parse = function (value, baseDate) {
95
+ if (angular.isDate(value))
96
+ return value;
97
+ var matches = regex.exec(value);
98
+ if (!matches)
99
+ return false;
100
+ var date = baseDate || new Date(0);
101
+ for (var i = 0; i < matches.length - 1; i++) {
102
+ setMap[i] && setMap[i].call(date, matches[i + 1]);
103
+ }
104
+ return date;
105
+ };
106
+ // Private functions
107
+ function setMapForFormat(format) {
108
+ var keys = Object.keys(setFnMap), i;
109
+ var map = [], sortedMap = [];
110
+ // Map to setFn
111
+ var clonedFormat = format;
112
+ for (i = 0; i < keys.length; i++) {
113
+ if (format.split(keys[i]).length > 1) {
114
+ var index = clonedFormat.search(keys[i]);
115
+ format = format.split(keys[i]).join('');
116
+ if (setFnMap[keys[i]])
117
+ map[index] = setFnMap[keys[i]];
118
+ }
119
+ }
120
+ // Sort result map
121
+ angular.forEach(map, function (v) {
122
+ sortedMap.push(v);
123
+ });
124
+ return sortedMap;
125
+ }
126
+ function escapeReservedSymbols(text) {
127
+ return text.replace(/\//g, '[\\/]').replace('/-/g', '[-]').replace(/\./g, '[.]').replace(/\\s/g, '[\\s]');
128
+ }
129
+ function regExpForFormat(format) {
130
+ var keys = Object.keys(regExpMap), i;
131
+ var re = format;
132
+ // Abstract replaces to avoid collisions
133
+ for (i = 0; i < keys.length; i++) {
134
+ re = re.split(keys[i]).join('${' + i + '}');
135
+ }
136
+ // Replace abstracted values
137
+ for (i = 0; i < keys.length; i++) {
138
+ re = re.split('${' + i + '}').join('(' + regExpMap[keys[i]] + ')');
139
+ }
140
+ format = escapeReservedSymbols(format);
141
+ return new RegExp('^' + re + '$', ['i']);
142
+ }
143
+ $dateParser.init();
144
+ return $dateParser;
145
+ };
146
+ return DateParserFactory;
147
+ }
148
+ ];
149
+ }
150
+ ]);
@@ -0,0 +1,583 @@
1
+ /**
2
+ * angular-strap
3
+ * @version v2.0.1 - 2014-04-10
4
+ * @link http://mgcrea.github.io/angular-strap
5
+ * @author Olivier Louvignes (olivier@mg-crea.com)
6
+ * @license MIT License, http://www.opensource.org/licenses/MIT
7
+ */
8
+ 'use strict';
9
+ angular.module('mgcrea.ngStrap.datepicker', [
10
+ 'mgcrea.ngStrap.helpers.dateParser',
11
+ 'mgcrea.ngStrap.tooltip'
12
+ ]).provider('$datepicker', function () {
13
+ var defaults = this.defaults = {
14
+ animation: 'am-fade',
15
+ prefixClass: 'datepicker',
16
+ placement: 'bottom-left',
17
+ template: 'datepicker/datepicker.tpl.html',
18
+ trigger: 'focus',
19
+ container: false,
20
+ keyboard: true,
21
+ html: false,
22
+ delay: 0,
23
+ useNative: false,
24
+ dateType: 'date',
25
+ dateFormat: 'shortDate',
26
+ strictFormat: false,
27
+ autoclose: false,
28
+ minDate: -Infinity,
29
+ maxDate: +Infinity,
30
+ startView: 0,
31
+ minView: 0,
32
+ startWeek: 0
33
+ };
34
+ this.$get = [
35
+ '$window',
36
+ '$document',
37
+ '$rootScope',
38
+ '$sce',
39
+ '$locale',
40
+ 'dateFilter',
41
+ 'datepickerViews',
42
+ '$tooltip',
43
+ function ($window, $document, $rootScope, $sce, $locale, dateFilter, datepickerViews, $tooltip) {
44
+ var bodyEl = angular.element($window.document.body);
45
+ var isTouch = 'createTouch' in $window.document;
46
+ var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
47
+ if (!defaults.lang)
48
+ defaults.lang = $locale.id;
49
+ function DatepickerFactory(element, controller, config) {
50
+ var $datepicker = $tooltip(element, angular.extend({}, defaults, config));
51
+ var parentScope = config.scope;
52
+ var options = $datepicker.$options;
53
+ var scope = $datepicker.$scope;
54
+ if (options.startView)
55
+ options.startView -= options.minView;
56
+ // View vars
57
+ var pickerViews = datepickerViews($datepicker);
58
+ $datepicker.$views = pickerViews.views;
59
+ var viewDate = pickerViews.viewDate;
60
+ scope.$mode = options.startView;
61
+ var $picker = $datepicker.$views[scope.$mode];
62
+ // Scope methods
63
+ scope.$select = function (date) {
64
+ $datepicker.select(date);
65
+ };
66
+ scope.$selectPane = function (value) {
67
+ $datepicker.$selectPane(value);
68
+ };
69
+ scope.$toggleMode = function () {
70
+ $datepicker.setMode((scope.$mode + 1) % $datepicker.$views.length);
71
+ };
72
+ // Public methods
73
+ $datepicker.update = function (date) {
74
+ // console.warn('$datepicker.update() newValue=%o', date);
75
+ if (angular.isDate(date) && !isNaN(date.getTime())) {
76
+ $datepicker.$date = date;
77
+ $picker.update.call($picker, date);
78
+ }
79
+ // Build only if pristine
80
+ $datepicker.$build(true);
81
+ };
82
+ $datepicker.select = function (date, keep) {
83
+ // console.warn('$datepicker.select', date, scope.$mode);
84
+ if (!angular.isDate(controller.$dateValue))
85
+ controller.$dateValue = new Date(date);
86
+ controller.$dateValue.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
87
+ if (!scope.$mode || keep) {
88
+ controller.$setViewValue(controller.$dateValue);
89
+ controller.$render();
90
+ if (options.autoclose && !keep) {
91
+ $datepicker.hide(true);
92
+ }
93
+ } else {
94
+ angular.extend(viewDate, {
95
+ year: date.getFullYear(),
96
+ month: date.getMonth(),
97
+ date: date.getDate()
98
+ });
99
+ $datepicker.setMode(scope.$mode - 1);
100
+ $datepicker.$build();
101
+ }
102
+ };
103
+ $datepicker.setMode = function (mode) {
104
+ // console.warn('$datepicker.setMode', mode);
105
+ scope.$mode = mode;
106
+ $picker = $datepicker.$views[scope.$mode];
107
+ $datepicker.$build();
108
+ };
109
+ // Protected methods
110
+ $datepicker.$build = function (pristine) {
111
+ // console.warn('$datepicker.$build() viewDate=%o', viewDate);
112
+ if (pristine === true && $picker.built)
113
+ return;
114
+ if (pristine === false && !$picker.built)
115
+ return;
116
+ $picker.build.call($picker);
117
+ };
118
+ $datepicker.$updateSelected = function () {
119
+ for (var i = 0, l = scope.rows.length; i < l; i++) {
120
+ angular.forEach(scope.rows[i], updateSelected);
121
+ }
122
+ };
123
+ $datepicker.$isSelected = function (date) {
124
+ return $picker.isSelected(date);
125
+ };
126
+ $datepicker.$selectPane = function (value) {
127
+ var steps = $picker.steps;
128
+ var targetDate = new Date(Date.UTC(viewDate.year + (steps.year || 0) * value, viewDate.month + (steps.month || 0) * value, viewDate.date + (steps.day || 0) * value));
129
+ angular.extend(viewDate, {
130
+ year: targetDate.getUTCFullYear(),
131
+ month: targetDate.getUTCMonth(),
132
+ date: targetDate.getUTCDate()
133
+ });
134
+ $datepicker.$build();
135
+ };
136
+ $datepicker.$onMouseDown = function (evt) {
137
+ // Prevent blur on mousedown on .dropdown-menu
138
+ evt.preventDefault();
139
+ evt.stopPropagation();
140
+ // Emulate click for mobile devices
141
+ if (isTouch) {
142
+ var targetEl = angular.element(evt.target);
143
+ if (targetEl[0].nodeName.toLowerCase() !== 'button') {
144
+ targetEl = targetEl.parent();
145
+ }
146
+ targetEl.triggerHandler('click');
147
+ }
148
+ };
149
+ $datepicker.$onKeyDown = function (evt) {
150
+ if (!/(38|37|39|40|13)/.test(evt.keyCode) || evt.shiftKey || evt.altKey)
151
+ return;
152
+ evt.preventDefault();
153
+ evt.stopPropagation();
154
+ if (evt.keyCode === 13) {
155
+ if (!scope.$mode) {
156
+ return $datepicker.hide(true);
157
+ } else {
158
+ return scope.$apply(function () {
159
+ $datepicker.setMode(scope.$mode - 1);
160
+ });
161
+ }
162
+ }
163
+ // Navigate with keyboard
164
+ $picker.onKeyDown(evt);
165
+ parentScope.$digest();
166
+ };
167
+ // Private
168
+ function updateSelected(el) {
169
+ el.selected = $datepicker.$isSelected(el.date);
170
+ }
171
+ function focusElement() {
172
+ element[0].focus();
173
+ }
174
+ // Overrides
175
+ var _init = $datepicker.init;
176
+ $datepicker.init = function () {
177
+ if (isNative && options.useNative) {
178
+ element.prop('type', 'date');
179
+ element.css('-webkit-appearance', 'textfield');
180
+ return;
181
+ } else if (isTouch) {
182
+ element.prop('type', 'text');
183
+ element.attr('readonly', 'true');
184
+ element.on('click', focusElement);
185
+ }
186
+ _init();
187
+ };
188
+ var _destroy = $datepicker.destroy;
189
+ $datepicker.destroy = function () {
190
+ if (isNative && options.useNative) {
191
+ element.off('click', focusElement);
192
+ }
193
+ _destroy();
194
+ };
195
+ var _show = $datepicker.show;
196
+ $datepicker.show = function () {
197
+ _show();
198
+ setTimeout(function () {
199
+ $datepicker.$element.on(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
200
+ if (options.keyboard) {
201
+ element.on('keydown', $datepicker.$onKeyDown);
202
+ }
203
+ });
204
+ };
205
+ var _hide = $datepicker.hide;
206
+ $datepicker.hide = function (blur) {
207
+ $datepicker.$element.off(isTouch ? 'touchstart' : 'mousedown', $datepicker.$onMouseDown);
208
+ if (options.keyboard) {
209
+ element.off('keydown', $datepicker.$onKeyDown);
210
+ }
211
+ _hide(blur);
212
+ };
213
+ return $datepicker;
214
+ }
215
+ DatepickerFactory.defaults = defaults;
216
+ return DatepickerFactory;
217
+ }
218
+ ];
219
+ }).directive('bsDatepicker', [
220
+ '$window',
221
+ '$parse',
222
+ '$q',
223
+ '$locale',
224
+ 'dateFilter',
225
+ '$datepicker',
226
+ '$dateParser',
227
+ '$timeout',
228
+ function ($window, $parse, $q, $locale, dateFilter, $datepicker, $dateParser, $timeout) {
229
+ var defaults = $datepicker.defaults;
230
+ var isNative = /(ip(a|o)d|iphone|android)/gi.test($window.navigator.userAgent);
231
+ var isNumeric = function (n) {
232
+ return !isNaN(parseFloat(n)) && isFinite(n);
233
+ };
234
+ return {
235
+ restrict: 'EAC',
236
+ require: 'ngModel',
237
+ link: function postLink(scope, element, attr, controller) {
238
+ // Directive options
239
+ var options = {
240
+ scope: scope,
241
+ controller: controller
242
+ };
243
+ angular.forEach([
244
+ 'placement',
245
+ 'container',
246
+ 'delay',
247
+ 'trigger',
248
+ 'keyboard',
249
+ 'html',
250
+ 'animation',
251
+ 'template',
252
+ 'autoclose',
253
+ 'dateType',
254
+ 'dateFormat',
255
+ 'strictFormat',
256
+ 'startWeek',
257
+ 'useNative',
258
+ 'lang',
259
+ 'startView',
260
+ 'minView'
261
+ ], function (key) {
262
+ if (angular.isDefined(attr[key]))
263
+ options[key] = attr[key];
264
+ });
265
+ // Initialize datepicker
266
+ if (isNative && options.useNative)
267
+ options.dateFormat = 'yyyy-MM-dd';
268
+ var datepicker = $datepicker(element, controller, options);
269
+ options = datepicker.$options;
270
+ // Observe attributes for changes
271
+ angular.forEach([
272
+ 'minDate',
273
+ 'maxDate'
274
+ ], function (key) {
275
+ // console.warn('attr.$observe(%s)', key, attr[key]);
276
+ angular.isDefined(attr[key]) && attr.$observe(key, function (newValue) {
277
+ // console.warn('attr.$observe(%s)=%o', key, newValue);
278
+ if (newValue === 'today') {
279
+ var today = new Date();
280
+ datepicker.$options[key] = +new Date(today.getFullYear(), today.getMonth(), today.getDate() + (key === 'maxDate' ? 1 : 0), 0, 0, 0, key === 'minDate' ? 0 : -1);
281
+ } else if (angular.isString(newValue) && newValue.match(/^".+"$/)) {
282
+ // Support {{ dateObj }}
283
+ datepicker.$options[key] = +new Date(newValue.substr(1, newValue.length - 2));
284
+ } else if (isNumeric(newValue)) {
285
+ datepicker.$options[key] = +new Date(parseInt(newValue, 10));
286
+ } else {
287
+ datepicker.$options[key] = +new Date(newValue);
288
+ }
289
+ // Build only if dirty
290
+ !isNaN(datepicker.$options[key]) && datepicker.$build(false);
291
+ });
292
+ });
293
+ // Watch model for changes
294
+ scope.$watch(attr.ngModel, function (newValue, oldValue) {
295
+ datepicker.update(controller.$dateValue);
296
+ }, true);
297
+ var dateParser = $dateParser({
298
+ format: options.dateFormat,
299
+ lang: options.lang,
300
+ strict: options.strictFormat
301
+ });
302
+ // viewValue -> $parsers -> modelValue
303
+ controller.$parsers.unshift(function (viewValue) {
304
+ // console.warn('$parser("%s"): viewValue=%o', element.attr('ng-model'), viewValue);
305
+ // Null values should correctly reset the model value & validity
306
+ if (!viewValue) {
307
+ controller.$setValidity('date', true);
308
+ return;
309
+ }
310
+ var parsedDate = dateParser.parse(viewValue, controller.$dateValue);
311
+ if (!parsedDate || isNaN(parsedDate.getTime())) {
312
+ controller.$setValidity('date', false);
313
+ return;
314
+ } else {
315
+ var isValid = (isNaN(datepicker.$options.minDate) || parsedDate.getTime() >= datepicker.$options.minDate) && (isNaN(datepicker.$options.maxDate) || parsedDate.getTime() <= datepicker.$options.maxDate);
316
+ controller.$setValidity('date', isValid);
317
+ // Only update the model when we have a valid date
318
+ if (isValid)
319
+ controller.$dateValue = parsedDate;
320
+ }
321
+ if (options.dateType === 'string') {
322
+ return dateFilter(viewValue, options.dateFormat);
323
+ } else if (options.dateType === 'number') {
324
+ return controller.$dateValue.getTime();
325
+ } else if (options.dateType === 'iso') {
326
+ return controller.$dateValue.toISOString();
327
+ } else {
328
+ return new Date(controller.$dateValue);
329
+ }
330
+ });
331
+ // modelValue -> $formatters -> viewValue
332
+ controller.$formatters.push(function (modelValue) {
333
+ // console.warn('$formatter("%s"): modelValue=%o (%o)', element.attr('ng-model'), modelValue, typeof modelValue);
334
+ var date;
335
+ if (angular.isUndefined(modelValue) || modelValue === null) {
336
+ date = NaN;
337
+ } else if (angular.isDate(modelValue)) {
338
+ date = modelValue;
339
+ } else if (options.dateType === 'string') {
340
+ date = dateParser.parse(modelValue);
341
+ } else {
342
+ date = new Date(modelValue);
343
+ }
344
+ // Setup default value?
345
+ // if(isNaN(date.getTime())) {
346
+ // var today = new Date();
347
+ // date = new Date(today.getFullYear(), today.getMonth(), today.getDate(), 0, 0, 0, 0);
348
+ // }
349
+ controller.$dateValue = date;
350
+ return controller.$dateValue;
351
+ });
352
+ // viewValue -> element
353
+ controller.$render = function () {
354
+ // console.warn('$render("%s"): viewValue=%o', element.attr('ng-model'), controller.$viewValue);
355
+ element.val(!controller.$dateValue || isNaN(controller.$dateValue.getTime()) ? '' : dateFilter(controller.$dateValue, options.dateFormat));
356
+ };
357
+ // Garbage collection
358
+ scope.$on('$destroy', function () {
359
+ datepicker.destroy();
360
+ options = null;
361
+ datepicker = null;
362
+ });
363
+ }
364
+ };
365
+ }
366
+ ]).provider('datepickerViews', function () {
367
+ var defaults = this.defaults = {
368
+ dayFormat: 'dd',
369
+ daySplit: 7
370
+ };
371
+ // Split array into smaller arrays
372
+ function split(arr, size) {
373
+ var arrays = [];
374
+ while (arr.length > 0) {
375
+ arrays.push(arr.splice(0, size));
376
+ }
377
+ return arrays;
378
+ }
379
+ // Modulus operator
380
+ function mod(n, m) {
381
+ return (n % m + m) % m;
382
+ }
383
+ this.$get = [
384
+ '$locale',
385
+ '$sce',
386
+ 'dateFilter',
387
+ function ($locale, $sce, dateFilter) {
388
+ return function (picker) {
389
+ var scope = picker.$scope;
390
+ var options = picker.$options;
391
+ var weekDaysMin = $locale.DATETIME_FORMATS.SHORTDAY;
392
+ var weekDaysLabels = weekDaysMin.slice(options.startWeek).concat(weekDaysMin.slice(0, options.startWeek));
393
+ var weekDaysLabelsHtml = $sce.trustAsHtml('<th class="dow text-center">' + weekDaysLabels.join('</th><th class="dow text-center">') + '</th>');
394
+ var startDate = picker.$date || new Date();
395
+ var viewDate = {
396
+ year: startDate.getFullYear(),
397
+ month: startDate.getMonth(),
398
+ date: startDate.getDate()
399
+ };
400
+ var timezoneOffset = startDate.getTimezoneOffset() * 60000;
401
+ var views = [
402
+ {
403
+ format: 'dd',
404
+ split: 7,
405
+ steps: { month: 1 },
406
+ update: function (date, force) {
407
+ if (!this.built || force || date.getFullYear() !== viewDate.year || date.getMonth() !== viewDate.month) {
408
+ angular.extend(viewDate, {
409
+ year: picker.$date.getFullYear(),
410
+ month: picker.$date.getMonth(),
411
+ date: picker.$date.getDate()
412
+ });
413
+ picker.$build();
414
+ } else if (date.getDate() !== viewDate.date) {
415
+ viewDate.date = picker.$date.getDate();
416
+ picker.$updateSelected();
417
+ }
418
+ },
419
+ build: function () {
420
+ var firstDayOfMonth = new Date(viewDate.year, viewDate.month, 1), firstDayOfMonthOffset = firstDayOfMonth.getTimezoneOffset();
421
+ var firstDate = new Date(+firstDayOfMonth - mod(firstDayOfMonth.getDay() - options.startWeek, 6) * 86400000), firstDateOffset = firstDate.getTimezoneOffset();
422
+ // Handle daylight time switch
423
+ if (firstDateOffset !== firstDayOfMonthOffset)
424
+ firstDate = new Date(+firstDate + (firstDateOffset - firstDayOfMonthOffset) * 60000);
425
+ var days = [], day;
426
+ for (var i = 0; i < 42; i++) {
427
+ // < 7 * 6
428
+ day = new Date(firstDate.getFullYear(), firstDate.getMonth(), firstDate.getDate() + i);
429
+ days.push({
430
+ date: day,
431
+ label: dateFilter(day, this.format),
432
+ selected: picker.$date && this.isSelected(day),
433
+ muted: day.getMonth() !== viewDate.month,
434
+ disabled: this.isDisabled(day)
435
+ });
436
+ }
437
+ scope.title = dateFilter(firstDayOfMonth, 'MMMM yyyy');
438
+ scope.labels = weekDaysLabelsHtml;
439
+ scope.rows = split(days, this.split);
440
+ this.built = true;
441
+ },
442
+ isSelected: function (date) {
443
+ return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth() && date.getDate() === picker.$date.getDate();
444
+ },
445
+ isDisabled: function (date) {
446
+ return date.getTime() < options.minDate || date.getTime() > options.maxDate;
447
+ },
448
+ onKeyDown: function (evt) {
449
+ var actualTime = picker.$date.getTime();
450
+ if (evt.keyCode === 37)
451
+ picker.select(new Date(actualTime - 1 * 86400000), true);
452
+ else if (evt.keyCode === 38)
453
+ picker.select(new Date(actualTime - 7 * 86400000), true);
454
+ else if (evt.keyCode === 39)
455
+ picker.select(new Date(actualTime + 1 * 86400000), true);
456
+ else if (evt.keyCode === 40)
457
+ picker.select(new Date(actualTime + 7 * 86400000), true);
458
+ }
459
+ },
460
+ {
461
+ name: 'month',
462
+ format: 'MMM',
463
+ split: 4,
464
+ steps: { year: 1 },
465
+ update: function (date, force) {
466
+ if (!this.built || date.getFullYear() !== viewDate.year) {
467
+ angular.extend(viewDate, {
468
+ year: picker.$date.getFullYear(),
469
+ month: picker.$date.getMonth(),
470
+ date: picker.$date.getDate()
471
+ });
472
+ picker.$build();
473
+ } else if (date.getMonth() !== viewDate.month) {
474
+ angular.extend(viewDate, {
475
+ month: picker.$date.getMonth(),
476
+ date: picker.$date.getDate()
477
+ });
478
+ picker.$updateSelected();
479
+ }
480
+ },
481
+ build: function () {
482
+ var firstMonth = new Date(viewDate.year, 0, 1);
483
+ var months = [], month;
484
+ for (var i = 0; i < 12; i++) {
485
+ month = new Date(viewDate.year, i, 1);
486
+ months.push({
487
+ date: month,
488
+ label: dateFilter(month, this.format),
489
+ selected: picker.$isSelected(month),
490
+ disabled: this.isDisabled(month)
491
+ });
492
+ }
493
+ scope.title = dateFilter(month, 'yyyy');
494
+ scope.labels = false;
495
+ scope.rows = split(months, this.split);
496
+ this.built = true;
497
+ },
498
+ isSelected: function (date) {
499
+ return picker.$date && date.getFullYear() === picker.$date.getFullYear() && date.getMonth() === picker.$date.getMonth();
500
+ },
501
+ isDisabled: function (date) {
502
+ var lastDate = +new Date(date.getFullYear(), date.getMonth() + 1, 0);
503
+ return lastDate < options.minDate || date.getTime() > options.maxDate;
504
+ },
505
+ onKeyDown: function (evt) {
506
+ var actualMonth = picker.$date.getMonth();
507
+ if (evt.keyCode === 37)
508
+ picker.select(new Date(picker.$date.setMonth(actualMonth - 1)), true);
509
+ else if (evt.keyCode === 38)
510
+ picker.select(new Date(picker.$date.setMonth(actualMonth - 4)), true);
511
+ else if (evt.keyCode === 39)
512
+ picker.select(new Date(picker.$date.setMonth(actualMonth + 1)), true);
513
+ else if (evt.keyCode === 40)
514
+ picker.select(new Date(picker.$date.setMonth(actualMonth + 4)), true);
515
+ }
516
+ },
517
+ {
518
+ name: 'year',
519
+ format: 'yyyy',
520
+ split: 4,
521
+ steps: { year: 12 },
522
+ update: function (date, force) {
523
+ if (!this.built || force || parseInt(date.getFullYear() / 20, 10) !== parseInt(viewDate.year / 20, 10)) {
524
+ angular.extend(viewDate, {
525
+ year: picker.$date.getFullYear(),
526
+ month: picker.$date.getMonth(),
527
+ date: picker.$date.getDate()
528
+ });
529
+ picker.$build();
530
+ } else if (date.getFullYear() !== viewDate.year) {
531
+ angular.extend(viewDate, {
532
+ year: picker.$date.getFullYear(),
533
+ month: picker.$date.getMonth(),
534
+ date: picker.$date.getDate()
535
+ });
536
+ picker.$updateSelected();
537
+ }
538
+ },
539
+ build: function () {
540
+ var firstYear = viewDate.year - viewDate.year % (this.split * 3);
541
+ var years = [], year;
542
+ for (var i = 0; i < 12; i++) {
543
+ year = new Date(firstYear + i, 0, 1);
544
+ years.push({
545
+ date: year,
546
+ label: dateFilter(year, this.format),
547
+ selected: picker.$isSelected(year),
548
+ disabled: this.isDisabled(year)
549
+ });
550
+ }
551
+ scope.title = years[0].label + '-' + years[years.length - 1].label;
552
+ scope.labels = false;
553
+ scope.rows = split(years, this.split);
554
+ this.built = true;
555
+ },
556
+ isSelected: function (date) {
557
+ return picker.$date && date.getFullYear() === picker.$date.getFullYear();
558
+ },
559
+ isDisabled: function (date) {
560
+ var lastDate = +new Date(date.getFullYear() + 1, 0, 0);
561
+ return lastDate < options.minDate || date.getTime() > options.maxDate;
562
+ },
563
+ onKeyDown: function (evt) {
564
+ var actualYear = picker.$date.getFullYear();
565
+ if (evt.keyCode === 37)
566
+ picker.select(new Date(picker.$date.setYear(actualYear - 1)), true);
567
+ else if (evt.keyCode === 38)
568
+ picker.select(new Date(picker.$date.setYear(actualYear - 4)), true);
569
+ else if (evt.keyCode === 39)
570
+ picker.select(new Date(picker.$date.setYear(actualYear + 1)), true);
571
+ else if (evt.keyCode === 40)
572
+ picker.select(new Date(picker.$date.setYear(actualYear + 4)), true);
573
+ }
574
+ }
575
+ ];
576
+ return {
577
+ views: options.minView ? Array.prototype.slice.call(views, options.minView) : views,
578
+ viewDate: viewDate
579
+ };
580
+ };
581
+ }
582
+ ];
583
+ });