alternating class in ng-repeat using scope var without binding - javascript

I have a nested ng-repeat on a table. It is an accordion table with child rows for each parent row. In order to accomplish this I created a <tbody> for each parent item, placing the parent row in a <tr> and then using ng-repeat to add all the child rows. Due to the multiple <tbody> elements the zebra striping on the table gets thrown off. Another wrinkle is that the table has the ability to collapse/expand child rows and I need the striping to be correct for whichever rows are visible. So I am trying to manually add striping classes. I'm using Angular's ng-init to toggle a scope variable, and then using ng-class to apply it. The problem is that it appears to be bound to the final state of the variable rather than what it was as the iterator was rendering the row.
HTML:
<tbody ng-repeat="parentRow in myData" ng-init="changeRowClass($even)">
<tr ng-class="{'industry-payments-even-row':industryRowEven,'industry-payments-odd-row':!industryRowEven}">
<td>{{parentRow.industryCode}} - {{parentRow.industryCodeDescription}}</td>
<td class="numeric">{{parentRow.ValueOneSum}}</td>
<td class="numeric">{{parentRow.ValueTwoSum}}</td>
</tr>
<tr ng-repeat="childRow in parentRow.childIndustries" ng-init="changeRowClass($even)" ng-class="{'industry-payments-even-row':industryRowEven,'industry-payments-odd-row':!industryRowEven}">
<td>{{childRow.industryCode}} - {{childRow.industryCodeDescription}}</td>
<td class="numeric">{{childRow.ValueOne}}</td>
<td class="numeric">{{childRow.ValueTwo}}</td>
</tr>
</tbody>
Controller code:
$scope.industryRowEven = false;
$scope.changeRowClass = function(isEven) {
$scope.industryRowEven = isEven;
};
I need each iteration (of parent OR child) to reverse the class for the next one (I'm leaving out the issue of if the child is visible or not for now to keep this more simple). I can see in the debugger that the variable is getting toggled properly on each iteration. The problem is that the ng-class seems to be bound to the current state in scope so when it is true it would apply one class, but then next time it is false and switches the class for all of them.
I need it to just print the class according to the variable state at the time it renders that row, and then ignore the variable unless the ng-repeat is started over (like for sorting or toggling visibility, etc.)
Is there a way to stop the binding?

AngularJS has this build in with directives called ngClassOdd and ngClassEven.
They work just like ngClass, but only on odd/even items inside an ngRepeat.

Related

Hide a particular instance of a repeating component

I have a table with an ngFor iterating over a collection. For each item in the collection it will produce a row containing a single cell. The cell contains a button and custom component. The custom component has an ngIf directive which only shows the custom component if the device is deemed to be online. I'd like to be able to toggle the visibility of the custom component by clicking the button also. How do I pass the particular instance of the custom component in the table cell to the onExampleBtnClick() function? I do not wish to show/hide all instances of the custom component, only the one that shares the cell with the clicked button. Thanks
<table>
<tr *ngFor="let item of items;let indexOfelement=index;">
<td>
<button type="button" (click)="onExampleBtnClick($event)" #ExampleButton>Click Me</button>
<custom-comp *ngIf="showIfDeviceOnline"></custom-comp>
</td>
</tr>
</table>
You can create another property on your data array which will keep track of if the component need to be shown or not. You dont need to touch existing data creating a new array of object using map function like below.
export class AppComponent {
title = "CodeSandbox";
Items:string[] = ['Apple','Oranges'] ;
data: any = null;
ngOnInit(){
this.data = this.Items.map(x=> {
return {
name: x,
show:true
}
});
}
hide(item){
item.show = false;
}
}
and based on that you can apply *ngIf condition on container like below.
<div>
<div *ngFor="let item of data">
<ng-container *ngIf="item.show">
<button type="button" (click)="hide(item)"> Click Me</button>
<app-custom></app-custom>
</ng-container>
</div>
</div>
Here is the codesandbox
https://codesandbox.io/s/pedantic-forest-871qc?file=/src/app/app.component.ts:160-449
Another approach is the create a mapper object which will keep track of status of each item of array and based on the status hide the component.
Local references is what I was looking for. I can wrap the custom component in a div and apply a local reference of #CustComp and then pass CustComp to the onExampleBtnClick function. It will reference the actual instance and allow me to target it's hidden property.

Angular loop over reactive form controls from HTML

I have a HTML table that is creating a TR component based on an ngFor loop.
<tbody>
<tr *ngFor="let t of intakeForm.controls['tasks'].value let i = index; trackBy:trackByIndex" [taskTR]="t" [ui]="uiOptions" [tasks]="configuredTasks" [intakeForm]="intakeForm"></tr>
</tbody>
Currently, I am looping over the value of the form for these controls but I am trying to loop over the controls instead so I can pass them to the trcomponent.
Something like:
let t of intakeForm.controls['tasks'].controls
tasks is a form array and I am trying to loop over the array of controls it has so that I can pass it to the component.
I tried this let t of (<FormArray>intakeForm.controls['tasks'].controls) but it didn't work. Not sure that it can be done within the html like that.
End goal here is that I want to pass the form control on each iteration to the tr component.
Try using the get method within form groups to inspect specific controls. It's a much cleaner way of accessing controls within form groups.
Also using the methodology below allows you to "drop" into each element within the array and access the properties directly within your template since you are already "within" the correct formGroup in the FormArray. So no need for long prefixes with indices to define which element of the FormArray you are working on.
<ng-container formArrayName="tasks">
<tr [formGroupName]="i" *ngFor="let task of intakeForm.get('tasks').controls; let i=index">
<input type="text" formControlName="taskName" />
</tr>
</ng-container>

Get table attribute or property value within ember controller

I have an ember application which has a table and a grid component within a page. I have enabled drag and drop feature which drags a value from a particular cell of a table and drops it into the empty space of the grid. As the tables have more than one column, I want to get the index position or I want to know which column's cell is being dragged. I want that index value of column within the controller.
Suppose a table has 3 rows with 3 columns. For all the elements within the first column being dragged, I want to get the index value as 1 similarly for 2nd and 3rd column.
Here is my .hbs code for the table
<table class = "table table-bordered">
<thead>
<tr>
<th colspan="6">Inbound Units</th>
</tr>
</thead>
<tbody>
{{#each currentbid as |currentbid|}}
<tr>
{{#each pull as |pull|}}
{{#if (eq pull.DRIVERNAME currentbid.DRIVERNAME)}}
<td abbr="P1">{{#draggable-object content=pull position=1 dragEndAction='dragEndAction'}}{{#draggable-object-target action="draganddrop"}}{{pull.P1}}{{/draggable-object-target}}{{/draggable-object}}</td>
<td>{{pull.P2}}</td>
<td>{{pull.P3}}</td>
<td>{{pull.P4}}</td>
{{/if}}
{{/each}}
</tr>
{{/each}}
</tbody>
</table>
As you can see wihtin the table td tag, I have specified abbr attribute. But I have no idea how to get the value of abbr within the controller. Any other way of getting this is also fine.
Thanks !
This answer applies to Ember 2.x.x and was written as of 2.15.
By default, actions assigned to a specific event via closure, like ondragEnd={{action "someAction"}} receive the event target as an argument:
actions: {
someAction(event) {
console.log(event.target)
}
}
Possibly, you could use event.target.parentElement.className in your component to get the class name, then send the action and argument to your controller. Hopefully that selector will return the new parent and not the old one.
You can read about different ways to catch browser events in the Ember Guides.

How to mark multiple elements without clicking in angular?

I have the following code:
<table>
<tr ng-repeat="minute in hour.minutes track by $index">
<td class="{{minute.class}}" ng-mousedown="setTdCol(hour, minute)" > </td>
</tr>
</table>
setTdCol just changes minute.class causing the cell to change its background color.
My goal is to allow the user to mark multiple cells by pushing the mouse button once and then moving above the cells.
That is why I used ng-mousedown instead of ng-click, but still I have to release the mouse and click each column. What has to be changed?
Try to build your logic together with ng-mouseover and ng-mouseup.
For example you can set a boolean variable mouseDown to true with ng-mousedown and set it to false with ng-mouseup. That way in your ng-mouseover function you can check if the mouse is down or up and mark the elements you go over. You can for example store them in an array and if the element exists in that array on hover - remove it. Or set them to active / inactive with boolean ... etc.
Hope that helps you :)
You want to know when the mouse is down on the table and when it is, which minutes its hovering above. You can set a flag to indicate if the mouse is down on the table that will vary when the table's ng-mouseup and ng-mousedown are invoked, and give an ng-mouseover function to each cell that will check if this flag is true.
<table ng-mousedown="isDown = true" ng-mouseup="isDown = false">
<tr ng-repeat="minute in hour.minutes track by $index">
<td ng-style="minute.class" ng-mouseover='setTdCol(hour, minute)'>aaa</td>
</tr>
</table>
In the example above, isDown is the flag. Now you just need to wrap setTdCol function in an if that checks if is down is true. just don't forget to initialize $scope.isDown in the controller
...
$scope.isDown = false;
$scope.setTdCol = function(hour, minute) {
if($scope.isDown) {
...
}
};
...

Access directive element from a controller

On my page I have a table with a custom directive that shows row item's detailed view when a user click on details button :
<table ng-controller="MyController">
<tr ng-repeat="item in controller.items">
<td>{{item.name}}</td>
<td> details
</td>
</tr>
<tr ng-details details-colspan="2" ng-model="controller.details"></tr>
</table>
Directive:
.directive('ngDetails', function() {
return {
restrict: 'A',
require: "^ngModel",
replace: true,
template: '<tr class="detailsTemplate {{ngModel.cssClass}}"> \
<td colspan="{{detailsColspan}}" ng-bind-html="ngModel.data"></td> \
</tr>',
scope: {
detailsColspan: '#',
ngModel: '='
}
}
});
Here's a demo.
Right now item details section is showing fine, but it's always at the bottom of the table. I would like to move my directive row element under the corresponding item row, but I'm not sure how to access the directive element inside of my controller.
Rather than having the details row appear only once and be moved around, let the ng-repeat include it for every row. You can do this using ng-repeat-start on the first <tr> and ng-repeat-end on the last <tr>. This special syntax allows arbitrary sets of elements to be repeated without being contained in the same repeating element. Then you can include a ng-hide, ng-show, or possibly ng-if on the directive element to only be displayed when the appropriate row has been clicked.
More information about the ng-repeat-start/end syntax can be found here.
You shouldn't access your element inside your controller. If you have to do that, than there's something wrong with either your design of the application or your understanding of Angular itself.
At a general level, you should start thinking of your directives as the glue between HTML and business logic (your actual JS code inside your services, libraries, models, etc...) and be very strict of keeping it this way.
In this particular case, you could think of the whole ng-repeat as part of one directive, and incorporate the detail row between each item rows.
Even better, if showing the detail row requires some action from the user, like a click on the item row, you can dynamically append/remove the detail row just under the clicked item row. This will be a little bit more optimal since the detail data will be "lazy" compiled.

Categories

Resources