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
I'm experiencing problems with the Jasmine (ver. 2.4) JavaScript testing framework.
I'm creating a course on udemy.com, but the online checker won't accept the last part of my code, where I'm using the "expect(variable).toBe(true);" function of Jasmine.
describe('A div with class', function() {
var parent = document.getElementsByClassName('media')[0];
it('"media" should exist.', function() {
expect(parent).toBeDefined();
});
var child = document.getElementsByClassName('media-body')[0];
it('"media-body" should exist.', function() {
expect(child).toBeDefined();
});
});
describe('A div with class "media"', function() {
var parent = document.getElementsByClassName('media')[0];
var child = document.getElementsByClassName('media-body')[0];
var answer;
if (parent.firstChild == child) {
answer = true;
} else {
answer = false;
}
it('should have a div with class "media-body" as its child.', function() {
expect(answer).toBe(true);
});
});
My test output from the udemy.com page is:
"A div with class "media" should have a div with class "media-body" as its child.
Expected false to be true"
My updated code which now works very well:
describe('A div with the class', function() {
var class1 = 'media';
var failClass1 = '"' + class1 + '"' + ' should exist.'
var class2 = 'media-body';
var failClass2 = '"' + class2 + '"' + ' should exist.'
var element1 = null;
var element2 = null;
var relationship1_2 = null;
var failRelationship = '"' + class2 + '"' + ' should be nested in a div with the class "' + class1 + '".'
beforeEach(function() {
element1 = document.getElementsByClassName(class1)[0];
element2 = document.getElementsByClassName(class2)[0];
relationship1_2 = element1.contains(element2);
})
it(failClass1, function() {
expect(element1).toBeDefined();
});
it(failClass2, function() {
expect(element2).toBeDefined();
});
it(failRelationship, function() {
expect(relationship1_2).toBe(true);
});
});
I'd recommend re-organize it using beforeEach blocks as the following example:
describe('testing media tag hierarchy', function() {
var parent = null;
beforeEach(function() {
parent = document.getElementsByClassName('media')[0];
})
it('"media" should exist.', function() {
expect(parent).toBeDefined();
});
it('should have a div with class "media-body" as its child.', function() {
expect(parent.firstChild.hasClass('media-body')).toBeTruthy();
});
});
I'm not sure this will fix your problem but it might... it's easier to read.
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!
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();
};
}
}
How do you test an angularjs directive's template function's attributes where the attributes have both functions and angular binding values that are defined in the link function?
This is the directive.
var app = angular.module('mmApp', []);
app.directive('mmField', function(){
return {
'restrict': 'E',
'priority': 5,
'replace': true,
'scope': {
'path': '#',
'label': '#',
'type': '#',
'editable': '#'
},
//this is the template function and this is where labelText() does not evaluate at least not where I test it.
'template': '<div class="mm-field">' +
'<label for="{{inputId()}}" ng-show="labelText()">{{labelText()}}</label> ' +
'</div>',
'link': function (scope, element, attrs) {
var query = null;
//this is where the labelText() function is defined
scope.labelText = function () {
var labelAttrValue = (scope.label || attrs['withLabel'] || '');
// cater for custom labels specified via the label or with-label attribute
if (labelAttrValue && labelAttrValue.toLowerCase() !== 'true' && labelAttrValue.toLowerCase() !== 'false') {
return (labelAttrValue || '') + ':';
} else if (labelAttrValue.toLowerCase() !== 'false' && scope.field) {
return (scope.field['name'] || 'FIELD_NAME_NOT_DEFINED') + ':';
} else if (labelAttrValue.toLowerCase() == 'false') {
return '';
} else {
return 'Loading...';
}
};
}
])
This is where the directive is on a html page.
<mm-field with-label editable="false" path="{{rootPath}}.name"></mm-field>
I am using mocha and chai test suite. This is how I want to test it.
describe('InputId', function () {
it.only('should generate an ID', function () {
var element = $($compile('<mm-field with-label="MONKEY" editable="false" path="something.name"></mm-field>' +
'</div>')($scope));
$scope.$digest();
expect(element.find('label').attr('ng-show')).to.equal('an evaluated value of labelText()');
});
});