Ng-repeat using click to show/hide not working properly - javascript

This is the directive I'm using, there's a list of names that is dynamically generated from JSON. When you click on a name, it is suppose to show/hide a window with more info on that name. What happens instead is it shows/hides every window for every name in the list. I want it to just show/hide the window for the one I click on.
JS:
app.directive("taskListing", function() {
return {
restrict: 'E',
templateUrl: "/templates/elements/tasklisting.html",
scope: {},
link: function(scope, element, attrs, $sce){
element.on("click", function(){
angular.element("tbody.task-tbody tr").toggleClass("hidden");
});
},
};
});
HTML:
<table class="table" ng-controller="taskController">
<tbody class="task-tbody" ng-repeat="task in tasks" ng-if="task.title != ''">
<tr >
<td>
<span class='tasks-task'>{{task.title}}</span>
</td>
</tr>
<!--This table row is toggled show/hide-->
<tr class="hidden" bgcolor="#F8F8F8" >
<td>
<strong>Description:</strong>
<p>{{task.description}}</p>
</td>
</tr>
</tbody>
</table>

you have wrong query in angular.element("tbody.task-tbody tr") you must specify which tr you want to show
first hide all tr and then show only one with specific ID for example
angular.element("tbody.task-tbody tr").addClass('hidden');
angular.element("#task_8").removeClass('hidden');
specify task id in template:
<tr id="task_{{task.id}}">

It is difficult to be certain without your HTML, but I believe that your issue is angular.element("tbody.task-tbody tr").toggleClass("hidden");.
angular.element(document) aliases a jQuery function (ng docs). In this case it is aliasing a selector and selecting all of the rows in your "tbody.task-tbody tr". Thus, when you are calling .toggleClass("hidden"), jQuery is applying the "hidden" class to all of those elements.
Given that you only want to hide the element that has been clicked on, you can use the provided reference to the element in the directive to apply "hidden" exclusively to that element.
For example:
app.directive("taskListing", function() {
return {
restrict: 'E',
templateUrl: "/templates/elements/tasklisting.html",
scope: {},
link: function(scope, element, attrs, $sce){
element.on("click", function(){
// use element instead of 'angular.element'
element.toggleClass("hidden");
});
}
}
});
I think this will solve your problem.

The element you're listening the click event is the directive itself, so everytime you click on something inside the directive, every <tr> will have the toggleClass performed.
So instead of element.on("click",.... you should do element.find("tbody.task-tbody tr").on("click",....
And if you only want to toggle the visiblity of the <tr> with the #F8F8F8 background, I suggest you add a class to target it more easily.
[edit]
Your link function would be:
function(scope, element) {
element.find("tbody.task-tbody tr").on("click", function() {
this.toggleClass("hidden");
});
}

Related

Get the child input tag for the current checkbox clicked

I am trying to get the child input tag for the current checkbox that is clicked and add checked to it through my directive. I have the directive setup correctly but I am getting undefined when I try to get elem[1]
HTML:
<div class="checkbox">
<input id="checkbox1" type="checkbox" checked ng-model="checkboxoption.value1">
<span class="custom"></span>
<label for="checkbox1">Checkbox 1</label>
</div>
JS:
.directive('checkbox', [function() {
return {
restrict: 'C',
scope: {},
link: function(scope, elem, attr) {
elem.bind('click', function(evt) {
var currentCheckbox = elem[1];
console.log(elem[1]);
elem.prop('checked');
});
}
};
}])
elem is a jQuery or jqLite object. The subscript lets you index into the collection of elements matched by a selector. So for example, in jQuery,
$("span")[1]
gets the second span on the page. On the other hand,
$("body")[1]
should return undefined because there should only be one body.
There is only one element (the <div class="checkbox">) in elem. To get its second child, you can do this:
elem.children()[1]
But you probably want its first child, since the checkbox comes first in your HTML:
elem.children()[0]
Another approach is:
elem.find("input")[0]
That may be better since it won't break if you change the order of the elements in your HTML.
Both of these will get you a plain DOM object. Once you have the checkbox element, you can set its checked attribute like this:
elem.children()[0].checked = true;
// or
elem.find("input")[0].checked = true;
By the way, you should probably remove the id element from your checkbox, because if you use this directive more than once, the ID will be duplicated.
You can simply do like this using Angular#angular.element;
You can get the current checked element using jQuery#target property of event
link: function(scope, elem, attr) {
elem.bind('click', function(evt) {
angular.element(evt.target).attr('checked',true);
});
}
Here is the working Plunker
you are trying to access elem[1] which does not exist in the array elem.
Instead of using elem[1] use elem[0]

Dynamically select directive based on data

I am trying to populate a table based on an array of objects. This array doesn't contain objects of the same type and for each row I'd like a completetly diferent style and, onclick function, basically a completely different behaviour.
For instance,
var data=[
{
type:'dir-b',
data: { ... }
},
{
type:'dir-b',
data: { ... }
},
{
type:'dir-c',
data: { ... }
}
]
For object type dirB I want a template and controller and for dirC a completely different function and template.
The solution I found was to create 3 directives. One of wich will run to determine one of the other two directives to add based on data.
.directive("dirA", function($compile){
return{
restrict:'A',
priority:1000,
terminal:true,
link: function(scope, element, attribute){
element.removeAttr("dir-a");//prevent endless loop
element.attr(attribute.type,"");
$compile(element)(scope);
}
}
})
.directive("dirB", function($compile){
return{
restrict:'A',
replace:true,
link: function(scope, element, attribute){
console.log("dirA");
}
}
})
.directive("dirC", function($compile){
return{
restrict:'A',
replace:true,
link: function(scope, element, attribute){
console.log("dirC");
}
}
});
Using <tr dir-a type='{{d.type}}' ng-repeat='d in data'/> is not having the desired effect. Either I give dirA a priority of 0 and it can parse the attribute but it's repeated more times than the array size, or I give it a priority of 1000 and it can't parse the b.type and use it as a literal.
Does anyone have a solution for this?
You could potentially use an ngSwitch here.
Plnkr
HTML
<div ng-repeat="(key, d) in data track by $index">
<div class="tbody" ng-switch on="d.type">
<div class="row" ng-switch-when="dir-b" dir-b>{{d}}</div>
<div class="row" ng-switch-when="dir-c" dir-c>{{d}}</div>
</div>
</div>
Then you just define dirB and dirC directives.
This doesn't display as an html table though, you can hopefully work from this though?
Not sure this was the best solution but it was the solution I found.
<table>
<tbody ng-repeat='d in data'>
<tr ng-if='d.type=="dir-b"' dir-b></tr>
<tr ng-if='d.type=="dir-c"' dir-c></tr>
</tbody>
</table>
This way due to ng-if only the correct row will ever be displayed but the problem is that tbody will be repeated as many row as there are in data. But until there is a beter solution this is how I did it.

Resize on ng-repeat filter

I currently have an AngularJS application embedded in an iframe which needs to be resized to avoid scrollbars. I've got a function in the app that calculates the height of the container and then resizes the iframe.
Currently I am using a directive (resizeAppPart) which will call the resize function on the last item in the scope.
Directive:
app.directive('resizeAppPart', function () {
return function (scope, element, attrs) {
if (scope.$last) {
Communica.Part.adjustSize();
}
}
});
Layout:
<tr ng-repeat="task in filteredTasks = (tasks | filter:filters)" resize-app-part>
<td>{{task.Task_x0020_Title}}</td>
<td><span ng-repeat="user in task.assignees" ng-show="user.Title != ''">
...
</tr>
This works on the initial load but if I filter the list using any of the search boxes, the directive doesn't run so you either end up with a scrollbar or a few thousand pixels of whitespace - neither are ideal.
Is there a way to call the directive, or even the function directly, after the table is filtered?
You need to put a $watch , Use this:
app.directive('resizeAppPart', function ($timeout) {
return function (scope, element, attrs) {
scope.$watch('$last',function(){
Communica.Part.adjustSize();
}
scope.$watch(attrs.filterWatcher,function(){
$timeout(function(){
Communica.Part.adjustSize();
},100)
})
}
});
And slight change in html this way
<tr ng-repeat="task in tasks | filter:filters" resize-app-part filter-watcher={{filters}}>

Toggle columns in a table with colResizable using ng-hide

I created a simple directive that utilizes colResizable plugin that simply activates the plugin on a table after it is rendered:
app.directive("columnResizable", this.columnResizable = function($timeout) {
return {
restrict: "A",
link: function(scope, element, attrs) {
// Initialize colResizable plugin after DOM
$timeout(function() {
element.colResizable({
liveDrag:true,
postbackSafe: true,
partialRefresh: true,
minWidth: 100
});
});
}
}
});
This directive worked fine until I needed to adda feature to hide and show columns in the table. I use ng-hide and turn columns on or off by changing a localStorage boolean variable. The columns hide/show just as expected if I start from a "show all" state. But refuses to show if I start from a hidden state:
<button class="btn btn-default"
ng-class="{active: emailHidden}"
ng-click="emailHidden = !emailHidden">Hide Email</button>
<table column-resizable class="table table-bordered table-striped">
<thead>
<tr>
<th>Name</th>
<th ng-hide="emailHidden">Email</th>
</tr>
</thead>
<tbody ng-if="!scheduleEntries || scheduleEntries.length == 0">
<tr>
<td>Foo Bar</td>
<td ng-hide="emailHidden">foo#bar.com</td>
</tr>
</tbody>
</table>
I created a plunker with regular boolean variable. If you change the $scope.emailHidden variable to start hidden, you can see that the Email column will not show when the button is clicked: http://plnkr.co/edit/Y20BH2
I ended up adding a watcher for the emailHidden variable inside the directive, once changed, it resets the column widths and reinitializes the colResizable plugin:
scope.$watch('emailHidden', function(newVal) {
element.find('th').css('width','auto');
$timeout(function() {
element.colResizable({
liveDrag:true,
postbackSafe: true,
partialRefresh: true,
minWidth: 100
});
});
});
updated plunker
I appreciate it if anyone has a better way to work this out. Preferably something that doesn't involve $watch
When $scope.emailHidden value is set to true, ColResizer updated the width of the second column to zero. And this width is not getting updated even after the emailHidden flag is set to false. As the second column is not having any width in this case, we are not able to see it on the screen.

How can I access an elements value using an angular.js directive?

If I have a directive for a table cell called
<table>
<tr>
<td cellDirective>Some cell Value</td>
<td cellDirective>Another cell value</td>
...
<tr> ...
<table>
defined by
myapp = angular.module('myapp', []);
myapp.directive('cellDirective', function() {
return {
link: function(scope, element) {
console.log(element);
element.addClass("coloring-class");
}
};
});
with the style
<style>
.coloring-class {
color: blue;
}
</style>
What I get in the console is a reference to an object with a ton of different attributes, but I cannot find one with the value in each cell. So how can I access the value inside an element?
As per your JSBin, if you have the cell defined as
<td ng-repeat="cell in row" class="spreadsheet" cell="{{ cell }}">
you can define your directive as
clinApp.directive('cell', function() {
return {
restrict: 'AE',
link: function(scope, element, attrs) {
console.log(attrs.cell);
attrs contains all the attributes in the current element where the directive is placed.
It's just good old fashioned jQuery:
console.log(element.text());

Categories

Resources