一次显示一个指令 AngularJS
Show directive one at a time AngularJS
我试图在一个页面中使用多个指令,这样一次只能启用一个日期选择器,我尝试通过添加动态 class 但不知何故我需要双击输入框来隐藏另一个.让我知道我在这里做错了什么。
工作计划 - 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>'
};
}]);
我认为你应该继续这样做的方法是实现一个 myDatepickerService
来处理 hiding/showing 的所有逻辑并只保留一个日期选择器。
这里有一些 "pseudocode" 可以让您开始思考:
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
上的点击的所有代码都是 运行创建了新的日期选择器,更糟糕的是,每次点击到达 $document
时,它都会在 每个 单个日期选择器上 运行。通过这种方式,您可以将此代码与每个元素分开,并使其进入服务。
此外,为了有价值,我认为您应该将逻辑放入控制器而不是链接函数,但那是另一回事了!
有几种方法可以解决这个问题:
- 在指令
link()
函数中放置文档 'click'
处理程序,以检查点击是否在当前指令元素内完成。
这基本上是您的解决方案,尽管可以使用 jQuery $.closest() 方法对其进行改进:https://api.jquery.com/closest/
- 使用更结构化的方法,并在另一个指令下聚合日历指令列表,该指令负责同步所有这些指令的
calendarOpened
标志。
这样的指令将监听所有日历指令的 calendarOpened
值数组,并确保其中只有一个在发生变化后保持为真(日历为 opened/closed)
您可以使用mousedown
事件关闭日历:
$document.on('mousedown', function (e) {
scope.closeCalendar();
scope.$apply();
});
此外,您必须停止在日历本身触发的 mousedown
事件的传播,这样当您 select 一个日期时它不会自行关闭:
<div class="my-datepicker" ng-show="calendarOpened" ng-mousedown="$event.stopPropagation()">
为了能够将 selected 日期放入 input
,在 selectDate()
方法中添加以下代码:
scope.val = selectedDate.format(scope.format);
并将 val
变量绑定到 input
元素:
<input type="text" ng-focus="showCalendar()" ng-model="val" .../>
这里是Plunker.
我试图在一个页面中使用多个指令,这样一次只能启用一个日期选择器,我尝试通过添加动态 class 但不知何故我需要双击输入框来隐藏另一个.让我知道我在这里做错了什么。
工作计划 - 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>'
};
}]);
我认为你应该继续这样做的方法是实现一个 myDatepickerService
来处理 hiding/showing 的所有逻辑并只保留一个日期选择器。
这里有一些 "pseudocode" 可以让您开始思考:
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
上的点击的所有代码都是 运行创建了新的日期选择器,更糟糕的是,每次点击到达 $document
时,它都会在 每个 单个日期选择器上 运行。通过这种方式,您可以将此代码与每个元素分开,并使其进入服务。
此外,为了有价值,我认为您应该将逻辑放入控制器而不是链接函数,但那是另一回事了!
有几种方法可以解决这个问题:
- 在指令
link()
函数中放置文档'click'
处理程序,以检查点击是否在当前指令元素内完成。 这基本上是您的解决方案,尽管可以使用 jQuery $.closest() 方法对其进行改进:https://api.jquery.com/closest/ - 使用更结构化的方法,并在另一个指令下聚合日历指令列表,该指令负责同步所有这些指令的
calendarOpened
标志。 这样的指令将监听所有日历指令的calendarOpened
值数组,并确保其中只有一个在发生变化后保持为真(日历为 opened/closed)
您可以使用mousedown
事件关闭日历:
$document.on('mousedown', function (e) {
scope.closeCalendar();
scope.$apply();
});
此外,您必须停止在日历本身触发的 mousedown
事件的传播,这样当您 select 一个日期时它不会自行关闭:
<div class="my-datepicker" ng-show="calendarOpened" ng-mousedown="$event.stopPropagation()">
为了能够将 selected 日期放入 input
,在 selectDate()
方法中添加以下代码:
scope.val = selectedDate.format(scope.format);
并将 val
变量绑定到 input
元素:
<input type="text" ng-focus="showCalendar()" ng-model="val" .../>
这里是Plunker.