@@ 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 { |