I have a nested parent - child directives, the purpose if to draw a table..
The child directive is not getting called when called from within the parent (tag).
It works fine when tested independently.. I seems to have followed all the rules/syntax, require is in place.. I don't see the console logs statements I have in the child directive, also there are no errors in the log.
Directives -
var app = angular.module ('gridapp', []);
app.directive ('gridControl', function(tableDataFactory){
return {
restrict: 'E',
scope : {},
controller : function($scope){
$scope.columns = [];
$scope.column = [];
$scope.addColumnProperties = function(columnProperties) {
console.log("In addColumnProperties "+ columnProperties);
$scope.column = columnProperties;
$scope.columns.push($scope.column);
$scope.column = [];
}
},
link : function (scope, element, attrs) {
console.log(attrs.source);
tableDataFactory
.get(
'http://localhost:8000/AngularTableWidget/json/accounts.json')
.then(
function(data) {
scope.items = data.items;
console.log("In grid directive" + scope.items);
});
},
templateUrl : '../template/gridtemplate.html'
};
});
//child directive...
app.directive('tableColumn', function(){
return{
restrict : 'E',
scope : {},
require : '^gridControl',
link : function(scope, element, attrs, gridCtrl) {
console.log("In tablecolumn "+ attrs.source);
var colProp = [];
console.log("In tablecolumn "+ attrs.caption);
colProp.push(attrs.caption);
colProp.push(attrs.source);
gridCtrl.addColumnProperties(colProp);
}
};
});
HTML -
<div>
<grid-control source="gridtable.json">
<table-column caption="Name" source="name"> </table-column>
<table-column caption="Account" source="account"> </table-column>
</grid-control>
template -
<div>
<table>
<tbody ng-repeat="row in items track by $index">
<tr ng-repeat ="col in columns">
<td>
Test
</td>
</tr>
</tbody>
</table>
</div>
On grid-control directive, add transclude = true. Inside the grid-control template, add ng-transclude where ever the child directive going to be inserted. Without using transclude, the system will ignore the child directive.
I hope this helps.
Austin
Related
I'd like to load my JavaScript class after the end of ng-repeat.
AngularJS version : 1.6.4
My JavaScript class :
+(function($) {
'use strict';
document.addEventListener('DOMContentLoaded', function() {
var buttons = document.querySelectorAll('[data-dialog="true"]');
console.log(buttons.length); // return 0
});
});
My AngularJS view :
<tr ng-repeat="item in filteredItems">
<td>{{item.title}}</td>
<td>
<a href="/page/duplic/{{item.id}}" data-dialog="true"
data-dialog-action-title="duplic item">file_copy</a>
</td>
</tr>
Issue : My JS class is loaded before the AngularJS render, and the console.log() return no elements.
Inject the code with a custom directive:
app.directive("dialog",function() {
return {
link: postLink
};
function postLink(scope, elem, attrs) {
var button = elem;
console.log(attrs.dialogActionTitle);
if (attr.dialog == "true") {
//script here
};
}
})
The postLink function will be invoke by the $compile service after the ng-repeat directive appends the element to the DOM.
For more information, see
AngularJS Developer Guide - Creating Custom Directives
There is no such thing as an event for when ng-repeat finished. But angularJS creates the following attributes:
$first: true
$last: false
$middle: false
$even: true
$odd: false
You can check it using $last and a custom directive as following:
HTML
<div ng-repeat="item in filteredItems" isitthelastone>
{{item.name}}
</div>
APP.JS
.directive('isitthelastone', function() {
return function(scope, element, attrs) {
if (scope.$last){
alert('event triggered');
}
};
})
I´m trying to do a parent directive with 2 child directives.
The main reason to do this is to force that all the tables in the application use the same "template".
My problem here is that i need to copy the Dom inside the directives.
So my main directive is the table directive, and the frist child is the thead and the second child is the tbody.
This is the markup
<div my-table >
<my-thead>
<tr>
<th ng-click="alert('Id')"> Id </th>
<th ng-click="alert('Name')"> Name </th>
</tr>
</my-thead>
<my-tbody>
<tr ng-repeat ="item in List">
<td>{{item.Id}}</td>
<td>{{item.Name}}</td>
</tr>
</my-tbody>
</div>
The directive my-table wil generate all the code necessary for the struct of a table while the dir my-thead and my-tbody will create the tags thead and tbody and give them to do parent div.
My problem remains on copying the inner markup of my-thead and my-tbody since they are tr, any suggestion?
Next i present to you the most recent code i get so far with a plunker
myTable
angular
.module('App')
.directive('myTable', ['$compile',
function($compile){
var tableSettings = {};
return {
restriction :'AE',
scope : {},
replace : false,
link : function(scope,element){
var divContainerFluid = angular.element('<div>');
var divRowTable = angular.element('<div>');
var divColTable = angular.element('<div>');
divContainerFluid.addClass('container-fluid');
divRowTable.addClass('Row');
divColTable.addClass('col-lg-12');
var divTable = angular.element('<div>');
var table = angular.element('<table>');
table.addClass('table');
table.addClass('table-striped');
//add from thead
table.append(tableSettings.tHead);
//add from tbody
table.append(tableSettings.tBody);
divTable.append(table);
divColTable.append(divTable);
divRowTable.append(divColTable);
divContainerFluid.append(divRowTable);
$compile(divContainerFluid)(scope);
element.append(divContainerFluid);
},
controller: function () {
this.receiveTHead = function (tHead) {
tableSettings.tHead = tHead;
}
this.receiveTBody = function (tBody) {
tableSettings.tBody = tBody;
}
}
}
}]);
myHead
angular
.module('App')
.directive('myThead', [
function() {
return {
restriction: "E",
scope: true,
require: "^myTable",
replace: true,
transclude: true,
template:'<thead class="form-class" ng-cloak>' +
'</thead>',
link: function(scope, element, attrs, myTableCtrl, transclude)
{
transclude(function(clone){
element.append(clone);
});
myTableCtrl
.receiveTHead(element);
}
};
}
]);
myBody
angular
.module('App')
.directive('myThead', [
function() {
return {
restriction: "E",
scope: true,
require: "^myTable",
replace: true,
transclude: true,
template:'<thead class="form-class" ng-cloak>' +
'</thead>',
link: function(scope, element, attrs, myTableCtrl, transclude)
{
transclude(function(clone){
element.append(clone);
});
myTableCtrl
.receiveTHead(element);
}
};
}
]);
Plunker
Thanks, for the help.
UPDATE
missing listCtrl
angular.module('App',[])
.controller('listCtrl',['$scope','$timeout',
function($scope,$timeout){
$timeout(function () {
$scope.List = [ {Id : '1', Name : 'Name1'}, {Id : '2', Name :'Name2'}];
}, 1500);
}
]);
UPDATE
I recreated the problem from a different perspective. But the body doesn't get populated with the info in the scope.List, any ideas?
<div my-table>
<table>
<thead>
<tr>
<th ng-click="resort('Id')"> Id </th>
<th ng-click="resort('Name')"> Name </th>
</tr>
</thead>
<tbody>
<tr ng-repeat="user in List">
<td>{{user.Id}}</td>
<td>{{user.Name}}</td>
</tr>
</tbody>
</table>
</div>
myTable
angular
.module('App')
.directive('myTable', [
'$compile',
function($compile) {
return {
restriction: 'AE',
//scope: {
// plEmptytext: '#'
//},
transclude: true,
template:
'<div class="container-fluid">' +
'<div class="row">' +
'<div class="col-lg-12">' +
'<div ng-transclude></div>' +
'</div>' +
'</div>' +
'</div>',
link: function(scope, element) {
var table = element.find('table');
table.addClass('table');
table.addClass('table-striped');
var tHead = element.find('thead');
var tBody = element.find('tbody');
tBody.attr('ng-controller', 'listCtrl');
$compile(tHead)(scope);
$compile(tBody)(scope);
$compile(table)(scope);
}
};
}
]);
the listCtrl is the same
Plunker 2
SOLUTION:
For the ones who might come here my solution has to add to the directive tBody return{ controller: 'listCtrl' ...
A different approach that might still provide your desired "one template for all" goal would be writing an html template file, and referencing it whenever a table is used by declaring an ng-include. Similar to the old server-side includes, it gives a way to inject a block of html anywhere, anytime, without all the overhead of a directive.
We've actually stepped back from making so many directives and started using includes more when there isn't any work to be done in the directive.
Angular documentation on ngInclude
Sounds like you need a "Multi-slot Transclusion".
This is available in angular versions > 1.5
https://docs.angularjs.org/api/ng/directive/ngTransclude
For the ones who might come here my solution has to add to the directive tBody
return{ controller: 'listCtrl' ...
For me it was the solution I was looking for.
My table looks like this, with more than one tbody:
<tbody>
<tr class='group-header' ng-click="collapseDetail($event)"> ... </tr>
<tr class='detail' ng-hide="groupIsCollapsed()">...</tr>
<tr class='group-footer'> ... </tr>
</tbody>
<tbody>
<tr class='group-header' ng-click="collapseDetail($event)"> ... </tr>
<tr class='detail' ng-hide="groupIsCollapsed($event)">...</tr>
<tr class='group-footer'> ... </tr>
</tbody>
In my collapseDetail() function I toggle a class collapsed on the tbody.
And so I would like to have the detail row hidden only if the parent tbody hasClass('collapsed`).
Is that legal? What I have isn't working:
$scope.collapseDetail = function (e) {
var targ = angular.element( e.currentTarget );
$scope.$apply( function(targ){
targ.parent().toggleClass('collapsed');
});
}
$scope.groupIsCollapsed = function (e) {
if (e == undefined) return false;
var targ = angular.element( e.currentTarget );
return targ.parent().hasClass('collapsed');
}
To give a neater solution had to work on creating a directive for you, as over here you require an isolated scope on individual tbody, so that they can show/hide the detail.
Using Angular Directive has many advantages like
having isolated scope (as mentioned above)
Reducing html markup
NO DOM manipulation in controller ( a strict no taking angular perspective into account, all DOM manipulations to be done only in
directive making it more maintainable)
HTML Code:
<table>
<tbody rendered key="assasa" val="tgtrtghrt"></tbody>
<tbody rendered key="fsfgsd" val="teeger"></tbody>
</table>
Controller Code for this question:
angular.module('t', [])
//You can see that nothing is in the controller now
.controller('test', function ($scope) {});
Directive Code:
.directive('rendered', function ($compile) {
return {
restrict: 'EA',
replace: false,
scope: {
key: '#',
val: '#'
},
link: function (scope, element, attrs) {
var ele = "<tr ng-init='collapseTbody = false;' class='group-header' ng-click='collapseTbody=!collapseTbody'><td>{{key}}</td></tr><tr class='detail' ng-hide='collapseTbody'><td>{{val}}</td></tr>";
scope.$watch('key', function () {
element.html(ele);
$compile(element.contents())(scope);
});
},
}
});
Working Fiddle
More on Angular Directives
I have started an angularjs project and I would like to implement fancybox.
For this, I have included the jQuery and fancybox plugins to the solution. I am attempting to open the template in the code shown below in a fancybox window.
View
ADD
Controller
app.controller('MainController',
function MainController($scope) {
$scope.user = "Hey Welcome";
$scope.open = function(template_path){
$.fancybox({"href":template_path})
}
}
)
And popup/add.html
<div class="pop-contnr">
<h2>ADD</h2>
<table>
<thead>
<tr>
<th align=center>{{user}}</th>
</tr>
</thead>
</table>
</div>
Fancybox successfully opens a window containing the template, but the {{user}} expression has not been evaluated. Can anyone please help?
I have created a directive for fancybox
app.directive('fancybox',function($compile, $timeout){
return {
link: function($scope, element, attrs) {
element.fancybox({
hideOnOverlayClick:false,
hideOnContentClick:false,
enableEscapeButton:false,
showNavArrows:false,
onComplete: function(){
$timeout(function(){
$compile($("#fancybox-content"))($scope);
$scope.$apply();
$.fancybox.resize();
})
}
});
}
}
});
Here is a simplified version of a fancybox directive my team and I wrote to open fancyboxes based on a template with a single click.
It is invoked in the markup like this:
<div fancybox ng-click="openFancybox('templateUrl')"> </div>
The code for the directive is:
app.directive('fancybox', function ($compile, $http) {
return {
restrict: 'A',
controller: function($scope) {
$scope.openFancybox = function (url) {
$http.get(url).then(function(response) {
if (response.status == 200) {
var template = angular.element(response.data);
var compiledTemplate = $compile(template);
compiledTemplate($scope);
$.fancybox.open({ content: template, type: 'html' });
}
});
};
}
};
});
It can be seen working in this plunker
I extended the answer above to use Angular's template cache.
It is invoked in the markup like this:
<div fancybox fancybox-template="template.html">Open Fancybox</div>
The code for the directive is:
app.directive('fancybox', function ($templateRequest, $compile) {
return {
scope: true,
restrict: 'A',
controller: function($scope) {
$scope.openFancybox = function (url) {
$templateRequest(url).then(function(html){
var template = $compile(html)($scope);
$.fancybox.open({ content: template, type: 'html' });
});
};
},
link: function link(scope, elem, attrs) {
elem.bind('click', function() {
var url = attrs.fancyboxTemplate;
scope.openFancybox(url);
});
},
}
});
Here's the plunker.
I have an anchor tag that I wish to hide or show depending on a value in the model.
<table>
<tr ng-repeat="item in items">
<td>Other Stuff</td>
<td>
<a href="#/somewhere" ng-show="model.showIt" myCustomDir="some value" onClick="bar(item)" />
</td>
</tr>
</table>
Now in my directive I have the following:
app.directive('myCustomDir', function() {
var def = {
restrict: 'A',
scope: {
onClick: "&"
},
link: function(scope, element, attrs) {
var hover = angular.element("<div><b>Some Text</b></div>");
var button = hover.find('b');
button.on('click', function() {
scope.$apply(function() {
scope.onClick();
})
});
}
};
return def;
})
The problem is as soon as I include my directive the ng-show one I think no longer works and that is because if I am correct it is because my directive works in isolate scope so the model from the parent scope is no longer present.
How would I get my directive to play nicely with ng-show while still being able to let someone what method they want to call when the tag is clicked.
Plunker for all those interested. http://plnkr.co/edit/BLMCgB
You directive creates an isolated scope. So you need to use $parent to get the value of the current repeater item
ng-show="$parent.item.visible"
If you want to make it more generic, you can take the scope off to make it compatible with other directives. Then you can use scope.$eval to call the function passed in.
myApp.directive('myDirective', function ($document) {
var definition = {
restrict: 'A',
link: function (scope, element, attrs) {
element.on('click', function () {
...
button.on('click', function () {
scope.$apply(function () {
scope.$eval(attrs.onClick);
hover.remove();
})
});
});
}
}
return definition;
})
If you want allow any global directive - don't declare private scope.
If you want allow only few directives, add links in scope declaration:
scope: {
onClick: "&",
ngShow : "&"
},
To your question in comments:
Declare controller in directive and declare method in this controller. Then in directive template assign ng-click to this method.
var def = {
restrict: 'A',
controller: function($scope){
$scope.callMe = function(){
console.log('foo');
}
}
}
in template:
<div ng-click="callMe()">content</div>
This method will be accessible only inside your directive.