Показать директиву по одной AngularJS

Я пытаюсь использовать несколько директив на одной странице, так что одновременно должен быть включен только один указатель даты, я попытался добавить динамический класс, но почему-то мне нужно дважды щелкнуть в поле ввода, чтобы скрыть другой. Дайте мне знать, что я делаю неправильно здесь.

Рабочий план — http://plnkr.co/edit/ZcJr9zg9PeUeRX4kRhbW?p=preview

HTML-код –

<body ng-controller="mainCtrl"> 
    <div class="" ng-repeat="row in fakeDataSet" style="height: 150px; float: left;">
        <my-datepicker 
            dateid="dateid"
            first-week-day-sunday="true" 
            placeholder="Choose date"
            view-format="Do MMMM YYYY"
            checkval="$index">
        </my-datepicker>
    </div>
</body>

JS-код —

var myApp = angular.module('myApp', []);

myApp.controller('mainCtrl', function($scope){

    $scope.fakeDataSet = [34,787,56,78];
})

myApp.directive('myDatepicker', ['$document', function($document) {

    'use strict';

    var setScopeValues = function (scope, attrs) {
        scope.format = attrs.format || 'YYYY-MM-DD';
        scope.viewFormat = attrs.viewFormat || 'Do MMMM YYYY';
        scope.locale = attrs.locale || 'en';
        scope.firstWeekDaySunday = scope.$eval(attrs.firstWeekDaySunday) || false; 
        scope.placeholder = attrs.placeholder || '';
    };

    return {
        restrict: 'EA',
        scope: {
            checkval: "="
        },
        link: function (scope, element, attrs, ngModel) {           
            scope.dynamicMyDatePicker = 'my-datepicker' + "_" + scope.checkval, 
            scope.dynamicMyDatePickerInput = 'my-datepicker-input' + "_" + scope.checkval;

            setScopeValues(scope, attrs);

            scope.calendarOpened = false;
            scope.days = [];
            scope.dayNames = [];
            scope.viewValue = null;
            scope.dateValue = null;

            moment.locale(scope.locale);
            var date = moment();

            var generateCalendar = function (date) {
                var lastDayOfMonth = date.endOf('month').date(),
                    month = date.month(),
                    year = date.year(),
                    n = 1;

                var firstWeekDay = scope.firstWeekDaySunday === true ? date.set('date', 2).day() : date.set('date', 1).day();
                if (firstWeekDay !== 1) {
                    n -= firstWeekDay - 1;
                }

                //Code to fix date issue
                if(n==2)
                    n = -5;

                scope.dateValue = date.format('MMMM YYYY');
                scope.days = [];

                for (var i = n; i <= lastDayOfMonth; i += 1) {
                    if (i > 0) {
                        scope.days.push({day: i, month: month + 1, year: year, enabled: true});
                    } else {
                        scope.days.push({day: null, month: null, year: null, enabled: false});
                    }
                }
            };

            var generateDayNames = function () {
                var date = scope.firstWeekDaySunday === true ?  moment('2015-06-07') : moment('2015-06-01');
                for (var i = 0; i < 7; i += 1) {
                    scope.dayNames.push(date.format('ddd'));
                    date.add('1', 'd');
                }
            };

            generateDayNames();

            scope.showCalendar = function () {
                scope.calendarOpened = true;
                generateCalendar(date);
            };

            scope.closeCalendar = function () {
                scope.calendarOpened = false;
            };

            scope.prevYear = function () {
                date.subtract(1, 'Y');
                generateCalendar(date);
            };

            scope.prevMonth = function () {
                date.subtract(1, 'M');
                generateCalendar(date);
            };

            scope.nextMonth = function () {
                date.add(1, 'M');
                generateCalendar(date);
            };

            scope.nextYear = function () {
                date.add(1, 'Y');
                generateCalendar(date);
            };

            scope.selectDate = function (event, date) {
                event.preventDefault();
                var selectedDate = moment(date.day + '.' + date.month + '.' + date.year, 'DD.MM.YYYY');
                ngModel.$setViewValue(selectedDate.format(scope.format));
                scope.viewValue = selectedDate.format(scope.viewFormat);
                scope.closeCalendar();
            };

            // if clicked outside of calendar
            //var classList = ['my-datepicker', 'my-datepicker-input'];
            var classList = [];
            classList.push(scope.dynamicMyDatePicker);
            classList.push(scope.dynamicMyDatePickerInput);
            if (attrs.id !== undefined) classList.push(attrs.id);
            $document.on('click', function (e) {
                if (!scope.calendarOpened) return;

                var i = 0,
                    element;

                if (!e.target) return;

                for (element = e.target; element; element = element.parentNode) {
                    var id = element.id;
                    var classNames = element.className;

                    if (id !== undefined) {
                        for (i = 0; i < classList.length; i += 1) {
                            if (id.indexOf(classList[i]) > -1 || classNames.indexOf(classList[i]) > -1) {
                                return;
                            }
                        }
                    }
                }

                scope.closeCalendar();
                scope.$apply();
            });

        },
        template: 
        '<div><input type="text" ng-focus="showCalendar()" ng-value="viewValue" class="my-datepicker-input" ng-class="dynamicMyDatePickerInput" placeholder="{{ placeholder }}"></div>' +
        '<div class="my-datepicker" ng-class="dynamicMyDatePicker" ng-show="calendarOpened">' +
        '  <div class="controls">' +
        '    <div class="left">' +
        '      <i class="fa fa-backward prev-year-btn" ng-click="prevYear()"></i>' +
        '      <i class="fa fa-angle-left prev-month-btn" ng-click="prevMonth()"></i>' +
        '    </div>' +
        '    <span class="date" ng-bind="dateValue"></span>' +
        '    <div class="right">' + 
        '      <i class="fa fa-angle-right next-month-btn" ng-click="nextMonth()"></i>' +
        '      <i class="fa fa-forward next-year-btn" ng-click="nextYear()"></i>' +
        '    </div>' +
        '  </div>' +
        '  <div class="day-names">' +
        '    <span ng-repeat="dn in dayNames">' +
        '      <span>{{ dn }}</span>' +
        '    </span>' +
        '  </div>' +
        '  <div class="calendar">' +
        '    <span ng-repeat="d in days">' +
        '      <span class="day" ng-click="selectDate($event, d)" ng-class="{disabled: !d.enabled}">{{ d.day }}</span>' +
        '    </span>' +
        '  </div>' +
        '</div>'
    };

}]);

person Nesh    schedule 09.05.2016    source источник


Ответы (3)


Вы можете использовать событие mousedown, чтобы закрыть календарь:

$document.on('mousedown', function (e) {
    scope.closeCalendar();
    scope.$apply();
});

Кроме того, вам придется остановить распространение события mousedown, инициированного в самом календаре, чтобы оно не закрывалось при выборе даты:

<div class="my-datepicker" ng-show="calendarOpened" ng-mousedown="$event.stopPropagation()">

Чтобы иметь возможность поместить выбранную дату в input, используйте следующий код в методе selectDate():

scope.val = selectedDate.format(scope.format);

и привяжите переменную val к элементу input:

<input type="text" ng-focus="showCalendar()" ng-model="val" .../>

Вот Plunker.

person Alexander Kravets    schedule 03.06.2016
comment
с этим кодом вы не можете выбрать дату из datepicker - person Nesh; 04.06.2016
comment
@Nesh, я изменил метод selectDate и привязку input в шаблоне. Смотрите обновленный ответ и обновленный плункер. - person Alexander Kravets; 04.06.2016

Есть несколько подходов к решению этой проблемы:

  1. Поместите обработчик документа 'click' внутри функции директивы link(), чтобы проверить, был ли щелчок выполнен внутри текущего элемента директивы или нет. В основном это ваше решение, хотя его можно улучшить с помощью метода jQuery $.closest(): https://api.jquery.com/closest/
  2. Используйте более структурированный подход и объедините список директив календаря с другой директивой, которая отвечает за синхронизацию флага calendarOpened для всех них. Такая директива будет прослушивать массив значений calendarOpened для всех директив календаря и следить за тем, чтобы только одно из них оставалось верным после внесения изменений (календарь открывается/закрывается).
person Danny R    schedule 09.05.2016
comment
мой click внутри link - $document.on('click', function (e) { .. }) - person Nesh; 09.05.2016

То, как я думаю, что вы должны двигаться вперед, - это реализовать myDatepickerService, который обрабатывает всю логику скрытия/отображения и сохранения только средства выбора даты.

Вот некоторый «псевдокод», чтобы вы начали думать:

myApp.service('myDatepickerService', function () {
  this.datepickers = [];
  this.currentDatepicker = null;

  this.openDatepicker = function($scope) {
    if (this.currentDatepicker) {
      this.closeDatepicker();
    }

    this.currentDatepicker = $scope;
    $scope.showCalendar(); // From your linking function
  };

  this.closeDatepicker = function () {
    this.currentDatepicker.closeCalendar();
    this.currentDatepicker = null;
  };
});

Затем по вашей директиве:

myApp.directive('myDatepicker', ['myDatepickerService', function(myDatepickerService) {
  return {
    restrict: 'EA',
    scope: {
        checkval: "="
    },
    link: function($scope, el, attrs) {
      $scope.open = function () {
        myDatepickerService.openDatepicker($scope);        
      };

      el.on('click', $scope.open);
    }
  };
});

РЕДАКТИРОВАТЬ: добавить больше, почему я думаю, что это хороший путь.

Итак, ИМХО, вы возлагаете большую ответственность на каждый элемент выбора даты, и это также неэффективно, поскольку весь код, который прослушивает щелчок по $document, запускается каждый раз, когда создается новый элемент выбора даты, и, что еще хуже, каждый раз, когда click достигает $document, он будет выполняться на каждом средстве выбора даты. Таким образом, вы отделяете этот код от каждого элемента и отправляете его в службу.

Кроме того, я думаю, что вам следует поместить логику в контроллер вместо функции связывания, но это уже другая история!

person Antonio Laguna    schedule 09.05.2016
comment
Спасибо за предложение... Я попробую сервисный способ, хотя моя первоначальная мысль состоит в том, чтобы добавить динамический класс к каждому календарю и входу календаря (dynamicMyDatePicker и dynamicMyDatePickerInput) и показать/скрыть календарь на основе щелкнутого элемента ввода. - person Nesh; 09.05.2016
comment
Я проверяю щелчок каждый раз, чтобы знать, где был щелчок во вводе или в самом календаре, чтобы оставаться активированным - person Nesh; 09.05.2016
comment
Да, но этот обработчик запускается при каждом щелчке по каждому отдельному средству выбора даты, что действительно неэффективно. Вы должны быть привязаны к элементу и элементам средства выбора даты, а не к документу $. - person Antonio Laguna; 09.05.2016