ngx-datatable detail row not expanding - html

I am attempting to create a ngx-datatable that I can reuse everywhere to keep styling and how editing, etc work.
Most everything works but I cannot figure out why I cannot get the details row to expand properly.
Here is the common table component html/template:
common-table.component.html
<div>
<h3>Results</h3>
<ngx-datatable
#myTable
class="material expandable"
[rows]="data"
[columns]="columns"
[columnMode]="'force'"
[headerHeight]="50"
[footerHeight]="50"
[rowHeight]="'auto'"
[externalPaging]="hasServerPaging"
[count]="tablePaging.count"
[offset]="tablePaging.offset"
[limit]="tablePaging.limit"
(page)='setPage($event)'>
<ngx-datatable-row-detail *ngIf="hasDetails" [rowHeight]="rowDetailHeight" #myDetailRow [template]="rowDetails" (toggle)="onDetailToggle($event)">
</ngx-datatable-row-detail>
</ngx-datatable>
</div>
The parent component contains the definitions for the columns and templateRefs to pass around:
search-results.component.html
<ng-template #rowDropDownTemplate let-row="row" ngx-datatable-cell-template>
<a [class.datatable-icon-right]="!row.$$expanded" [class.datatable-icon-down]="!row.$$expanded" title="Expand/Collapse Details"
(click)="toggleExpandRow(row)">
</a>
</ng-template>
<ng-template #rowDetailsTemplate let-row="row" ngx-datatable-row-detail-template>
<div class="uk-grid">
<div class="uk-width-1-2">
left-side data
</div>
<div class="uk-width-1-2">
right-side data
</div>
</div>
</ng-template>
<app-common-table
*ngIf="results && results.length > 0"
[tablePaging]="tablePaging"
[columns]="columns"
[data]="tableData"
[rowDetails]="rowDetailsTemplate"
[rowDetailHeight]="200"
[hasServerPaging]="true"
(onPaging)="onPaging($event)"
></app-common-table>
Just for possible issues in the code, here is how the columns are being set:
search-results.component.ts
private setColumns(): void {
this._columns = [];
let templateTest: TemplateRef<any>;
let dropDownColumn: TableColumn = {
width: 50,
resizeable: false,
sortable: false,
draggable: false,
canAutoResize: false,
cellTemplate: this.rowDropDownTemplate,
}
this._columns.push(dropDownColumn);
let nameColumn: TableColumn = {
name: "Name",
width: 120
};
this._columns.push(nameColumn);
let positionsColumn: TableColumn = {
name: "Positions",
width: 200
};
this._columns.push(positionsColumn);
let experienceColumn: TableColumn = {
name: "Experience",
width: 80
};
this._columns.push(experienceColumn);
let leveleColumn: TableColumn = {
name: "Level",
width: 80
};
this._columns.push(leveleColumn);
let educationeColumn: TableColumn = {
name: "Education",
width: 80
};
this._columns.push(educationeColumn);
}
The data appears and so does the icon to expand the details row. Events fire and show the row data, but nothing is shown.
Any help would be greatly appreciated!!

I suspect your toggleExpandRow method is wrong
I would do it like this:
app.common-table.ts
export class AppCommonTableComponent {
#ViewChild('myTable') myTable: any;
parent.component.ts
#ViewChild(AppCommonTableComponent) commonTable: AppCommonTableComponent;
toggleExpandRow(row) {
console.log('Toggled Expand Row!', row);
this.commonTable.myTable.rowDetail.toggleExpandRow(row);
}
And you should fix your rowDropDownTemplate template. It should be
[class.datatable-icon-right]="!row.$$expanded" [class.datatable-icon-down]="row.$$expanded"
If you want to get the right classes
Plunker Example

This is isssue is fixed in version 16.0.3
Refer https://github.com/swimlane/ngx-datatable/blob/master/docs/changelog.md

Related

How to properly bind data to ngx-datatable for inline edit mode

I am trying to set up ngx-datatable component with inline edit option turned on but I am having a problem with binding row data into the table (I am quite new to angular). Could you please give me some hints what is wrong? I have really tried a lot of different options with success rate 0 :(
This is my current configuration:
ProductService is providing data as json in the following format:
"columns":[
{"name":"First column 88"},
{"name":"Second column 88"},
{"name":"Third column 88"}
],
"rows":[
{"First column 88":"Col-1-Row-1 Object nr 88","Second column 88":"Col-2-Row-1 Object nr 88","Third column 88":"Col-3-Row-1 Object nr 88"},
{"First column 88":"Col-1-Row-2 Object nr 88","Second column 88":"Col-2-Row-2 Object nr 88","Third column 88":"Col-3-Row-2 Object nr 88"}
]}
ngx-datatable template looks like that:
<ngx-datatable class="bootstrap"
[rows]="rows"
[columnMode]="'force'">
<ng-container *ngFor="let column of columns; let j = index;">
<ngx-datatable-column [name]="columns[j].name" [sortable]="false">
<ng-template ngx-datatable-header-template>
<span [ngStyle]="{ 'font-weight': 'bold'}">{{columns[j].name}}</span>
</ng-template>
<ng-template ngx-datatable-cell-template let-rowIndex="rowIndex" let-value="value" let-row="row">
<span title="Double click to edit" (dblclick)="editing[rowIndex + '-' + columns[j].name] = true"
*ngIf="!editing[rowIndex + '-' + columns[j].name]">
{{value}}
</span>
<input autofocus (blur)="updateValue($event, 'columns[j].name', rowIndex)" *ngIf="editing[rowIndex+ '-' + columns[j].name]"
type="text" [value]="value" />
</ng-template>
</ngx-datatable-column>
</ng-container>
</ngx-datatable>
And this is my component's code:
export class ProductDetailsComponent implements OnInit {
product: DetailedProduct;
rows = [];
columns: ProductTableColumn[] = [];
editing = {};
constructor(private productService: ProductService, private alertify: AlertifyService, private route: ActivatedRoute) { }
ngOnInit() {
this.route.data.subscribe(data => {
this.product = data.product;
this.columns = data.product.productTable.columns;
this.rows = data.product.productTable.rows;
});
}
updateValue(event, cell, rowIndex) {
console.log('inline editing rowIndex', rowIndex);
this.editing[rowIndex + '-' + cell] = false;
this.rows[rowIndex][cell] = event.target.value;
this.rows = [...this.rows];
console.log('UPDATED!', this.rows[rowIndex][cell]);
}
As a result I'm getting a table with proper amout of columns, proper headers, proper amount of rows BUT with empty cells (looks like a binding issue to me, but no clue where is the problem).
Thank you in advance.
UPDATE
If I send columns like that:
"columns":[
{"prop":"First column 88"},
{"prop":"Second column 88"},
{"prop":"Third column 88"}
]
And use ngx-datatable like that:
<ngx-datatable class="bootstrap"
[rows]="rows"
[columns]="columns"
[columnMode]="'standard'">
</ngx-datatable>
Than binding works fine and as expected. So I guess there is something wrong with my inline edit template.

Unable to store the html content and display again for edit purpose

I am working on a requirement where I have to add & edit the content using quill-editor, the data is getting stored into database that is fine but after saving again I have to retrieve it for edit purpose.
My Problem:
While storing the content p tags are not adding to the content. I dont know how to fix it, I am new to quill editor & angular as well. I have searched for solution but I dint find any solution working So came here.
quill-editor HTML:
<div>
<ul class="list-style-none mt-0">
<li *ngFor="let field of fieldList" class="py-4 text-uppercase">
<a color='accent' class='cursor-pointer' (click)="appendTagTo(field.field_name)"> {{ field.label_name }}
</a>
</li>
</ul>
<div>
<quill-editor [style.display]="'block'" (onEditorCreated)="onEditorCreated($event)" [style.height]="'400px'" formControlName="body" #description>
</quill-editor>
component.ts
import { QuillEditorComponent } from 'ngx-quill';
export class EditMailTemplateComponent implements OnInit { #ViewChild('description') description: QuillEditorComponent;
public editor;
editForm() {
this.mailTemplateForm = this.fb.group({ id: 0, name: [''], body: [''], });
}
getFormData()
{
this.editForm();
this.mailTemplateForm.patchValue(this.data.form_data);
}
onEditorCreated(event) {
this.editor = event;
}
onSubmit()
{
this.mailTemplateForm.controls.body.setValue(this.editor.getText());
}
appendTagTo(textTOInsert: string)
{
textTOInsert = '{{'+textTOInsert+'}}';
const selection = this.editor.getSelection(true);
this.editor.insertText(selection.index, textTOInsert);
}
}
where am I doing the mistake, Is it while storing the content into database or while retrieving the data? Thanks for your help.
I have fixed the issue finally. By using innerHTML, below is the fix:
onSubmit() {
this.mailTemplateForm.controls.body.setValue(document.querySelector(".ql-editor").innerHTML);
--------
--------
}

Add and Remove class in Angular

I want to add background color to the li element when clicked but when I clicked another li element the previous li element background color remains unchanged.
component.html
<div class="col-md-3 categories">
<h3>News By Type</h3>
<ul>
<li class="cat" id="cat_{{i}}" *ngFor="let category of categories; let i = index" (click)="sortNewsItems($event,category,i)"><img src="../assets/images/news.jpg" width="70"><h4>{{category}}</h4></li>
</ul>
</div>
component.ts
sortNewsItems(event,cat,index) {
event.target.classList.add('cat_active');
}
You should use srcElement of the $event
sortNewsItems(event,cat,index) {
event.srcElement.classList.add('cat_active');
}
Read this answer and use its demo
I know this is an old post but just in case. when there are several classes already on the element you might just want to add or remove an extra class you can do this:
On the element:
<div #element </div>
On the component.ts
#ViewChild('element') element: ElementRef;
then you can just add classes or remove them by
this.element.nativeElement.classList.add("newclass");
this.element.nativeElement.classList.remove("newclass");
Remove 'cat_active' class from all the sibling elements before adding a new 'cat_active' class to the selected element.
component.html
<li #li class="cat" *ngFor="let category of categories;" (click)="sortNewsItems($event)">
component.ts
#ViewChildren('li') livs: QueryList<any>;
constructor(private elementRef: ElementRef) { }
sortNewsItems(event) {
this.livs.forEach(liv => liv.nativeElement.children[0].classList = []);
event.target.classList.add('cat_active');
}
I hope it might helps.
Use ngStyle instead of direct class binding in html element.
component.html
<div class="col-md-3 categories">
<h3>News By Type</h3>
<ul>
<li [ngStyle]="setListItemStyle(category)" class="cat" id="cat_{{i}}" *ngFor="let category of categories; let i = index" (click)="sortNewsItems($event,category,i)"><img src="../assets/images/news.jpg" width="70"><h4>{{category.label}}</h4></li>
</ul>
</div>
component.ts
activeListItem: any = null;
categories: any[] = [{ id: 1, label: 'Test label 1' }, { id: 2, label: 'Test label 2' }];
sortNewsItems(event, category, i) {
event.stopPropagation();
this.activeListItem = category;
}
setListItemStyle(category) {
return { 'background-color': this.activeListItem && this.activeListItem.id == category.id ? '#fff000' : null };
}
I just taken a variable and set category name to it when clicked on category li and add active class based on the below condition. finally I set it like what i want. Thank you everyone for the well support.
component.html
<li class="cat" *ngFor="let category of categories; let i = index" (click)="sortNewsItems(category,i)" [ngClass]="{'cat_active':toggleClass === category}"><img src="../assets/images/news.jpg" width="70"><h4>{{category}}</h4></li>
component.ts
toggleClass:string;
sortNewsItems(cat,index) {
this.toggleClass = cat;
}
I read that using srcElement is not a "so good" practice. Better to use renderer2.
<any-element [ngClass]="{selected: isSelected}">
...
</any-element>
OR
<any-element [ngClass]="{selected: isSelected, disabled: isDisabled}">
...
</any-element>
document.querySelector(".menu-open-btn a").onclick = function addNewClass() {
document.querySelector(".mobile-header").classList.add("newClass");
}

Using #input and #output between Angular 2 child components

I have an ng-table which is a child component of my main page. When a row is clicked, it sends the information in that row via onCellClick using an EventEmitter. I'm trying to send this information to another child component. This happens to be a button which is the child of a Bootstrap 4 modal which pops up when a button on the main page is clicked. Just having trouble with the receiving and manipulation of that information.
HTML of child component table:
<ng-table [config]="config"
(tableChanged)="onChangeTable(config)"
(cellClicked)="onCellClick($event)"
[rows]="rows" [columns]="columns">
</ng-table>
HTML for the child component (this appears in the main page's HTML):
<app-datatable (row)="received($event)"></app-datatable>
Typescript for getting and sending the row's data (this.row is the EvenEmitter. data.row is the actual row that's clicked on):
#Output() row: EventEmitter<any> = new EventEmitter<any>();
public onCellClick(data: any): any {
let d = data.row.tDataPoint;
let i = data.row.tICCP;
let s = data.row.tStartDate;
let e = data.row.tEndDate;
let toSend:DataTable = new DataTable(d, i, s, e);
this.row.emit(toSend);
}
HTML for the button that is the child component of the Bootstrap 4 modal:
<button type="submit" class="btn" data-dismiss="modal" (click)="onClick($event)">Delete</button>
Typescript for the button child component:
selector: 'deletebutton'
#Input() receivedRow:DataTable;
onClick(message:DataTable){
this.sender.emit('This is from On Click Deletebutton');
console.log("On Click Deletebutton");
console.log(this.receivedRow);
for (let entry in DPS){
if (DPS[entry].tDataPoint===message.tDataPoint){
DPS.splice(parseInt(entry),1);
}
}
}
HTML of the button child component (this appears in the modal's HTML). This is what should actually be receiving the data from the clicked row as input.
<deletebutton [receivedRow]='row'></deletebutton>
Right now in my onClick method is saying receivedRow is undefined. I feel like what is missing is the coordination between [receivedRow]='row' where I have my deletebutton HTML and the onClick function call in the HTML for that child component. Overall, I just want to click a row, click the button to open the delete Boostrap Modal, and have the correct row be deleted I click the Delete button inside the modal. Let me know if something's not clear or more code is needed.
Is there actually a way to communicate between child components like this using #Input and #Output?
With angular2, your data flow should be :
- down to pass data
- up to send events
So if you really want to go this way, you should have something like that :
I think there's a better way tho :
For your app AND for your user, it'd be best to have a remove button on each line. This way, it avoid the user to be confused clicking on a row and then click on a remove button and within your code you'll be able to do something like that :
src/app.html :
<table class="table">
<tr *ngFor="let row of tableData">
<td *ngFor="let column of row.columns">
{{ column.name }}
</td>
<td (click)="deleteRow(row)"><button>X</button></td>
</tr>
</table>
<button (click)="addRow()">Add a row</button>
src/app.ts (troncated here to the class only) :
#Component({
selector: 'app',
templateUrl: `./src/app.html`,
})
export class App {
private tableData;
private cptRow = 1;
constructor() {
this.tableData = [
{
idRow: `idR${this.cptRow++}`,
columns: [
{idColumn: 'idR1C1', name: 'Column 1-1'},
{idColumn: 'idR1C2', name: 'Column 1-2'},
{idColumn: 'idR1C3', name: 'Column 1-3'}
]
},
{
idRow: `idR${this.cptRow++}`,
columns: [
{idColumn: 'idR2C1', name: 'Column 2-1'},
{idColumn: 'idR2C2', name: 'Column 2-2'},
{idColumn: 'idR2C3', name: 'Column 2-3'}
]
},
{
idRow: `idR${this.cptRow++}`,
columns: [
{idColumn: 'idR3C1', name: 'Column 3-1'},
{idColumn: 'idR3C2', name: 'Column 3-2'},
{idColumn: 'idR3C3', name: 'Column 3-3'}
]
}
];
}
deleteRow(row) {
// we can do this by reference ...
// this.tableData = this.tableData.filter(r => r !== row);
// or by ID
this.tableData = this.tableData.filter(r => r.idRow !== row.idRow);
}
addRow() {
this.tableData.push({
idRow: `idR${this.cptRow}`,
columns: [
{idColumn: `idR${this.cptRow}C1`, name: `Column ${this.cptRow}-1`},
{idColumn: `idR${this.cptRow}C2`, name: `Column ${this.cptRow}-2`},
{idColumn: `idR${this.cptRow}C3`, name: `Column ${this.cptRow}-3`}
]
});
this.cptRow++;
}
}
Here's a working Plunkr : http://plnkr.co/edit/hNhcdraoDNnI2C92TQvr?p=preview
Now, if you really want to use input/output properties, you should look for tutorials because the structure here seems a bit confused. I can help you to understand that (and it's important to understand it with angular2 !) but maybe you should give me a shout on Gitter/Angular instead of detailing Angular2 flow here :)
Somewhat of a work around is to place the Delete button component in the HTML for the table component like this:
<ng-table [config]="config"
(tableChanged)="onChangeTable(config)"
(cellClicked)="onCellClick($event)"
[rows]="rows" [columns]="columns">
</ng-table>
<deletebutton [receivedRow]='toSend'></deletebutton>
And still leave the table's tag in the main page's HTML like I had it:
<app-datatable (row)="received($event)"></app-datatable>
And now the row's data is being sent to that Delete button since it is technically a part of the child component of the main page.
Still not able to communicate between child components like I asked in my question though. But this is something close that works.

How to control ng-repeat divs from ng-repeat inputs

So, just getting started in Angular and it's pretty tricky, coming from a pretty simple JS and jQuery background. Here's what I'm trying to do. I have a "tag template" that has a couple categories and then some sub-tags contained within. I have defined these as an object, with the idea that the object/file can be called via file request and manipulated, etc.
I have loaded labels and tag category inputs dynamically by using a factory service and a controller with ng-repeat. Likewise, I have deposited the subtags into another div on page2 (using jQuery mobile page swiping). I'd like to use the checkbox state of the category tags to show/hide the sub-tags on page2.
I have tried dozens of things and searched all over stackexchange, the net, etc, but is simple and straightforward and similar enough for me to get it working. If someone can point me in the right direction, that would be great. Keep in mind that my next step is to add a button on page 1 to add a new category, and buttons on page 2 to add sub-tags to the sub-tag categories.
Finally, I have one more weird thing to report. If I only have two pages in my DOM, I have some weird behavior when loading the page. If I load from page 1, the tag checkboxes do not function, and I see a slight fattening of the border of the labels. If I swipe left to page 2 and reload from this page, the borders of the labels are thin and the checkboxes function. Cannot track down why this would be happening. My hacky workaround is to add an empty page zero and then changepage immediately to page one, but this is far from ideal. Any thoughts on that would be appreciated as well.
Here it is:
HTML
<!-- Angular version -->
<button class="ui-btn" onclick="selectTemplate();">My Template</button>
<form>
<div data-role="controlgroup">
<fieldset data-role="controlgroup">
<div ng-controller="templateCtrl">
<label
class="ui-checkbox"
ng-style="{backgroundColor: '{{tagCat.color | bgColor}}'}"
ng-repeat="tagCat in template"><input type="checkbox"
class="ui-checkbox"
id="{{tagCat.name}}"
ng-model="clicked"
ng-click="click();"
/>{{tagCat.name}}</label>
<div ng-repeat="tagCat in template">{{cb}} {{tagCat.name}} hallo</div>
</div>
</fieldset>
</div>
<div style="display:none" class="flashNotification"></div>
</form>
</div>
<div data-role="page" id="two">
<button class="ui-btn" onclick="selectTemplate();">My Template</button>
<form>
<div data-role="controlgroup">
<div ng-controller="templateCtrl">
<div ng-repeat="tagCat in template" ng-show="clicked" class="{{tagCat.name}}">{{tagCat.name}}
<fieldset data-role="controlgroup">
<label class="ui-checkbox"
ng-repeat="item in tagCat.items"
ng-style="{backgroundColor: '{{tagCat.color | bgColor}}'}"
for="item.name">{{tagCat.color | bgColor}}
<input class="ui-checkbox"
name="{{item.name}}"
id='{{item.name}}'
type="checkbox" />{{item.name}}</label>
</fieldset>
</div>
</div>
</div>
<div style="display:none" class="flashNotification"></div>
</form>
</div>
</div>
JS for jQuery Mobile
$(document).ready(function() {
// addTemplateItems(tagTemplate); // not necessary with Angular
// $.mobile.changePage('#two', { transition: 'none' }); // required or checkboxes don't work on load
$.mobile.changePage('#one', { transition: 'none' });
// // $("[data-role=controlgroup]").controlgroup("refresh");
// set up page nav
$(document).delegate('.ui-page', "swipeleft", function(){
var $nextPage = $(this).next('[data-role="page"]');
var $prevPage = $(this).prev('[data-role="page"]');
console.log("binding to swipe-left on "+$(this).attr('id') );
// swipe using id of next page if exists
if ($nextPage.length > 0) {
$.mobile.changePage($nextPage, { transition: 'slide' });
} else {
var message = 'tagged!';
// save tags here
flashNotify(message);
console.log('fire event!');
$('#flashNotification').promise().done(function () {
$('#group1').hide();
$('#group2').hide();
$('.ui-btn').hide();
// addTemplateItems(tagTemplate);
$.mobile.changePage($prevPage, { transition: 'none' });
captureImage();
});
}
}).delegate('.ui-page', "swiperight", function(){
var $prevPage = $(this).prev('[data-role="page"]');
console.log("binding to swipe-right on "+$(this).attr('id') );
// swipe using id of next page if exists
if ($prevPage .length > 0) {
$.mobile.changePage($prevPage, { transition: 'slide', reverse : true });
} else {
alert('no backy backy!');
}
});
// $("input[type='checkbox']").checkboxradio().checkboxradio("refresh");
});
JS for Angular App
var app = angular.module('STL', []);
app.factory('TagTemplate', [function () {
var TagTemplate = {};
var tagTemplate = {
family: {
name: "family",
description: "These are your family members.",
color: "red",
items: [
{
name: "Joe"
},
{
name: "Mary"
},
{
name: "Jim"
}
]
},
design: {
name: "design",
description: "Different types of design notes.",
color: "blue",
items: [
{
name: "inspiring"
},
{
name: "fail"
},
{
name: "wayfinding"
},
{
name: "graphics"
}
]
},
work: {
name: "work",
description: "Stuff for work.",
color: "green",
items: [
{
name: "whiteboard"
},
{
name: "meeting"
},
{
name: "event"
}
]
}
};
TagTemplate = tagTemplate;
return TagTemplate;
}])
// Controller that passes the app factory
function templateCtrl($scope, TagTemplate) {
$scope.template = TagTemplate;
$scope.click = function(model) {
console.log(this.checked, this.tagCat.name);
}
}
app.filter('bgColor', function () {
return function (color) {
// console.log(color, $.Color(color).lightness(.05).toHexString(.05));
// var rgba = $.Color(color).alpha(.05);
return $.Color(color).lightness(.97).toHexString();
}
})
For the main part, success!
I found a jsfiddle that gave me a good base for experimenting. After some playing, I realized that I just have to create a show property within each of the categories in my data service model, and then assign the ng-model to that property to control it.
I had to do it slightly differently in my own code, but the understanding gained from the following jsfiddle led me to the answer:
http://jsfiddle.net/Y43yP/
HTML
<div ng-app ng-controller="Ctrl">
<div class="control-group" ng-repeat="field in customFields">
<label class="control-label">{{field}}</label>
<div class="controls">
<input type="text" ng-model="person.customfields[field]" />
<label><input type="checkbox" ng-model="person.show[field]" /></label>
</div>
</div>
<button ng-click="collectData()">Collect</button><button ng-click="addField()">Add Field</button><br/><br/>
<em>Booleans</em>
<div ng-repeat="field in customFields">
<p>{{field}}: {{person.show[field]}}</p>
</div>
<em>Show/Hide</em>
<div ng-repeat="field in customFields">
<p ng-show="person.show[field]">{{field}}: {{person.customfields[field]}}</p>
</div>
</div>
JS
function Ctrl($scope) {
$scope.customFields = ["Age", "Weight", "Height"];
$scope.person = {
customfields: {
"Age": 0,
"Weight": 0,
"Height": 0
},
show: {
"Age": false,
"Weight": false,
"Height": false
}
};
$scope.collectData = function () {
console.log($scope.person.customfields, $scope.person.show);
}
$scope.addField = function () {
var newField = prompt('Name your field');
$scope.customFields.push(newField);
}
}
Still having the checkbox issue but I'll open a separate issue for that if I can't figure it out.
Thanks.