Show directive one at a time AngularJS - javascript

I am trying to use multiple directive in one page such that only one datepicker should be enable at one time, I tried by adding dynamic class but somehow I need to double click in input box to hide another. Let me know what I am doing wrong here.
Working Plnkr - http://plnkr.co/edit/ZcJr9zg9PeUeRX4kRhbW?p=preview
HTML Code -
<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 Code -
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>'
};
}]);

You can use the mousedown event to close the calendar:
$document.on('mousedown', function (e) {
scope.closeCalendar();
scope.$apply();
});
Additionally you would have to stop the propagation of the mousedown event triggered on the calendar itself so it doesn't close itself when you select a date:
<div class="my-datepicker" ng-show="calendarOpened" ng-mousedown="$event.stopPropagation()">
To be able to put the selected date into the input, have the following code in selectDate() method:
scope.val = selectedDate.format(scope.format);
and bind the val variable to the input element:
<input type="text" ng-focus="showCalendar()" ng-model="val" .../>
Here the Plunker.

There are a few approaches to solve this problem:
Put a document 'click' handler inside the directive link() function, to check if the click was done inside the current directive element or not.
This is basically your solution, although it can be improved by using jQuery $.closest() method: https://api.jquery.com/closest/
Use a more structured approach, and aggregate the list of calendar directives under another directive, that is responsible for synchronizing the calendarOpened flag for all of them.
Such a directive would listen to the array of calendarOpened values for all the calendar directives, and make sure only one of them stays true after there is a change (a calendar is opened/closed)

The way I think you should go forward with this is to implement a myDatepickerService which handles all of the logic of hiding/showing and keeping only a datepicker.
Here's some "pseudocode" to get your mind started:
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;
};
});
Then on your directive:
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);
}
};
});
EDIT: Adding more into why I think this is the good way to go.
So, IMHO you're putting a lot of responsibility on each datepicker and it's also not efficient, since all the code that listens for the click on the $document is run everytime that a new datepicker is created and, what's even worst is that everytime a click reaches the $document it will run on every single datepicker. In this way, you're separating this code from every element and making it go to the Service.
Also, for what is worth, I think you should put the logic into the controller instead of the linking function but that's another story!

Related

Communicate beween AngularJs filter() to AngularJs directive()

angular.module('myApp')
.filter('projectTeamPhotos', function($compile) {
return function(teams, element) {
if (teams.length == 0) {
return '';
}
var el = pageUrl; /* pageUrl global variable */
var html = '';
for (var i = 0; i < teams.length; i++) {
var url = el + 'files/image/' + teams[i].User.image;
var link = el + '#/employee/view-details/' + teams[i].User.unique_id;
var name = teams[i].User.name;
html += '<img check-image alt="image" class ="img-circle" src="' + url + '" />';
}
return html;
};
}).directive('checkImage', function($http) {
return {
restrict: 'A',
link: function(scope, element, attrs) {
attrs.$observe('ngSrc', function(ngSrc) {
$http.get(ngSrc).success(function() {
alert('image exist');
}).error(function() {
alert('image not exist');
element.attr('src', 'http://upload.wikimedia.org/wikipedia/commons/7/73/Lion_waiting_in_Namibia.jpg'); // set default image
});
});
}
};
});
HTML
<td ng-bind-html="project.ProjectTeam | projectTeamPhotos"></td>
See screenshot:
Here I have posted my filter and directive code. I want to access checkImage directive in my projectTeamPhotos filter.
I have called check-image directive in my image tag which content in the projectTeamPhotos filter. But my check-image directive not working. Please help me and say me how to access my directive in my filter?
JSFIDDLE

How to change attribute on a selected directive from a controller

I build an application with interactive SVG map with angular.js
Whole app controlled by controller, svg wrapped in a directive, and every <path> in svg is a partial directive too.
When user clicked on a wall, <path> id is memorized in a custom service and next when user select a color, I need to fill this wall with that color.
The problem is that I can't access directive within a controller. I also cant share a scope with a directive because i have multiple nested directives in my controller and want to access them by id.
Maybe there is a design mistake? how can I achieve this workflow?
There is my code for controller, service and directives
ColorPicker Controller
app.controller("ColorPicker", function($scope, $timeout, appConfig, ColorService) {
$scope.colors = [];
$scope.currentColorSet = 0;
$scope.svgTemplateUrl = appConfig.arSvg[$scope.$parent.roomType.id][$scope.$parent.roomStyle.id] + 'over.svg';
$scope.maxColorSet = Math.max.apply(Math, jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors.map(function(color) {
return color.color.group;
}));
$scope.sliderConfig = {
min: 0,
max: $scope.maxColorSet,
step: 1
}
$scope.emptyColors = ( ColorService.getColors().length === 0 );
$scope.currentProps = {};
$scope.applyColor = function(color) {
console.log($scope.currentProps);
//**here I need to set fill with selected color to selected directive**
}
$scope.prevColorSet = function(){
if($scope.currentColorSet > 0) {
$scope.currentColorSet--;
generateColorSet($scope.currentColorSet);
}
}
$scope.nextColorSet = function(){
if($scope.currentColorSet < $scope.maxColorSet) {
$scope.currentColorSet++;
generateColorSet($scope.currentColorSet);
}
}
generateColorSet = function(setIndex){
$scope.colors = [];
for(var i = 0; i < jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors.length; i++){
if(jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors[i].color.group == setIndex) {
$scope.colors.push(jsondata.roomtype[$scope.$parent.roomType.id].data.roomstyle[$scope.$parent.roomStyle.id].colors[i].color)
}
}
}
generateColorSet($scope.currentColorSet);
$scope.$watch("currentColorSet", function(newValue, oldValue) {
generateColorSet($scope.currentColorSet);
});
});
Directive for whole SVG
app.directive('svgmap', ['$compile', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var svg = $(element).find('svg');
svg.removeAttr('xmlns:a');
svg.attr('width', '869px');
svg.attr('height', '489px');
element.append(svg);
var regions = element[0].querySelectorAll('path');
angular.forEach(regions, function (path, key) {
var regionElement = angular.element(path);
regionElement.attr("region", "");
$compile(regionElement)(scope);
});
},
}
}]);
Directive for region (wall)
app.directive('region', ['$compile','ColorService','$timeout', function ($compile, ColorService, $timeout) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.properties = {
elementId : element.attr("id"),
stroke: '',
fill: '#fff'
};
scope.regionClick = function () {
scope.currentProps = scope.properties;
if(ColorService.selectedRegion != scope.properties.elementId) {
scope.properties.stroke = '#e5514e';
ColorService.selectedRegion = scope.properties.elementId;
} else {
scope.properties.stroke = '';
ColorService.selectedRegion = '';
}
console.log('click: ' + scope.properties.elementId + ' : ' + scope.properties.stroke);
};
scope.setStroke = function () {
scope.stroke = '#e5514e';
};
scope.removeStroke = function() {
if(ColorService.selectedRegion != scope.properties.elementId) {
scope.properties.stroke = '';
}
//console.log('enter: ' + scope.elementId + ' : ' + scope.stroke);
};
scope.removeColor = function() {
console.log('removed');
scope.properties.fill = '#fff';
ColorService.remove(scope.properties.elementId);
};
ColorService.getColors().forEach(function(item) {
if(item.id === scope.properties.elementId) {
scope.properties.fill = item.info.val;
}
});
element.attr("ng-click", "regionClick()");
element.attr("ng-attr-stroke", "{{properties.stroke}}");
element.attr("ng-attr-fill", "{{properties.fill}}");
element.attr("ng-mouseenter", "setStroke()");
element.attr("ng-mouseleave", "removeStroke()");
element.attr("ng-right-click", "removeColor()");
element.after('<rect width="300" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />');
element.removeAttr("region");
$compile(element)(scope);
}
}
}]);

ng-pattern not working inside directive

I'm trying to wrap an <input> in a directive so that I can handle date validation and convert it from a string to an actual Date object and maintain the Date version in the original scope. This interaction is working as expected. But the ng-pattern on the <input> element isn't acting right. It is never invalidating the <input>, regardless of what is entered.
HTML
<pl-date date="date"></pl-date>
JS
.directive("plDate", function (dateFilter) {
return {
restrict: 'E',
replace: true,
template: '<input id="birthDateDir" name="birthDate" type="text" ng-pattern="{{getDatePattern()}}" ng-model="dateInput">',
scope: {
date: '='
},
link: function (scope) {
scope.dateInput = dateFilter(scope.date, 'MM/dd/yyyy');
scope.$watch('date', function (newVal) {
if (newVal !== scope.tmp) {
if (!newVal) {
scope.dateInput = null;
} else {
scope.dateInput = dateFilter(scope.date, 'MM/dd/yyyy');
}
}
});
scope.getDatePattern = function () {
var exp = '/';
// Removed for brevity
exp += '/';
return exp;
};
scope.$watch('dateInput', function (newVal) {
if (newVal !== null) {
scope.date = new Date(newVal);
scope.tmp = scope.date;
}
});
}
};
JSFiddle here: https://jsfiddle.net/e5qu5rgy/1/
Any help at all is greatly appreciated!
So it looks like the problem can be fixed by changing the link function for the directive to be a controller function instead, as follows
.directive("plDate", function (dateFilter) {
return {
restrict: 'E',
replace: true,
template: '<input id="birthDateDir" name="birthDate" class="formField" type="text" ng-pattern="{{getDatePattern()}}" ng-model="dateInput">',
scope: {
date: '='
},
controller: function ($scope, $element, $attrs) {
$scope.dateInput = dateFilter($scope.date, 'MM/dd/yyyy');
$scope.$watch('date', function (newVal) {
if (newVal !== $scope.tmp) {
if (!newVal) {
$scope.dateInput = null;
} else if (newVal.toString() !== "Invalid Date") {
$scope.dateInput = dateFilter($scope.date, 'MM/dd/yyyy');
}
}
});
$scope.getDatePattern = function() {
var exp = '/';
// Months with 31 days
exp += '^(0?[13578]|1[02])[\/.](0?[1-9]|[12][0-9]|3[01])[\/.](18|19|20)[0-9]{2}$';
//Months with 30 days
exp += '|^(0?[469]|11)[\/.](0?[1-9]|[12][0-9]|30)[\/.](18|19|20)[0-9]{2}$';
// February in a normal year
exp += '|^(0?2)[\/.](0?[1-9]|1[0-9]|2[0-8])[\/.](18|19|20)[0-9]{2}$';
// February in a leap year
exp += '|^(0?2)[\/.]29[\/.](((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)$';
exp += '/';
return exp;
};
$scope.$watch('dateInput', function (newVal) {
if (newVal !== null) {
$scope.date = new Date(newVal);
$scope.tmp = $scope.date;
}
});
}
};
});
Before going into production, the controller needs to be changed over to use an array for its arguments to protect against minification.

Angularjs: How to pass function into compile

I have the following directive:
app.directive('pagedownAdmin', ['$compile', '$timeout', function ($compile, $timeout) {
var nextId = 0;
var converter = Markdown.getSanitizingConverter();
converter.hooks.chain("preBlockGamut", function (text, rbg) {
return text.replace(/^ {0,3}""" *\n((?:.*?\n)+?) {0,3}""" *$/gm, function (whole, inner) {
return "<blockquote>" + rbg(inner) + "</blockquote>\n";
});
});
return {
restrict: "E",
scope: {
content: "=",
modal: '=modal'
},
template: '<div class="pagedown-bootstrap-editor"></div>',
link: function (scope, iElement, attrs) {
var editorUniqueId;
if (attrs.id == null) {
editorUniqueId = nextId++;
} else {
editorUniqueId = attrs.id;
}
scope.hideDiv = function () {
document.getElementById("wmd-button-bar-" + editorUniqueId).style.display = 'none';
};
scope.showDiv = function () {
document.getElementById("wmd-button-bar-" + editorUniqueId).style.display = 'block';
};
scope;
var newElement = $compile(
'<div>' +
'<div class="wmd-panel">' +
'<div data-ng-hide="modal.wmdPreview == true" id="wmd-button-bar-' + editorUniqueId + '" style="display:none;"></div>' +
'<textarea on-focus="showDiv()" on-blur="hideDiv()" data-ng-hide="modal.wmdPreview == true" class="wmd-input" id="wmd-input-' + editorUniqueId + '" ng-model="content" >' +
'</textarea>' +
'</div>' +
'<div data-ng-show="modal.wmdPreview == true" id="wmd-preview-' + editorUniqueId + '" class="pagedownPreview wmd-panel wmd-preview">test div</div>' +
'</div>')(scope)
;
iElement.append(newElement);
var help = angular.isFunction(scope.help) ? scope.help : function () {
// redirect to the guide by default
$window.open("http://daringfireball.net/projects/markdown/syntax", "_blank");
};
var editor = new Markdown.Editor(converter, "-" + editorUniqueId, {
handler: help
});
var editorElement = angular.element(document.getElementById("wmd-input-" + editorUniqueId));
editor.hooks.chain("onPreviewRefresh", function () {
// wire up changes caused by user interaction with the pagedown controls
// and do within $apply
$timeout(function () {
scope.content = editorElement.val();
});
});
editor.run();
}
}
}]);
Inside I have the showDiv and hideDiv function that would show and hide the page editor's menu when I click in and out of the textarea.
I am passing the functions to an event inside the compile:
//First try
<textarea onfocus="showDiv()" onblur="hideDiv()"></textarea>
When I click inside and outside the textarea I get the errors:
Uncaught ReferenceError: on is not defined
Uncaught ReferenceError: off is not defined
//Second try
<textarea on-focus="showDiv()" on-blur="hideDiv()"></textarea>
When I click in and out of textarea nothing is happening. No errors but not calling the function either.
Can anyone point me to the right direction? Thanks
Instead of using the same scope, instantiate a new scope (scope.$new()) and assign the functions to this new scope. Because otherwise you will override the function-references assigned by the scope-declaration to the scope-object.
var newScope = scope.$new();
newScope.hideDiv = function() {...};
newScope.showDiv = function() {...};
...
var newElement = $compile(...)(newScope);
And to use the function-references given to the original scope (of the directive) you can call those in the new-scopes functions (e.g. function() { scope.hideDiv(); }).
Working plnkr:
http://plnkr.co/edit/nGux3DOsrcPBcmz43p2A?p=preview
https://docs.angularjs.org/api/ng/service/$compile
https://code.angularjs.org/1.3.16/docs/api/ng/type/$rootScope.Scope#$new
Thank you guys for trying to help. I have found what was wrong with my code. I did a very silly/noob mistake. I used on-focus instead of ng-focus and on-blur instead of ng-blur.

Directive that run after ng-repeat

I have similar problem with this one Calling a function when ng-repeat has finished but I have directive and ng-repeat inside.
<my-dropdown>
<li ng-repeat="item in items"><a>{{item.text}}</a></li>
</my-dropdown>
In my dropdown I have ng-transclude in two places (one for a list and one for caption) and I need to add class ng-hide to all items except one using jQuery. So I need to have the code that will run after ng-repeat. I've try to set priority in my directive to 2000 or 0 (ngRepeat have 1000) but this doesn't work. I've got one item when I run element.find('li');
return {
restrict: 'E',
require: '?ngModel',
template: ['<div class="btn-group dropdown-button">',
' <div class="btn caption" ng-transclude></div>',
' <button class="btn dropdown-toggle" data-toggle="dropdown">',
' <span class="caret"></span>',
' </button>',
' <ul class="dropdown-menu" ng-transclude></ul>',
'</div>'].join('\x0D'), // newline - peach replace newlines, gods only know why
transclude: true,
replace: true,
compile: function(element, attrs, transclude) {
// I've used compile because I wante to test the transclude function
return function(scope, element, attrs, ngModelCtrl) {
element.find('.caption li').attr('ng-hide', 'true');
var selected_index = 0;
function setValue(item) {
var value = item.attr('value');
ngModelCtrl.$setViewValue(value ? $interpolate(value)(scope) : item.index());
}
var caption = element.find('.caption');
function update() {
// for model with ng-repeat it return 1 item
console.log(attrs.ngModel + ' ' + caption.find('li').length);
caption.find('li').removeClass('ng-hide').not(':eq(' + selected_index + ')').addClass('ng-hide');
}
if (ngModelCtrl) {
element.on('click', 'ul li', function() {
var self = $(this);
selected_index = self.index();
scope.$apply(function() {
setValue(self);
});
var selected = self.attr('selected');
if (selected) {
scope.$eval(selected);
}
});
if (!ngModelCtrl.$viewValue && attrs.placeholder) {
$('<li>' + attrs.placeholder + '</li>').appendTo(caption);
selected_index = caption.find('li').length-1;
} else {
selected_index = ngModelCtrl.$viewValue || 0;
}
setValue(element.find('ul li:eq(' + selected_index + ')'));
ngModelCtrl.$viewChangeListeners.push(function() {
scope.$eval(attrs.ngChange);
update();
});
ngModelCtrl.$render = function() {
if (!ngModelCtrl.$modelValue) {
selected_index = 0;
update();
} else {
$(element).find('ul li').each(function(i) {
var self = $(this);
var value = self.attr('value');
if (value && ngModelCtrl.$modelValue == $interpolate(value)(scope) ||
ngModelCtrl.$modelValue == i) {
selected_index = i;
update();
return false;
}
});
}
};
}
update();
};
}
}

Categories