| @@ 2570-2973 (lines=404) @@ | ||
| 2567 | }) |
|
| 2568 | ||
| 2569 | .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning', |
|
| 2570 | function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) { |
|
| 2571 | var cache = {}, |
|
| 2572 | isHtml5DateInput = false; |
|
| 2573 | var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, |
|
| 2574 | datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl, |
|
| 2575 | ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [], |
|
| 2576 | timezone; |
|
| 2577 | ||
| 2578 | this.init = function(_ngModel_) { |
|
| 2579 | ngModel = _ngModel_; |
|
| 2580 | ngModelOptions = _ngModel_.$options; |
|
| 2581 | closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ? |
|
| 2582 | $scope.$parent.$eval($attrs.closeOnDateSelection) : |
|
| 2583 | datepickerPopupConfig.closeOnDateSelection; |
|
| 2584 | appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ? |
|
| 2585 | $scope.$parent.$eval($attrs.datepickerAppendToBody) : |
|
| 2586 | datepickerPopupConfig.appendToBody; |
|
| 2587 | onOpenFocus = angular.isDefined($attrs.onOpenFocus) ? |
|
| 2588 | $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; |
|
| 2589 | datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ? |
|
| 2590 | $attrs.datepickerPopupTemplateUrl : |
|
| 2591 | datepickerPopupConfig.datepickerPopupTemplateUrl; |
|
| 2592 | datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ? |
|
| 2593 | $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; |
|
| 2594 | altInputFormats = angular.isDefined($attrs.altInputFormats) ? |
|
| 2595 | $scope.$parent.$eval($attrs.altInputFormats) : |
|
| 2596 | datepickerPopupConfig.altInputFormats; |
|
| 2597 | ||
| 2598 | $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ? |
|
| 2599 | $scope.$parent.$eval($attrs.showButtonBar) : |
|
| 2600 | datepickerPopupConfig.showButtonBar; |
|
| 2601 | ||
| 2602 | if (datepickerPopupConfig.html5Types[$attrs.type]) { |
|
| 2603 | dateFormat = datepickerPopupConfig.html5Types[$attrs.type]; |
|
| 2604 | isHtml5DateInput = true; |
|
| 2605 | } else { |
|
| 2606 | dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; |
|
| 2607 | $attrs.$observe('uibDatepickerPopup', function(value, oldValue) { |
|
| 2608 | var newDateFormat = value || datepickerPopupConfig.datepickerPopup; |
|
| 2609 | // Invalidate the $modelValue to ensure that formatters re-run |
|
| 2610 | // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 |
|
| 2611 | if (newDateFormat !== dateFormat) { |
|
| 2612 | dateFormat = newDateFormat; |
|
| 2613 | ngModel.$modelValue = null; |
|
| 2614 | ||
| 2615 | if (!dateFormat) { |
|
| 2616 | throw new Error('uibDatepickerPopup must have a date format specified.'); |
|
| 2617 | } |
|
| 2618 | } |
|
| 2619 | }); |
|
| 2620 | } |
|
| 2621 | ||
| 2622 | if (!dateFormat) { |
|
| 2623 | throw new Error('uibDatepickerPopup must have a date format specified.'); |
|
| 2624 | } |
|
| 2625 | ||
| 2626 | if (isHtml5DateInput && $attrs.uibDatepickerPopup) { |
|
| 2627 | throw new Error('HTML5 date input types do not support custom formats.'); |
|
| 2628 | } |
|
| 2629 | ||
| 2630 | // popup element used to display calendar |
|
| 2631 | popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>'); |
|
| 2632 | if (ngModelOptions) { |
|
| 2633 | timezone = ngModelOptions.timezone; |
|
| 2634 | $scope.ngModelOptions = angular.copy(ngModelOptions); |
|
| 2635 | $scope.ngModelOptions.timezone = null; |
|
| 2636 | if ($scope.ngModelOptions.updateOnDefault === true) { |
|
| 2637 | $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ? |
|
| 2638 | $scope.ngModelOptions.updateOn + ' default' : 'default'; |
|
| 2639 | } |
|
| 2640 | ||
| 2641 | popupEl.attr('ng-model-options', 'ngModelOptions'); |
|
| 2642 | } else { |
|
| 2643 | timezone = null; |
|
| 2644 | } |
|
| 2645 | ||
| 2646 | popupEl.attr({ |
|
| 2647 | 'ng-model': 'date', |
|
| 2648 | 'ng-change': 'dateSelection(date)', |
|
| 2649 | 'template-url': datepickerPopupTemplateUrl |
|
| 2650 | }); |
|
| 2651 | ||
| 2652 | // datepicker element |
|
| 2653 | datepickerEl = angular.element(popupEl.children()[0]); |
|
| 2654 | datepickerEl.attr('template-url', datepickerTemplateUrl); |
|
| 2655 | ||
| 2656 | if (!$scope.datepickerOptions) { |
|
| 2657 | $scope.datepickerOptions = {}; |
|
| 2658 | } |
|
| 2659 | ||
| 2660 | if (isHtml5DateInput) { |
|
| 2661 | if ($attrs.type === 'month') { |
|
| 2662 | $scope.datepickerOptions.datepickerMode = 'month'; |
|
| 2663 | $scope.datepickerOptions.minMode = 'month'; |
|
| 2664 | } |
|
| 2665 | } |
|
| 2666 | ||
| 2667 | datepickerEl.attr('datepicker-options', 'datepickerOptions'); |
|
| 2668 | ||
| 2669 | if (!isHtml5DateInput) { |
|
| 2670 | // Internal API to maintain the correct ng-invalid-[key] class |
|
| 2671 | ngModel.$$parserName = 'date'; |
|
| 2672 | ngModel.$validators.date = validator; |
|
| 2673 | ngModel.$parsers.unshift(parseDate); |
|
| 2674 | ngModel.$formatters.push(function(value) { |
|
| 2675 | if (ngModel.$isEmpty(value)) { |
|
| 2676 | $scope.date = value; |
|
| 2677 | return value; |
|
| 2678 | } |
|
| 2679 | ||
| 2680 | $scope.date = dateParser.fromTimezone(value, timezone); |
|
| 2681 | ||
| 2682 | if (angular.isNumber($scope.date)) { |
|
| 2683 | $scope.date = new Date($scope.date); |
|
| 2684 | } |
|
| 2685 | ||
| 2686 | return dateParser.filter($scope.date, dateFormat); |
|
| 2687 | }); |
|
| 2688 | } else { |
|
| 2689 | ngModel.$formatters.push(function(value) { |
|
| 2690 | $scope.date = dateParser.fromTimezone(value, timezone); |
|
| 2691 | return value; |
|
| 2692 | }); |
|
| 2693 | } |
|
| 2694 | ||
| 2695 | // Detect changes in the view from the text box |
|
| 2696 | ngModel.$viewChangeListeners.push(function() { |
|
| 2697 | $scope.date = parseDateString(ngModel.$viewValue); |
|
| 2698 | }); |
|
| 2699 | ||
| 2700 | $element.on('keydown', inputKeydownBind); |
|
| 2701 | ||
| 2702 | $popup = $compile(popupEl)($scope); |
|
| 2703 | // Prevent jQuery cache memory leak (template is now redundant after linking) |
|
| 2704 | popupEl.remove(); |
|
| 2705 | ||
| 2706 | if (appendToBody) { |
|
| 2707 | $document.find('body').append($popup); |
|
| 2708 | } else { |
|
| 2709 | $element.after($popup); |
|
| 2710 | } |
|
| 2711 | ||
| 2712 | $scope.$on('$destroy', function() { |
|
| 2713 | if ($scope.isOpen === true) { |
|
| 2714 | if (!$rootScope.$$phase) { |
|
| 2715 | $scope.$apply(function() { |
|
| 2716 | $scope.isOpen = false; |
|
| 2717 | }); |
|
| 2718 | } |
|
| 2719 | } |
|
| 2720 | ||
| 2721 | $popup.remove(); |
|
| 2722 | $element.off('keydown', inputKeydownBind); |
|
| 2723 | $document.off('click', documentClickBind); |
|
| 2724 | if (scrollParentEl) { |
|
| 2725 | scrollParentEl.off('scroll', positionPopup); |
|
| 2726 | } |
|
| 2727 | angular.element($window).off('resize', positionPopup); |
|
| 2728 | ||
| 2729 | //Clear all watch listeners on destroy |
|
| 2730 | while (watchListeners.length) { |
|
| 2731 | watchListeners.shift()(); |
|
| 2732 | } |
|
| 2733 | }); |
|
| 2734 | }; |
|
| 2735 | ||
| 2736 | $scope.getText = function(key) { |
|
| 2737 | return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; |
|
| 2738 | }; |
|
| 2739 | ||
| 2740 | $scope.isDisabled = function(date) { |
|
| 2741 | if (date === 'today') { |
|
| 2742 | date = dateParser.fromTimezone(new Date(), timezone); |
|
| 2743 | } |
|
| 2744 | ||
| 2745 | var dates = {}; |
|
| 2746 | angular.forEach(['minDate', 'maxDate'], function(key) { |
|
| 2747 | if (!$scope.datepickerOptions[key]) { |
|
| 2748 | dates[key] = null; |
|
| 2749 | } else if (angular.isDate($scope.datepickerOptions[key])) { |
|
| 2750 | dates[key] = dateParser.fromTimezone(new Date($scope.datepickerOptions[key]), timezone); |
|
| 2751 | } else { |
|
| 2752 | if ($datepickerPopupLiteralWarning) { |
|
| 2753 | $log.warn('Literal date support has been deprecated, please switch to date object usage'); |
|
| 2754 | } |
|
| 2755 | ||
| 2756 | dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium')); |
|
| 2757 | } |
|
| 2758 | }); |
|
| 2759 | ||
| 2760 | return $scope.datepickerOptions && |
|
| 2761 | dates.minDate && $scope.compare(date, dates.minDate) < 0 || |
|
| 2762 | dates.maxDate && $scope.compare(date, dates.maxDate) > 0; |
|
| 2763 | }; |
|
| 2764 | ||
| 2765 | $scope.compare = function(date1, date2) { |
|
| 2766 | return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); |
|
| 2767 | }; |
|
| 2768 | ||
| 2769 | // Inner change |
|
| 2770 | $scope.dateSelection = function(dt) { |
|
| 2771 | if (angular.isDefined(dt)) { |
|
| 2772 | $scope.date = dt; |
|
| 2773 | } |
|
| 2774 | var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function |
|
| 2775 | $element.val(date); |
|
| 2776 | ngModel.$setViewValue(date); |
|
| 2777 | ||
| 2778 | if (closeOnDateSelection) { |
|
| 2779 | $scope.isOpen = false; |
|
| 2780 | $element[0].focus(); |
|
| 2781 | } |
|
| 2782 | }; |
|
| 2783 | ||
| 2784 | $scope.keydown = function(evt) { |
|
| 2785 | if (evt.which === 27) { |
|
| 2786 | evt.stopPropagation(); |
|
| 2787 | $scope.isOpen = false; |
|
| 2788 | $element[0].focus(); |
|
| 2789 | } |
|
| 2790 | }; |
|
| 2791 | ||
| 2792 | $scope.select = function(date, evt) { |
|
| 2793 | evt.stopPropagation(); |
|
| 2794 | ||
| 2795 | if (date === 'today') { |
|
| 2796 | var today = new Date(); |
|
| 2797 | if (angular.isDate($scope.date)) { |
|
| 2798 | date = new Date($scope.date); |
|
| 2799 | date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); |
|
| 2800 | } else { |
|
| 2801 | date = new Date(today.setHours(0, 0, 0, 0)); |
|
| 2802 | } |
|
| 2803 | } |
|
| 2804 | $scope.dateSelection(date); |
|
| 2805 | }; |
|
| 2806 | ||
| 2807 | $scope.close = function(evt) { |
|
| 2808 | evt.stopPropagation(); |
|
| 2809 | ||
| 2810 | $scope.isOpen = false; |
|
| 2811 | $element[0].focus(); |
|
| 2812 | }; |
|
| 2813 | ||
| 2814 | $scope.disabled = angular.isDefined($attrs.disabled) || false; |
|
| 2815 | if ($attrs.ngDisabled) { |
|
| 2816 | watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) { |
|
| 2817 | $scope.disabled = disabled; |
|
| 2818 | })); |
|
| 2819 | } |
|
| 2820 | ||
| 2821 | $scope.$watch('isOpen', function(value) { |
|
| 2822 | if (value) { |
|
| 2823 | if (!$scope.disabled) { |
|
| 2824 | $timeout(function() { |
|
| 2825 | positionPopup(); |
|
| 2826 | ||
| 2827 | if (onOpenFocus) { |
|
| 2828 | $scope.$broadcast('uib:datepicker.focus'); |
|
| 2829 | } |
|
| 2830 | ||
| 2831 | $document.on('click', documentClickBind); |
|
| 2832 | ||
| 2833 | var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; |
|
| 2834 | if (appendToBody || $position.parsePlacement(placement)[2]) { |
|
| 2835 | scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element)); |
|
| 2836 | if (scrollParentEl) { |
|
| 2837 | scrollParentEl.on('scroll', positionPopup); |
|
| 2838 | } |
|
| 2839 | } else { |
|
| 2840 | scrollParentEl = null; |
|
| 2841 | } |
|
| 2842 | ||
| 2843 | angular.element($window).on('resize', positionPopup); |
|
| 2844 | }, 0, false); |
|
| 2845 | } else { |
|
| 2846 | $scope.isOpen = false; |
|
| 2847 | } |
|
| 2848 | } else { |
|
| 2849 | $document.off('click', documentClickBind); |
|
| 2850 | if (scrollParentEl) { |
|
| 2851 | scrollParentEl.off('scroll', positionPopup); |
|
| 2852 | } |
|
| 2853 | angular.element($window).off('resize', positionPopup); |
|
| 2854 | } |
|
| 2855 | }); |
|
| 2856 | ||
| 2857 | function cameltoDash(string) { |
|
| 2858 | return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); |
|
| 2859 | } |
|
| 2860 | ||
| 2861 | function parseDateString(viewValue) { |
|
| 2862 | var date = dateParser.parse(viewValue, dateFormat, $scope.date); |
|
| 2863 | if (isNaN(date)) { |
|
| 2864 | for (var i = 0; i < altInputFormats.length; i++) { |
|
| 2865 | date = dateParser.parse(viewValue, altInputFormats[i], $scope.date); |
|
| 2866 | if (!isNaN(date)) { |
|
| 2867 | return date; |
|
| 2868 | } |
|
| 2869 | } |
|
| 2870 | } |
|
| 2871 | return date; |
|
| 2872 | } |
|
| 2873 | ||
| 2874 | function parseDate(viewValue) { |
|
| 2875 | if (angular.isNumber(viewValue)) { |
|
| 2876 | // presumably timestamp to date object |
|
| 2877 | viewValue = new Date(viewValue); |
|
| 2878 | } |
|
| 2879 | ||
| 2880 | if (!viewValue) { |
|
| 2881 | return null; |
|
| 2882 | } |
|
| 2883 | ||
| 2884 | if (angular.isDate(viewValue) && !isNaN(viewValue)) { |
|
| 2885 | return viewValue; |
|
| 2886 | } |
|
| 2887 | ||
| 2888 | if (angular.isString(viewValue)) { |
|
| 2889 | var date = parseDateString(viewValue); |
|
| 2890 | if (!isNaN(date)) { |
|
| 2891 | return dateParser.toTimezone(date, timezone); |
|
| 2892 | } |
|
| 2893 | } |
|
| 2894 | ||
| 2895 | return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined; |
|
| 2896 | } |
|
| 2897 | ||
| 2898 | function validator(modelValue, viewValue) { |
|
| 2899 | var value = modelValue || viewValue; |
|
| 2900 | ||
| 2901 | if (!$attrs.ngRequired && !value) { |
|
| 2902 | return true; |
|
| 2903 | } |
|
| 2904 | ||
| 2905 | if (angular.isNumber(value)) { |
|
| 2906 | value = new Date(value); |
|
| 2907 | } |
|
| 2908 | ||
| 2909 | if (!value) { |
|
| 2910 | return true; |
|
| 2911 | } |
|
| 2912 | ||
| 2913 | if (angular.isDate(value) && !isNaN(value)) { |
|
| 2914 | return true; |
|
| 2915 | } |
|
| 2916 | ||
| 2917 | if (angular.isString(value)) { |
|
| 2918 | return !isNaN(parseDateString(viewValue)); |
|
| 2919 | } |
|
| 2920 | ||
| 2921 | return false; |
|
| 2922 | } |
|
| 2923 | ||
| 2924 | function documentClickBind(event) { |
|
| 2925 | if (!$scope.isOpen && $scope.disabled) { |
|
| 2926 | return; |
|
| 2927 | } |
|
| 2928 | ||
| 2929 | var popup = $popup[0]; |
|
| 2930 | var dpContainsTarget = $element[0].contains(event.target); |
|
| 2931 | // The popup node may not be an element node |
|
| 2932 | // In some browsers (IE) only element nodes have the 'contains' function |
|
| 2933 | var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); |
|
| 2934 | if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { |
|
| 2935 | $scope.$apply(function() { |
|
| 2936 | $scope.isOpen = false; |
|
| 2937 | }); |
|
| 2938 | } |
|
| 2939 | } |
|
| 2940 | ||
| 2941 | function inputKeydownBind(evt) { |
|
| 2942 | if (evt.which === 27 && $scope.isOpen) { |
|
| 2943 | evt.preventDefault(); |
|
| 2944 | evt.stopPropagation(); |
|
| 2945 | $scope.$apply(function() { |
|
| 2946 | $scope.isOpen = false; |
|
| 2947 | }); |
|
| 2948 | $element[0].focus(); |
|
| 2949 | } else if (evt.which === 40 && !$scope.isOpen) { |
|
| 2950 | evt.preventDefault(); |
|
| 2951 | evt.stopPropagation(); |
|
| 2952 | $scope.$apply(function() { |
|
| 2953 | $scope.isOpen = true; |
|
| 2954 | }); |
|
| 2955 | } |
|
| 2956 | } |
|
| 2957 | ||
| 2958 | function positionPopup() { |
|
| 2959 | if ($scope.isOpen) { |
|
| 2960 | var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup')); |
|
| 2961 | var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; |
|
| 2962 | var position = $position.positionElements($element, dpElement, placement, appendToBody); |
|
| 2963 | dpElement.css({top: position.top + 'px', left: position.left + 'px'}); |
|
| 2964 | if (dpElement.hasClass('uib-position-measure')) { |
|
| 2965 | dpElement.removeClass('uib-position-measure'); |
|
| 2966 | } |
|
| 2967 | } |
|
| 2968 | } |
|
| 2969 | ||
| 2970 | $scope.$on('uib:datepicker.mode', function() { |
|
| 2971 | $timeout(positionPopup, 0, false); |
|
| 2972 | }); |
|
| 2973 | }]) |
|
| 2974 | ||
| 2975 | .directive('uibDatepickerPopup', function() { |
|
| 2976 | return { |
|
| @@ 2569-2972 (lines=404) @@ | ||
| 2566 | }) |
|
| 2567 | ||
| 2568 | .controller('UibDatepickerPopupController', ['$scope', '$element', '$attrs', '$compile', '$log', '$parse', '$window', '$document', '$rootScope', '$uibPosition', 'dateFilter', 'uibDateParser', 'uibDatepickerPopupConfig', '$timeout', 'uibDatepickerConfig', '$datepickerPopupLiteralWarning', |
|
| 2569 | function($scope, $element, $attrs, $compile, $log, $parse, $window, $document, $rootScope, $position, dateFilter, dateParser, datepickerPopupConfig, $timeout, datepickerConfig, $datepickerPopupLiteralWarning) { |
|
| 2570 | var cache = {}, |
|
| 2571 | isHtml5DateInput = false; |
|
| 2572 | var dateFormat, closeOnDateSelection, appendToBody, onOpenFocus, |
|
| 2573 | datepickerPopupTemplateUrl, datepickerTemplateUrl, popupEl, datepickerEl, scrollParentEl, |
|
| 2574 | ngModel, ngModelOptions, $popup, altInputFormats, watchListeners = [], |
|
| 2575 | timezone; |
|
| 2576 | ||
| 2577 | this.init = function(_ngModel_) { |
|
| 2578 | ngModel = _ngModel_; |
|
| 2579 | ngModelOptions = _ngModel_.$options; |
|
| 2580 | closeOnDateSelection = angular.isDefined($attrs.closeOnDateSelection) ? |
|
| 2581 | $scope.$parent.$eval($attrs.closeOnDateSelection) : |
|
| 2582 | datepickerPopupConfig.closeOnDateSelection; |
|
| 2583 | appendToBody = angular.isDefined($attrs.datepickerAppendToBody) ? |
|
| 2584 | $scope.$parent.$eval($attrs.datepickerAppendToBody) : |
|
| 2585 | datepickerPopupConfig.appendToBody; |
|
| 2586 | onOpenFocus = angular.isDefined($attrs.onOpenFocus) ? |
|
| 2587 | $scope.$parent.$eval($attrs.onOpenFocus) : datepickerPopupConfig.onOpenFocus; |
|
| 2588 | datepickerPopupTemplateUrl = angular.isDefined($attrs.datepickerPopupTemplateUrl) ? |
|
| 2589 | $attrs.datepickerPopupTemplateUrl : |
|
| 2590 | datepickerPopupConfig.datepickerPopupTemplateUrl; |
|
| 2591 | datepickerTemplateUrl = angular.isDefined($attrs.datepickerTemplateUrl) ? |
|
| 2592 | $attrs.datepickerTemplateUrl : datepickerPopupConfig.datepickerTemplateUrl; |
|
| 2593 | altInputFormats = angular.isDefined($attrs.altInputFormats) ? |
|
| 2594 | $scope.$parent.$eval($attrs.altInputFormats) : |
|
| 2595 | datepickerPopupConfig.altInputFormats; |
|
| 2596 | ||
| 2597 | $scope.showButtonBar = angular.isDefined($attrs.showButtonBar) ? |
|
| 2598 | $scope.$parent.$eval($attrs.showButtonBar) : |
|
| 2599 | datepickerPopupConfig.showButtonBar; |
|
| 2600 | ||
| 2601 | if (datepickerPopupConfig.html5Types[$attrs.type]) { |
|
| 2602 | dateFormat = datepickerPopupConfig.html5Types[$attrs.type]; |
|
| 2603 | isHtml5DateInput = true; |
|
| 2604 | } else { |
|
| 2605 | dateFormat = $attrs.uibDatepickerPopup || datepickerPopupConfig.datepickerPopup; |
|
| 2606 | $attrs.$observe('uibDatepickerPopup', function(value, oldValue) { |
|
| 2607 | var newDateFormat = value || datepickerPopupConfig.datepickerPopup; |
|
| 2608 | // Invalidate the $modelValue to ensure that formatters re-run |
|
| 2609 | // FIXME: Refactor when PR is merged: https://github.com/angular/angular.js/pull/10764 |
|
| 2610 | if (newDateFormat !== dateFormat) { |
|
| 2611 | dateFormat = newDateFormat; |
|
| 2612 | ngModel.$modelValue = null; |
|
| 2613 | ||
| 2614 | if (!dateFormat) { |
|
| 2615 | throw new Error('uibDatepickerPopup must have a date format specified.'); |
|
| 2616 | } |
|
| 2617 | } |
|
| 2618 | }); |
|
| 2619 | } |
|
| 2620 | ||
| 2621 | if (!dateFormat) { |
|
| 2622 | throw new Error('uibDatepickerPopup must have a date format specified.'); |
|
| 2623 | } |
|
| 2624 | ||
| 2625 | if (isHtml5DateInput && $attrs.uibDatepickerPopup) { |
|
| 2626 | throw new Error('HTML5 date input types do not support custom formats.'); |
|
| 2627 | } |
|
| 2628 | ||
| 2629 | // popup element used to display calendar |
|
| 2630 | popupEl = angular.element('<div uib-datepicker-popup-wrap><div uib-datepicker></div></div>'); |
|
| 2631 | if (ngModelOptions) { |
|
| 2632 | timezone = ngModelOptions.timezone; |
|
| 2633 | $scope.ngModelOptions = angular.copy(ngModelOptions); |
|
| 2634 | $scope.ngModelOptions.timezone = null; |
|
| 2635 | if ($scope.ngModelOptions.updateOnDefault === true) { |
|
| 2636 | $scope.ngModelOptions.updateOn = $scope.ngModelOptions.updateOn ? |
|
| 2637 | $scope.ngModelOptions.updateOn + ' default' : 'default'; |
|
| 2638 | } |
|
| 2639 | ||
| 2640 | popupEl.attr('ng-model-options', 'ngModelOptions'); |
|
| 2641 | } else { |
|
| 2642 | timezone = null; |
|
| 2643 | } |
|
| 2644 | ||
| 2645 | popupEl.attr({ |
|
| 2646 | 'ng-model': 'date', |
|
| 2647 | 'ng-change': 'dateSelection(date)', |
|
| 2648 | 'template-url': datepickerPopupTemplateUrl |
|
| 2649 | }); |
|
| 2650 | ||
| 2651 | // datepicker element |
|
| 2652 | datepickerEl = angular.element(popupEl.children()[0]); |
|
| 2653 | datepickerEl.attr('template-url', datepickerTemplateUrl); |
|
| 2654 | ||
| 2655 | if (!$scope.datepickerOptions) { |
|
| 2656 | $scope.datepickerOptions = {}; |
|
| 2657 | } |
|
| 2658 | ||
| 2659 | if (isHtml5DateInput) { |
|
| 2660 | if ($attrs.type === 'month') { |
|
| 2661 | $scope.datepickerOptions.datepickerMode = 'month'; |
|
| 2662 | $scope.datepickerOptions.minMode = 'month'; |
|
| 2663 | } |
|
| 2664 | } |
|
| 2665 | ||
| 2666 | datepickerEl.attr('datepicker-options', 'datepickerOptions'); |
|
| 2667 | ||
| 2668 | if (!isHtml5DateInput) { |
|
| 2669 | // Internal API to maintain the correct ng-invalid-[key] class |
|
| 2670 | ngModel.$$parserName = 'date'; |
|
| 2671 | ngModel.$validators.date = validator; |
|
| 2672 | ngModel.$parsers.unshift(parseDate); |
|
| 2673 | ngModel.$formatters.push(function(value) { |
|
| 2674 | if (ngModel.$isEmpty(value)) { |
|
| 2675 | $scope.date = value; |
|
| 2676 | return value; |
|
| 2677 | } |
|
| 2678 | ||
| 2679 | $scope.date = dateParser.fromTimezone(value, timezone); |
|
| 2680 | ||
| 2681 | if (angular.isNumber($scope.date)) { |
|
| 2682 | $scope.date = new Date($scope.date); |
|
| 2683 | } |
|
| 2684 | ||
| 2685 | return dateParser.filter($scope.date, dateFormat); |
|
| 2686 | }); |
|
| 2687 | } else { |
|
| 2688 | ngModel.$formatters.push(function(value) { |
|
| 2689 | $scope.date = dateParser.fromTimezone(value, timezone); |
|
| 2690 | return value; |
|
| 2691 | }); |
|
| 2692 | } |
|
| 2693 | ||
| 2694 | // Detect changes in the view from the text box |
|
| 2695 | ngModel.$viewChangeListeners.push(function() { |
|
| 2696 | $scope.date = parseDateString(ngModel.$viewValue); |
|
| 2697 | }); |
|
| 2698 | ||
| 2699 | $element.on('keydown', inputKeydownBind); |
|
| 2700 | ||
| 2701 | $popup = $compile(popupEl)($scope); |
|
| 2702 | // Prevent jQuery cache memory leak (template is now redundant after linking) |
|
| 2703 | popupEl.remove(); |
|
| 2704 | ||
| 2705 | if (appendToBody) { |
|
| 2706 | $document.find('body').append($popup); |
|
| 2707 | } else { |
|
| 2708 | $element.after($popup); |
|
| 2709 | } |
|
| 2710 | ||
| 2711 | $scope.$on('$destroy', function() { |
|
| 2712 | if ($scope.isOpen === true) { |
|
| 2713 | if (!$rootScope.$$phase) { |
|
| 2714 | $scope.$apply(function() { |
|
| 2715 | $scope.isOpen = false; |
|
| 2716 | }); |
|
| 2717 | } |
|
| 2718 | } |
|
| 2719 | ||
| 2720 | $popup.remove(); |
|
| 2721 | $element.off('keydown', inputKeydownBind); |
|
| 2722 | $document.off('click', documentClickBind); |
|
| 2723 | if (scrollParentEl) { |
|
| 2724 | scrollParentEl.off('scroll', positionPopup); |
|
| 2725 | } |
|
| 2726 | angular.element($window).off('resize', positionPopup); |
|
| 2727 | ||
| 2728 | //Clear all watch listeners on destroy |
|
| 2729 | while (watchListeners.length) { |
|
| 2730 | watchListeners.shift()(); |
|
| 2731 | } |
|
| 2732 | }); |
|
| 2733 | }; |
|
| 2734 | ||
| 2735 | $scope.getText = function(key) { |
|
| 2736 | return $scope[key + 'Text'] || datepickerPopupConfig[key + 'Text']; |
|
| 2737 | }; |
|
| 2738 | ||
| 2739 | $scope.isDisabled = function(date) { |
|
| 2740 | if (date === 'today') { |
|
| 2741 | date = dateParser.fromTimezone(new Date(), timezone); |
|
| 2742 | } |
|
| 2743 | ||
| 2744 | var dates = {}; |
|
| 2745 | angular.forEach(['minDate', 'maxDate'], function(key) { |
|
| 2746 | if (!$scope.datepickerOptions[key]) { |
|
| 2747 | dates[key] = null; |
|
| 2748 | } else if (angular.isDate($scope.datepickerOptions[key])) { |
|
| 2749 | dates[key] = dateParser.fromTimezone(new Date($scope.datepickerOptions[key]), timezone); |
|
| 2750 | } else { |
|
| 2751 | if ($datepickerPopupLiteralWarning) { |
|
| 2752 | $log.warn('Literal date support has been deprecated, please switch to date object usage'); |
|
| 2753 | } |
|
| 2754 | ||
| 2755 | dates[key] = new Date(dateFilter($scope.datepickerOptions[key], 'medium')); |
|
| 2756 | } |
|
| 2757 | }); |
|
| 2758 | ||
| 2759 | return $scope.datepickerOptions && |
|
| 2760 | dates.minDate && $scope.compare(date, dates.minDate) < 0 || |
|
| 2761 | dates.maxDate && $scope.compare(date, dates.maxDate) > 0; |
|
| 2762 | }; |
|
| 2763 | ||
| 2764 | $scope.compare = function(date1, date2) { |
|
| 2765 | return new Date(date1.getFullYear(), date1.getMonth(), date1.getDate()) - new Date(date2.getFullYear(), date2.getMonth(), date2.getDate()); |
|
| 2766 | }; |
|
| 2767 | ||
| 2768 | // Inner change |
|
| 2769 | $scope.dateSelection = function(dt) { |
|
| 2770 | if (angular.isDefined(dt)) { |
|
| 2771 | $scope.date = dt; |
|
| 2772 | } |
|
| 2773 | var date = $scope.date ? dateParser.filter($scope.date, dateFormat) : null; // Setting to NULL is necessary for form validators to function |
|
| 2774 | $element.val(date); |
|
| 2775 | ngModel.$setViewValue(date); |
|
| 2776 | ||
| 2777 | if (closeOnDateSelection) { |
|
| 2778 | $scope.isOpen = false; |
|
| 2779 | $element[0].focus(); |
|
| 2780 | } |
|
| 2781 | }; |
|
| 2782 | ||
| 2783 | $scope.keydown = function(evt) { |
|
| 2784 | if (evt.which === 27) { |
|
| 2785 | evt.stopPropagation(); |
|
| 2786 | $scope.isOpen = false; |
|
| 2787 | $element[0].focus(); |
|
| 2788 | } |
|
| 2789 | }; |
|
| 2790 | ||
| 2791 | $scope.select = function(date, evt) { |
|
| 2792 | evt.stopPropagation(); |
|
| 2793 | ||
| 2794 | if (date === 'today') { |
|
| 2795 | var today = new Date(); |
|
| 2796 | if (angular.isDate($scope.date)) { |
|
| 2797 | date = new Date($scope.date); |
|
| 2798 | date.setFullYear(today.getFullYear(), today.getMonth(), today.getDate()); |
|
| 2799 | } else { |
|
| 2800 | date = new Date(today.setHours(0, 0, 0, 0)); |
|
| 2801 | } |
|
| 2802 | } |
|
| 2803 | $scope.dateSelection(date); |
|
| 2804 | }; |
|
| 2805 | ||
| 2806 | $scope.close = function(evt) { |
|
| 2807 | evt.stopPropagation(); |
|
| 2808 | ||
| 2809 | $scope.isOpen = false; |
|
| 2810 | $element[0].focus(); |
|
| 2811 | }; |
|
| 2812 | ||
| 2813 | $scope.disabled = angular.isDefined($attrs.disabled) || false; |
|
| 2814 | if ($attrs.ngDisabled) { |
|
| 2815 | watchListeners.push($scope.$parent.$watch($parse($attrs.ngDisabled), function(disabled) { |
|
| 2816 | $scope.disabled = disabled; |
|
| 2817 | })); |
|
| 2818 | } |
|
| 2819 | ||
| 2820 | $scope.$watch('isOpen', function(value) { |
|
| 2821 | if (value) { |
|
| 2822 | if (!$scope.disabled) { |
|
| 2823 | $timeout(function() { |
|
| 2824 | positionPopup(); |
|
| 2825 | ||
| 2826 | if (onOpenFocus) { |
|
| 2827 | $scope.$broadcast('uib:datepicker.focus'); |
|
| 2828 | } |
|
| 2829 | ||
| 2830 | $document.on('click', documentClickBind); |
|
| 2831 | ||
| 2832 | var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; |
|
| 2833 | if (appendToBody || $position.parsePlacement(placement)[2]) { |
|
| 2834 | scrollParentEl = scrollParentEl || angular.element($position.scrollParent($element)); |
|
| 2835 | if (scrollParentEl) { |
|
| 2836 | scrollParentEl.on('scroll', positionPopup); |
|
| 2837 | } |
|
| 2838 | } else { |
|
| 2839 | scrollParentEl = null; |
|
| 2840 | } |
|
| 2841 | ||
| 2842 | angular.element($window).on('resize', positionPopup); |
|
| 2843 | }, 0, false); |
|
| 2844 | } else { |
|
| 2845 | $scope.isOpen = false; |
|
| 2846 | } |
|
| 2847 | } else { |
|
| 2848 | $document.off('click', documentClickBind); |
|
| 2849 | if (scrollParentEl) { |
|
| 2850 | scrollParentEl.off('scroll', positionPopup); |
|
| 2851 | } |
|
| 2852 | angular.element($window).off('resize', positionPopup); |
|
| 2853 | } |
|
| 2854 | }); |
|
| 2855 | ||
| 2856 | function cameltoDash(string) { |
|
| 2857 | return string.replace(/([A-Z])/g, function($1) { return '-' + $1.toLowerCase(); }); |
|
| 2858 | } |
|
| 2859 | ||
| 2860 | function parseDateString(viewValue) { |
|
| 2861 | var date = dateParser.parse(viewValue, dateFormat, $scope.date); |
|
| 2862 | if (isNaN(date)) { |
|
| 2863 | for (var i = 0; i < altInputFormats.length; i++) { |
|
| 2864 | date = dateParser.parse(viewValue, altInputFormats[i], $scope.date); |
|
| 2865 | if (!isNaN(date)) { |
|
| 2866 | return date; |
|
| 2867 | } |
|
| 2868 | } |
|
| 2869 | } |
|
| 2870 | return date; |
|
| 2871 | } |
|
| 2872 | ||
| 2873 | function parseDate(viewValue) { |
|
| 2874 | if (angular.isNumber(viewValue)) { |
|
| 2875 | // presumably timestamp to date object |
|
| 2876 | viewValue = new Date(viewValue); |
|
| 2877 | } |
|
| 2878 | ||
| 2879 | if (!viewValue) { |
|
| 2880 | return null; |
|
| 2881 | } |
|
| 2882 | ||
| 2883 | if (angular.isDate(viewValue) && !isNaN(viewValue)) { |
|
| 2884 | return viewValue; |
|
| 2885 | } |
|
| 2886 | ||
| 2887 | if (angular.isString(viewValue)) { |
|
| 2888 | var date = parseDateString(viewValue); |
|
| 2889 | if (!isNaN(date)) { |
|
| 2890 | return dateParser.toTimezone(date, timezone); |
|
| 2891 | } |
|
| 2892 | } |
|
| 2893 | ||
| 2894 | return ngModel.$options && ngModel.$options.allowInvalid ? viewValue : undefined; |
|
| 2895 | } |
|
| 2896 | ||
| 2897 | function validator(modelValue, viewValue) { |
|
| 2898 | var value = modelValue || viewValue; |
|
| 2899 | ||
| 2900 | if (!$attrs.ngRequired && !value) { |
|
| 2901 | return true; |
|
| 2902 | } |
|
| 2903 | ||
| 2904 | if (angular.isNumber(value)) { |
|
| 2905 | value = new Date(value); |
|
| 2906 | } |
|
| 2907 | ||
| 2908 | if (!value) { |
|
| 2909 | return true; |
|
| 2910 | } |
|
| 2911 | ||
| 2912 | if (angular.isDate(value) && !isNaN(value)) { |
|
| 2913 | return true; |
|
| 2914 | } |
|
| 2915 | ||
| 2916 | if (angular.isString(value)) { |
|
| 2917 | return !isNaN(parseDateString(viewValue)); |
|
| 2918 | } |
|
| 2919 | ||
| 2920 | return false; |
|
| 2921 | } |
|
| 2922 | ||
| 2923 | function documentClickBind(event) { |
|
| 2924 | if (!$scope.isOpen && $scope.disabled) { |
|
| 2925 | return; |
|
| 2926 | } |
|
| 2927 | ||
| 2928 | var popup = $popup[0]; |
|
| 2929 | var dpContainsTarget = $element[0].contains(event.target); |
|
| 2930 | // The popup node may not be an element node |
|
| 2931 | // In some browsers (IE) only element nodes have the 'contains' function |
|
| 2932 | var popupContainsTarget = popup.contains !== undefined && popup.contains(event.target); |
|
| 2933 | if ($scope.isOpen && !(dpContainsTarget || popupContainsTarget)) { |
|
| 2934 | $scope.$apply(function() { |
|
| 2935 | $scope.isOpen = false; |
|
| 2936 | }); |
|
| 2937 | } |
|
| 2938 | } |
|
| 2939 | ||
| 2940 | function inputKeydownBind(evt) { |
|
| 2941 | if (evt.which === 27 && $scope.isOpen) { |
|
| 2942 | evt.preventDefault(); |
|
| 2943 | evt.stopPropagation(); |
|
| 2944 | $scope.$apply(function() { |
|
| 2945 | $scope.isOpen = false; |
|
| 2946 | }); |
|
| 2947 | $element[0].focus(); |
|
| 2948 | } else if (evt.which === 40 && !$scope.isOpen) { |
|
| 2949 | evt.preventDefault(); |
|
| 2950 | evt.stopPropagation(); |
|
| 2951 | $scope.$apply(function() { |
|
| 2952 | $scope.isOpen = true; |
|
| 2953 | }); |
|
| 2954 | } |
|
| 2955 | } |
|
| 2956 | ||
| 2957 | function positionPopup() { |
|
| 2958 | if ($scope.isOpen) { |
|
| 2959 | var dpElement = angular.element($popup[0].querySelector('.uib-datepicker-popup')); |
|
| 2960 | var placement = $attrs.popupPlacement ? $attrs.popupPlacement : datepickerPopupConfig.placement; |
|
| 2961 | var position = $position.positionElements($element, dpElement, placement, appendToBody); |
|
| 2962 | dpElement.css({top: position.top + 'px', left: position.left + 'px'}); |
|
| 2963 | if (dpElement.hasClass('uib-position-measure')) { |
|
| 2964 | dpElement.removeClass('uib-position-measure'); |
|
| 2965 | } |
|
| 2966 | } |
|
| 2967 | } |
|
| 2968 | ||
| 2969 | $scope.$on('uib:datepicker.mode', function() { |
|
| 2970 | $timeout(positionPopup, 0, false); |
|
| 2971 | }); |
|
| 2972 | }]) |
|
| 2973 | ||
| 2974 | .directive('uibDatepickerPopup', function() { |
|
| 2975 | return { |
|