Isolate scope variable is undefined - angularjs

For some reason I am not able to access my isolate scope variable "index" in my link function. I have tried both # and = to see if it made any difference and this had no effect, I also tried using a word other than index (in case it was already in use by angular or something) with the same result. Error msg: ReferenceError: index is not defined.
directive:
var jobernizeDirectives = angular.module('jobernizeDirectives', []);
jobernizeDirectives.directive('viewLargeContent', function() {
return {
scope: {
index: '=index'
},
require: 'ngModel',
link: function (scope, el, attrs, ngModel) {
console.log(index);
function openEditor() {
console.log(index);
var main_container = angular.element('.main_container');
var view_frame = angular.element('.view-frame');
console.log(ngModel);
main_container.append('<div class="dimmer"></div>');
main_container.append(
'<div class="large-content' + index + '">'
+ '<textarea>' + ngModel.$viewValue + '</textarea>'
+ '<button>Submit Changes</button>'
+ '</div>'
);
var dimmer = angular.element('.dimmer');
var content_container = angular.element('.large-content' + index);
// content_container.css({ marginTop: (content_container.height()/-2), width: view_frame.width(), marginLeft: (view_frame.width()/-2) })
content_container.css({ height: (0.8*main_container.height()), width: view_frame.width(), marginLeft: (view_frame.width()/-2) })
content_container.find('button').on('click', function() {
var new_content = content_container.find('textarea').get(0).value;
ngModel.$setViewValue(new_content);
content_container.hide();
dimmer.hide();
});
}
// el.on('click', openEditor);
el.on('click', openEditor);
}
}
});
html:
<div data-ng-controller="resumesController">
<div class="row">
<div class="small-12 columns">
<h2>Your Resumes</h2>
</div>
</div>
<div class="row">
<div class="small-12 columns">
<table>
<thead>
<tr>
<th>Name</th>
<th>Category</th>
<th>Date Added</th>
<th>View/Edit Resume</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="resume in resumes">
<td>{{ resume.name }}</td>
<td>{{ resume.category }}</td>
<td>{{ resume.date_added }}</td>
<td><button index="le" view-large-content data-ng-model="resume.content">View Resume</button></td>
</tr>
</tbody>
<table>
</div>
</div>
</div>

Index is an attribute of you scope object, so you access it like this:
scope.index
Both '#' or '=' will work, the difference is that '#' will interpolate the value and update the isolated scope when it changes and '=' means Angular should keep the attribute and the isolated scope in sync.
If the variables have the same name you can use the syntactic sugar
scope: {
index: '#'
},
Here is a JSBin with your code.

Related

Pass parameter outside of AngularJS directive into HTML

This is the problem: I want to sort a table by clicking on a table header. To avoid repeating code every , I have made a directive. The table headers show up nicely but I cannot find a way to make them sortable.
This is (part of) my HTML:
<div class="table-responsive" >
<table class="table table-striped" >
<thead>
<tr>
<th> </th>
<th><t-header name="vnaam" hoofd="voornaam" ></t-header></th>
<th><t-header name="anaam" hoofd="achternaam"></t-header></th>
</tr>
</thead>
<tbody>
<tr dir-paginate="cursist in homeCtrl.ckaart | orderBy: homeCtrl.sortKey:!homeCtrl.reverse" current-page="homeCtrl.currentPage">
<td><a ng-click="homeCtrl.gotoDetail(cursist.id)"><span ng-class="cursist.keuzemotivatie.length || cursist.leerdoelen.length ? 'infosign-true' : 'infosign-false'" class="fa fa-info-circle infosign"></span></a></td>
<td>{{cursist.vnaam}}</td>
<td>{{cursist.anaam}}</td>
</tr>
</tbody>
My directive:
.directive('tHeader', function(){
return {
restrict: 'EA',
scope: {
name: "#",
hoofd: "#"
},
templateUrl: 'templates/theader.html',
controllerAs: 'vm',
bindToController: true,
controller: function() {
var vm = this;
vm.sortKey = '-instrument';
// sort t.b.v. table sorting
vm.sort = function(keyname) {
// alert(keyname);
vm.sortKey = keyname;
vm.reverse = !vm.reverse;
console.log(vm.sortKey);
}
}
}
})
And the template belonging to the directive:
<div ng-click="vm.sort(vm.name)">{{vm.hoofd}}
<span ng-show="vm.sortKey === vm.name" ng-class="{ 'fa fa-chevron-up':vm.reverse, 'fa fa-chevron-down':!vm.reverse }"></span>
</div>
It seems to me that the 'vm.sortKey' parameter as well as the 'vm.reverse' parameter are not being passed into the HTML.
Anyone an idea? What mistake do I make?

How to spy on method in the scope that is not binded

I'm trying to test that the method "removePlayer" in the following component is called when removing a player with : spyOn(scope, 'removePlayer').and.callThrough(); and expect(scope.removePlayer).toHaveBeenCalled(); but got the following error : "Error: : removePlayer() method does not exist
Usage: spyOn(, )" how can I achieve that please?
.component("playerList", {
templateUrl: "src/js/player/player.html",
bindings: {
list: '<'
},
controller: function() {
this.removePlayer = function(index) {
console.log('Remove player with index : ', index);
if (index > -1) {
this.list.splice(index, 1);
}
};
}
});
With the following test:
beforeEach(inject(function($rootScope, $compile) {
scope = $rootScope.$new();
scope.list = angular.copy(playersList);
element = angular.element('<player-list list="list"></player-list>');
element = $compile(element)(scope);
scope.$apply();
}));
it('should render the text', function() {
// spyOn(scope, 'removePlayer').and.callThrough();
var title = element.find('h1');
var deleteBtn = element[0].querySelector('button');
expect(title.text()).toBe('This table contains ' + playersList.length + ' player.');
deleteBtn.click();
// expect(scope.removePlayer).toHaveBeenCalled();
expect(title.text()).toBe('This table contains ' + (playersList.length - 1) + ' player.');
});
here is the template :
<div class="container">
<h1 class- "player-table-title">This table contains {{ $ctrl.list.length }} players[![enter image description here][1]][1].</h1>
<table class="table table-striped">
<thead>
<tr>
<th>Name</th>
<th>Position</th>
<th>Team</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="player in $ctrl.list">
<td>{{ player.name }}</td>
<td>{{ player.position }}</td>
<td>{{ player.team }}</td>
<td>
<button type="button" class="btn btn-danger btn-sm" ng-click="$ctrl.removePlayer($index)">
<span class="glyphicon glyphicon-trash"></span>
</button>
</td>
</tr>
</tbody>
</table>
</div>
This is how the app look like in browser:
This method is controller method, not a scope. By the way, components don't have a scope.
Try to get controller of your component like this (don't forget to inject $componentController)
controller = $componentController('playerList', {$scope: scope});
And then replace scope to controller in spyOn function
spyOn(controller, 'removePlayer').and.callThrough();
....
expect(controller.removePlayer).toHaveBeenCalled();
I was able to resolve that by doing : element.controller('playerList');
it('should render the text', function() {
var component = element.controller('playerList');
spyOn(component, 'removePlayer').and.callThrough();
var title = element.find('h1');
var deleteBtn = element[0].querySelector('button');
expect(title.text()).toBe('Ce tableau contient ' + playersList.length + ' joueurs.');
deleteBtn.click();
expect(title.text()).toBe('Ce tableau contient ' + (playersList.length - 1) + ' joueurs.');
expect(component.removePlayer).toHaveBeenCalled();
});

Angular directive with no template - assign ng-click function from directive

How can I set the ng-click function on the element where my directive attribute is applied? After having issues with my directive template, because it spans multiple tr's, I resulted to using ng-repeat-start/end i.e., no template. So, my question is: what is the proper way to get the contents of the directive element into the isolate scope of the directive, so I can assign values/functions to it, as if it were the template?
// controller scope
<tr campaign-item ng-repeat-start="campaign in vm.allCampaigns.results | orderBy:vm.params.sort:vm.sortReverse track by $index" ng-if="vm.allCampaigns.totalCount > 0" campaign="campaign" close-preview="vm.hideCampaignPreview()" class="campaign-tr" id="campaign-{{campaign.id}}" ng-class="{'selected': showCampaignDetails}" user-role="{{vm.user.role}}" campaign-options="" campaign-detail="" show-campaign-details="">
<td>{{ campaign.name }}</td>
<td>{{ campaign.priority }}</td>
<td>{{ campaign.status }}</td>
<td>{{ campaign.createdBy }}</td>
<td>{{ campaign.approvedBy }}</td>
<td ng-show="item.releaseDate">{{ campaign.releaseDate * 1000 | date:'short' || ''}}</td>
<td ng-show="!item.releaseDate"></td>
<td ng-show="item.expirationDate">{{ campaign.expirationDate * 1000 | date:'short' }}</td>
<td ng-show="!item.expirationDate"></td>
<td>
<select id="campaign-options" name="campaignOptions" class="form-control" ng-model="selectedCampaignOption" ng-options="option for option in campaignOptions track by $index">
</select>
</td>
</tr>
<tr class="campaign-description campaign-{{campaign.id}}" ng-show="showCampaignDetails" ng-class="{'selected': showCampaignDetails}">
<td colspan="8">
<p> {{ campaignDetails.description }}</p>
</td>
</tr>
// directive
angular.module('fotaAdminPortal')
.directive('campaignItem', ['vsmsCampaignFactory', function (vsmsCampaignFactory) {
return {
restrict: 'A',
transclude: true,
scope: {
campaign: '=',
closePreview: '&',
userRole: '#',
campaignOptions: '=?',
showPreview: '=?',
showCampaignDetails: '=?',
campaignDetail: '=?'
},
// templateUrl: 'app/vsms/admin/campaign/campaign.tpl.html',
link: function(scope, element, attr, ctrl, transclude) {
scope.showCampaignDetails = false;
transclude(scope, function(clone) {
element.append(clone);
element[0].addEventListener('click', function(e) {
if(e.target.id == 'campaign-options') {
return;
} else {
vsmsCampaignFactory.getCampaign(scope.campaign.id)
.then(function(response) {
scope.showCampaignDetails = true;
scope.campaignDetails = response;
console.log(scope);
});
}
})
});
...
You might add ng-click from directive like so: (If you'd provide a fiddle or plnkr would be easier for us to test)
element.attr("ng-click", "onClick()");
If you have the onClick() function on controller add controllerName.onClick()
element.attr("ng-click", "vm.onClick()");
Then at the end
$compile(element.contents())(scope);
or
$compile(element)(scope);
Another option is
element.bind('click', function(e) {
// Handle onclick event here.
});

AngularJS pass function with dynamic parameter to directive

TL;DR version: I'm trying to write a directive to show various objects in a table, and add an edit / delete button at the end of the table rows.
The example entity will be ProjectType, so:
project-type.controller.js
// determines which attributes we want to show in the table
$scope.attr = [
'computerName',
'shortName',
'longName'
];
// puts the current projectType object to the form
$scope.edit = function(projectType) {
$scope.projectType = projectType;
$scope.computerName = projectType.computerName;
$scope.isEdit = true;
};
// shows a delete modal window
$scope.deleteConfirm = function (projectType) {
Modal.confirm.deleteModal('Project type', projectType.computerName, function () {
Message.createMessage('project-type.message.delete.success', { projectType: projectType.computerName }, { type: 'warning', timeout: 4000 });
$scope.remove(projectType);
})();
};
project-type.html
<!-- show table with directive -->
<entity-table object="projectTypes" entity="'project-type'" attr="attr"></entity-table>
<!-- show table without directive -->
<table class="table table-responsive">
<thead>
<tr>
<th>{{ 'project-type.create.label.computerName' | translate }}</th>
<th>{{ 'project-type.create.label.shortName' | translate }}</th>
<th>{{ 'project-type.create.label.longName' | translate }}</th>
<th>{{ 'common.operations' | translate }}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="projectType in projectTypes">
<td><strong>{{ projectType.computerName }}</strong></td>
<td>{{ projectType.shortName }}</td>
<td>{{ projectType.longName }}</td>
<td>
<a ng-click="edit(projectType)" class="pencil"><span class="glyphicon glyphicon-pencil"></span></a>
<a ng-click="deleteConfirm(projectType)" class="trash"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</tbody>
</table>
entity-table.directive.js
angular.module('unioffice-centralApp')
.directive('entityTable', function () {
return {
restrict: 'E',
replace: true,
transclude : true,
templateUrl: '/components/directives/entity-table/entity-table.html',
scope: {
object: '=',
entity: '=',
attr: '='
},
link: function (scope, element, attrs, controllers, transclude) {
console.log(scope);
}
};
});
entity-table.html
<div>
<table class="table table-responsive">
<thead>
<tr>
<th ng-repeat="att in attr">{{ (entity + ".table.label." + att) | translate }}</th>
<th ng-repeat-end translate>common.operations</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="obj in object">
<td ng-repeat="att in attr">{{ obj[att] }}</td>
<td ng-repeat-end>
<a ng-click="edit(obj)" class="pencil"><span class="glyphicon glyphicon-pencil"></span></a>
<a ng-click="deleteConfirm(obj)" class="trash"><span class="glyphicon glyphicon-trash"></span></a>
</td>
</tr>
</tbody>
</table>
(there is a /div at the end of the entity-table.html, stackoverflow seems to kill it)
So the question is: how do I pass the edit() and deleteConfirm() functions to the directive to make them work?
In this picture you can see the two tables are the same, but the buttons in the first one obviously won't do anything at this point: (also I know the second one has bold first column, it's ok, that's not the point now)
Passing/binding the functions from the controller to the directive is done in the same manner as binding an object = or a string # with the difference being how it's referenced in the directive
Pass in the functions as attributes on the directive as shown below ..
<entity-table object="projectTypes" entity="'project-type'" on-delete="deleteConfirm" on-edit="edit" attr="attr"></entity-table>
In your directive do this ..
angular.module('unioffice-centralApp')
.directive('entityTable', function () {
return {
restrict: 'E',
replace: true,
transclude : true,
templateUrl: '/components/directives/entity-table/entity-table.html',
scope: {
object: '=',
entity: '=',
attr: '=',
onDelete: '&', // function referencing
onEdit: '&' // function referencing
},
link: function (scope, element, attrs, controllers, transclude) {
scope.deleteFn = function (obj) {
scope.onDelete()(obj); // this invokes the deleteConfirm function in the controller
}
scope.editFn = function (obj) {
scope.onEdit()(obj); // this invokes the deleteConfirm function in the controller
}
}
};
});
In your controller ...
$scope.edit = function(projectType) {
$scope.projectType = projectType;
$scope.computerName = projectType.computerName;
$scope.isEdit = true;
};
// shows a delete modal window
$scope.deleteConfirm = function (projectType) {
Modal.confirm.deleteModal('Project type', projectType.computerName, function () {
Message.createMessage('project-type.message.delete.success', { projectType: projectType.computerName }, { type: 'warning', timeout: 4000 });
$scope.remove(projectType);
})();
};
PLUNKR

Angular directive showing in the wrong place

I have the following page
<body ng-controller="myController">
<table>
<tbody>
<tr ng-repeat="r in rows">
<div show-row></div>
</tr>
</tbody>
</table>
</body>
with the following Angular module
ng.module('myModule', [ ])
.controller('myController', ['$scope', function ($scope) {
$scope.rows = [a non-empty array of values]
}]).directive('showRow', function () {
return {
replace: true,
template: '<td>Hello!</td>'
};
});
My problem is that Hello! is shown once before the table and not, as I would expect, once for every row of the table.
I guess I have to use the scope property for the directive, but how?
The problem is with div which cannot be a children to tr. Change the div to td.
<body ng-controller="myController">
<table>
<tbody>
<tr ng-repeat="r in rows">
<td show-row></td>
</tr>
</tbody>
</table>
</body>
I'm having a similar problem with the following layout:
<table>
<table-header data="headers" order="order"></table-header>
<tbody>
<tr ng-repeat="...">
...
</tr>
</tbody>
</table>
The tableHeader directive looks like this:
.directive('tableHeader', [function () {
return {
restrict: 'E',
replace: true,
scope: {
data: '=',
order: '='
},
template: '\
<thead><tr> \
<th ng-repeat="header in ::data" \
ng-click="orderBy(header)"> \
{{:: header.name }} \
</th> \
</tr></thead>',
link: function (scope, elem, attrs) {
scope.orderBy = function (header) {
if (scope.order.by === header.prop) {
scope.order.reverse = !scope.order.reverse;
} else {
scope.order.by = header.prop;
}
};
}
};
Weird enough, the thead element ends up out of the table, instead of inside.
Edit: seems to be documented in https://github.com/angular/angular.js/issues/7295.

Resources