Displaying hidden Div element at specific index using Angular Material table - html

I have a mat-table which has the following functionality: -
(1) Add / Remove rows
(2) Add data into a row using various controls (combo-box, text box, etc).
One of the controls (Addition Information) is a text box that when a ? is entered displays a hidden 'div' element that will eventually be used to hold a list of data.
The issue I have is that if I add say 3 rows and enter a ? into the 3rd row the hidden 'div' display on all 3 rows.
I need a way to somehow index each row added to the table and only display the 'div' element of that row.
Unfortunately my knowledge of HTML is limited and I am fairly new to Angular as well.
I have created a StackBlitz solution demoing my issue. demo

HERE'S A WORKING STACKBLITZ I created an array expandedCols : boolean[] = []; that keeps track of the state (expanded or not) of your div, when you add a row, I also add one to that array with default value false, when you put ? I just change the value at index i to true.
<ng-container matColumnDef="additionalCode" class="parent" >
<th mat-header-cell *matHeaderCellDef>Additional Code</th>
<td mat-cell *matCellDef="let element; let i = index" class="parent" >
<mat-form-field class="type">
<input matInput (keyup)="toggleLookup($event, i)" autocomplete="off" (keydown.ArrowDown)="onDown()" placeholder="Enter ?">
</mat-form-field>
<div *ngIf="expandedCols[i] == true" class="child">Yah it expanded
<button (click)="expanded = false">Close</button>
</div>
</td>
</ng-container>
TS:
addRow() {
this.doAddRow();
this.expanded = false;
this.expandedCols.push(false);
}
toggleLookup(event: any, i): void {
if (event.target.value !== '?') {
return;
}
event.target.value = '';
this.expanded = true;
this.expandedCols[i] = true;
}
You should also pay attention to removing rows, do splice, you get the idea.

The proper way to solve your issue is to use row local state - so put your attribute inside column collection (new column is hidden). This way you will have an opportunity to manage expanded property of specific row. Here is your updated stackblitz.

Related

Is it possible to use [maxSelectedLabels] property in an ngif condition?

I'm using Prime NG Multiselect component and I want to show selectedItemsLabel="{0} Selected" when there are more than 3 selected checkboxes, but if all of the checkboxes are selected, then selectedItemsLabel="All" should be shown in the placeholder.
I'm new to angular and I been following documentation of this MultiSelect component, yet this doesn't show the options to able to implement multiple conditions of properties, and I was wondering if it's even possible.
Example of how It might be
<ng-template pTemplate="filter" let-value let-filter="filterCallback">
<p-multiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
<ng-template let-option pTemplate="item">
<div>
<span class="p-ml-1">{{ option.name }}</span>
</div>
<div *ngIf="[maxSelectedLabels="routeOptions.length - 1"] Then selectedItemsLabel="All"></div>
</ng-template>
</p-multiSelect>
</ng-template>
Yes, you can. First give the component a ref with # like this:
<p-multiSelect
#myMultiSelect
[ngModel]="value"
[options]="routeOptions"
placeholder="Any"
(onChange)="filter($event.value)"
optionLabel="name"
selectedItemsLabel="{0} selected"
[maxSelectedLabels]="3"
>
.......
Then you have access to it:
<div *ngIf="myMultiSelect.maxSelectedLabels === routeOptions.length - 1">Im visible</div>
If the option of maxSelectedLables is the length - 1 of routeOptions then the div is visible. That is how ngIf works
BUT
Thats not what you want. You wanna set the selectedItemsLabel property. And you have it not understand correctly. You set the maxSelectedLables to 3 as example AND set the selectedItemsLabel directly, too! The text of the selectedItemsLabel will be only shown if needed (controlled by the component).
<h5>Basic</h5>
<p-multiSelect #meins [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="3" selectedItemsLabel="{0} items selected">
</p-multiSelect>
Look here the Stackblitz!
The documentation of ng-prime will helps, too and say:
selectedItemsLabel: Label to display after exceeding max selected labels e.g. ({0} items selected), defaults "ellipsis" keyword to indicate a text-overflow.
UPDATE 18.02.2023
You wanna show "ALL" only if all items selected. So add the onChange event and bind the selectedItemsLabel. Why binding? It has some problems with a condition in it. So we make it inside the code.
HTML
<p-multiSelect [options]="cities" [(ngModel)]="selectedCities" defaultLabel="Select a City" optionLabel="name"
[maxSelectedLabels]="2" [selectedItemsLabel]="bindTest" (onChange)="onChange()">
</p-multiSelect>
Inside the code do the follow with onChange:
Code
onChange() {
if (this.selectedCities.length === this.cities.length) {
this.bindTest = "ALL";
this.changeRef.detectChanges();
}
else {
this.bindTest = "{0} items selected";
this.changeRef.detectChanges();
}
}
Now it works how you wish. One important thing: We use changeRef.detectChanges(); Without this the components selected text will not changing directly. Import it in the components constructor:
constructor(
private countryService: CountryService,
private primengConfig: PrimeNGConfig,
private changeRef: ChangeDetectorRef
) {
.....
I made a Stackblitz of the problem: https://stackblitz.com/edit/primeng-tablefilter-demo-ipt7y1?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts
(Expand the page to the left to view the column filter in the stackblitz)
If you notice, the clear button doesn't clear the selected textbox anymore. After some testing it seems the [(ngModel)] breaks it, I think it got to do something with two-way binding? It is not shown in the stackblitz, but if you include
(onChange)="filter($event.value)"
the clear button still clears the filter from the table, but not in the selected textbox.
I found out that there is this property
[showClear]="true"
That adds an X at the end of the textbox that clears it out. Sadly, the styling/positioning is not what I need.
What could be the ways to fix the clear button ? Add a ts function to clear out the selected values? If so, how to bind it to the clear button because it is generated from
<p-columnFilter
display="menu"
menu property and I had no luck to find the way to try add/change functionality to that button.

PrimeNG p-table: How to clear p-dropdown filter values when resetting table filters?

I am using PrimeNG p-table with a header row that has both input and p-dropdown filters and need to clear the filter values of the input and p-dropdown when calling the .reset() method on the table.
As other's have pointed out (https://stackoverflow.com/a/51402834/5764320), you can use [value] on input elements, but there doesn't seem to be a way to clear any non-input filter values.
How can I reset the filters values for the p-dropdown (and other non-input filter types)?
I figured out a simple, clean way that you can use ng-template to accomplish this.
Put your <tr> that has your filters into an ng-template
Add the ng-template to your HTML twice using [ngTemplateOutlet] and *ngIf and assign a value that gets toggled so that one template is used for true and the other for false.
Toggle the value assigned to the templates when you clear your filters.
This "clears" the filters since Angular completely adds and removes the HTML of the templates from the DOM when they are toggled, which means that any values that had previously been used won't exist anymore.
HTML
This assumes you are using <p-table #dt ...> so that dt can be passed with your button click.
Note: leaving some of the p-table related parts and properties out to keep it clean.
<ng-template [ngTemplateOutlet]="FilterRow" *ngIf="showFilters"></ng-template>
<ng-template [ngTemplateOutlet]="FilterRow" *ngIf="!showFilters"></ng-template>
<!-- Whatever your 'tr' content is goes inside this template, this is an abbreviated example -->
<ng-template #FilterRow>
<tr>
<th class="text-center">
<button (click)="clearFilters(dt)">Reset</button>
</th>
<th>
<p-dropdown (onChange)="dt.filter($event.value, col.field, 'equals')"></p-dropdown>
</th>
<th>
<input pInputText type="text" (input)="dt.filter($event.target.value, col.field,'contains')"/>
</th>
</tr>
</ng-template>
TypeScript
...
showFilters = true;
...
clearFilters(dt) {
this.showFilters = !this.showFilters; // toggle the ng-templates
dt.reset(); // reset the table
}
I just manually clear them by referencing the in the ts component.
Template
<p-dropdown #myDropDown (onChange)="dt.filter($event.value, col.field, 'equals')">
</p-dropdown>
TypeScript
...
#ViewChild("myTable", {static: false}) myTable: Table
#ViewChild("myDropDown", {static: false}) myDropDown: Dropdown
onResetTable() {
this.myTable.clear()
this.myDropDown.clear(null);
}
...

How to display saved filters in headers with PrimeNG?

I am able to save the filters locally using stateStorage="local" stateKey="myKey". So when the user leaves the component and returns, the data is still filtered based upon whatever filters they have set.
The problem is, the user has no idea what they are filtering on anymore, as the filter headers do not show them anything anymore, just the default label. I can access the filters via local storage, and delete them using localStorage.removeItem("myKey");, but I cannot for the life of me figure out how to get this filter information to display in the filter headers. We are not using lazyLoad, as suggested in another answer. You'd think this would be built in any time a filter is saved because not knowing what you are filtering on seems like a major flaw.
For more clarity, I have attached the primeFaces documentation below. If you select 'Red' 'White' and 'Green' from the multiselect dropdown, it will display your selected filter in the header (Red, White, Green) above. I need this information to display anytime the user enters the component if they have filters saved (both with text input, and with the dropdowns).
https://www.primefaces.org/primeng/#/table/filter
I am using multi-select dropdown filters, text input, as well as calendar filters. Here is a snippet of the html, which includes examples of these three filter types:
<th *ngFor="let col of columns" [ngSwitch]="col.field">
<input *ngSwitchCase="'userID'" pInputText type="text" size="12" placeholder="contains" (input)="table.filter($event.target.value, col.field, col.filterMatchMode)" [value]="table.filters['userID'] ? table.filters['userID'].value : ''">
<div class="ui-g ui-fluid">
<p-calendar #calendar1 class="ui-fluid" *ngSwitchCase="'myDate'" [monthNavigator]="true" [showOnFocus]="false" [yearNavigator]="true" yearRange="2010:2060" [showIcon]="true"
[showOtherMonths]="false" [showButtonBar]="true" [appendTo]="attach" [style]="{'overflow': 'visible'}"
[(ngModel)]="calendar1Filter"
(ngModelChange)="table.filter($event, 'myDate', calendar1Option)"
(onSelect)="table.filter($event, 'myDate', calendar1Option)">
<p-footer>
<div class="ui-grid-row">
<div class="ui-grid-col-3"><label style="font-weight: bold; color: #337ab7">Mode:</label></div>
<div class="ui-grid-col-6"><p-dropdown [options]="calendar1Options" [style]="{'width':'60px', 'padding-top': '0px'}" [(ngModel)]="calendar1Option" (onChange)="onChangeModCalOpt(calendar1, 1)" ></p-dropdown> </div>
</div>
</p-footer>
</p-calendar>
</div>
<div class="ui-fluid">
<p-multiSelect *ngSwitchCase="'myDropdown'" appendTo="body" [options]="dropdownOptions" pInputText type="text" size="12" [style]="{'width':'100%'}" defaultLabel="All" [(ngModel)]="myDropdownFilter" (onChange)="table.filter($event.value, col.field, 'in')"></p-multiSelect>
</div>
</th>
I did this almost a year ago; it involved some tricky code, as you'll see below, because the table element was beneath an *ngIf directive. I'm not sure if your case is the same, but if it is, here's what I had to to do get it to work. In the example, I have a fooTable that has a custom filter on the status column.
foo.component.ts:
import { ChangeDetectorRef, Component, ViewChild } from "#angular/core";
#Component({
selector : 'foo',
templateUrl : 'foo.component.html'
})
export class FooComponent {
// members /////////////////////////////////////////////////////////////////////////////////////////////////////////
showFooTable: boolean = true;
statusFilter: string[] = [];
// constructor(s) //////////////////////////////////////////////////////////////////////////////////////////////////
constructor(private cd: ChangeDetectorRef) { }
// getters and setters /////////////////////////////////////////////////////////////////////////////////////////////
/**
* Due to the Angular lifecycle, we have to do some tricky things here to pull the filters from the session,
* if they are present. The workarounds done in this function are as follows:
*
* 1) Wait until the element is accessible. This is not until the *ngIf is rendered, which is the second call to
* this function. The first call is simply 'undefined', which is why we check for that and ignore it.
* 2) Check and see if the filters for this object are even part of the template. If not, just skip this step.
* 3) If we find the filters in the session, then change this object's filters model to equal it and call the change
* detection manually to prevent Angular from throwing an ExpressionChangedAfterItHasBeenCheckedError error
* #param fooTable the reference to the foo table template
*/
#ViewChild('fooTable') set fooTable(fooTable: any) {
if(fooTable != undefined) {
let filters = fooTable.filters['status'];
if (filters != undefined && filters.value != undefined) {
this.statusFilter = filters.value;
}
this.cd.detectChanges();
}
}
}
foo.component.html:
<ng-container *ngIf="showFooTable">
<div id="filters">
<p-checkbox [(ngModel)]="statusFilter" value="up" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Up
<p-checkbox [(ngModel)]="statusFilter" value="down" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Down
<p-checkbox [(ngModel)]="statusFilter" value="unknown" (onChange)="fooTable.filter(statusFilter, 'status', 'in')"></p-checkbox> Unknown
</div>
<p-table #fooTable
stateStorage="session"
stateKey="foo-table-state">
</p-table>
</ng-container>
Turbo tables filters can be access like so. table.filters['myColumn']?.value. You will need to set the input values in the header, [value]="table.filters[col.field]?.value"
...
<tr>
<th *ngFor="let col of columns" [ngSwitch]="col.field" class="ui-fluid">
<input pInputText
type="text"
(input)="table.filter($event.target.value, col.field, col.filterMatchMode)"
[value]="table.filters[col.field]?.value">
</th>
</tr>
...
https://www.primefaces.org/primeng/#/table/state
Just figured this out. What I ended up doing was attaching the default label to a model like so:
<p-multiSelect [defaultLabel]="table.filters[col.field]?.value || 'All'">
If the table has the filter value in its state, it will populate the label else default to 'All'.

Angular nested draggable rows and columns

Resources I've looked at:
https://github.com/valor-software/ng2-dragula
Issues describing similar problems:
https://github.com/valor-software/ng2-dragula/issues/309
https://github.com/valor-software/ng2-dragula/issues/663
I'm using ng2-dragula and I'm trying to have nested draggable items but I cannot for the life of me figure out how to do it despite looking at examples.
This one allows columns to be dragged from one row to another just fine:
<div>
<row *ngFor="let row of rows" dragula="columns" [dragulaModel]="row?.columns">
<column *ngFor="let column of row?.columns"></column>
</row>
</div>
However I also need the rows themselves to be draggable so I did this:
<div dragula="rows" [dragulaModel]="rows">
<row *ngFor="let row of rows" dragula="columns" [dragulaModel]="row?.columns">
<column *ngFor="let column of row?.columns"></column>
</row>
</div>
This allows the rows to be draggable but now the columns doesn't work, if I try to drag a column into another row I get the error:
ERROR DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.
The columns also cannot be re-ordered within the same row.
It appears as if dragging a column causes the row to be dragged instead of the column.
What am I doing wrong here?
I made an example for you. Let me know if this answers your question. Setting dragula options for the row "bag" allows you to pass your own invalid handler. So, dont let the parent container drag if you are attempting to drag something with the class name "column" for example.
https://embed.plnkr.co/Y4Gy0nHuGKpaYQObXLWG/
In your constructor, or whenever you set your dragula options for the parent dragula:
constructor(dragulaService: DragulaService) {
dragulaService.setOptions('bag', {
invalid(el, handle) {
// If the selected element className is column,
// dont allow the row to be dragged. This will allow
// the column to be dragged separate from the row.
return (el.className == "column");
}
});
}
HTML:
<div [dragula]="'bag'" [dragulaModel]="rows">
<div class="row" *ngFor="let row of rows">
{{row.value}}
<div [dragula]="'bag2'" [dragulaModel]="row.columns">
<div class="column" *ngFor="let column of row.columns">
{{column.value}}
</div>
</div>
</div>
</div>
Try this:
<div dragula="myBag" [dragulaModel]="rows" class="iAmContainer">
<row *ngFor="let row of rows" dragula="myBag" [dragulaModel]="row?.columns" class="iAmRow">
<column *ngFor="let column of row?.columns" class="iAmCol"></column>
</row>
</div>
I set dragula="myBag" to both row and column and set classes on elements.
Now in ngOnInit use code similar to this:
const bag: any = this.dragulaService.find('myBag');
if (bag !== undefined) this.dragulaService.destroy('myBag');
this.dragulaService.setOptions('myBag', {
revertOnSpill: true,
accepts: function (el, target, source, sibling) {
return !el.contains(target) &&
((el.className == 'iAmCol' && target.className == 'iAmRow') ||
(el.className == 'iAmRow' && target.className == 'iAmContainer'));
},
});
I used the className of the element and target to determine if they are compatible.

AngularJS, checkboxes, and generating dynamic html

I am wanting to generate a table dynamically using Angular JS, based on what is checked on some checkboxes. The problem is that there are a few fields, we will call them relation/column, that I want to display ALWAYS, and the remaining fields only if their box is checked. The relation is searched for via a search box (relations can have multiple columns), and I want to display only the properties of that relation that are relevant to a user.
So
Update Time [X]
Update Status [ ]
Time Zone [ ]
Would display some html along the lines of
<table>
<tr>
<th> Relation </th>
<th> Column </th>
<th> Update Time </th>
</tr>
<tr ng-repeat= "result in results">
<td> {{result.relation}} </td>
<td> {{result.column}} </td>
<td> {{result.update_time}}</td>
</tr>
If no boxes were checked, only the relation and column fields would be populated. The documentation for Angular JS is taking me all over the place, so would anyone have an idea on how to do this?
edit : controller isn't working quite yet, I still need to filter the search results, but basically it goes
$scope.search = function(){
//validate form input
//create url with the input recieved
$http.get(url).success(function(data){
$scope.results = angular.fromJson(data);
});
}
I use mojolicious backend to grab the data I want. Again, the problem isn't that I can't get any data, or that I can't filter the results based on the relation. I want to be able to search based on relation, and only display the attributes of that relation that I want to, based on what is checked. THAT part, I can't figure out.
edit again : the firewall where I'm at prevents me from writing comments/upvoting. You shall be rewarded for your help when I get home tonight. Thank you thank you!
I think the best way to do this would be using ng-show expressions tied to a variable in the model.
For example.
<input type="checkbox" ng-model="updateTime">
makes a checkbox and ties the result to $scope.updateTime. You can then use this variable later on via the ng-show directive like so...
<th ng-show="updateTime"> Update Time </th>
...
<td ng-show="updateTime"> {{result.update_time}}</td>
this means that these elements will only show when updateTime is set to true (i.e the checkbox is checked.)
You can see an example here, I've only implemented the one field but it should be possible to extend it pretty easily!
http://plnkr.co/edit/W6Ht6dnGw4fBplI83fB1?p=preview
I would suggest using a custom filter with the checkbox scope variables passed in. Something like this:
html
<input type="checkbox" ng-model="checkbox1" />
<input type="checkbox" ng-model="checkbox2" />
<input type="checkbox" ng-model="checkbox3" />
... ng-repeat= "result in results|checkboxFilter:{checkbox1,checkbox2,checkbox3}"
filter.js
.filter('checkboxFilter', function() {
return function (results, checkbox1, checkbox2, checkbox3) {
var filtered_objects = angular.copy(results);
for (var i = 0, len = filtered_objects.length; i < len; i++) {
**modify your filtered_objects based on your checkboxes**
if (checkbox1) ...
if (checkbox2) ...
if (checkbox3) ...
}
return filtered_objects;
}
});
Maybe something like that.