AgGrid tree grid does not show identical leaf name items on the same group level - ag-grid

I'm using AgGrid with tree data. https://www.ag-grid.com/javascript-grid-tree-data/
The problem is that I need to have 2 leaf node on the tree with the same name, because they share the name but not it's properties.
I thought this was specified by getRowNodeId GridOption. Example of usage in Option 1: https://www.ag-grid.com/javascript-grid-rxjs/.
But it doesn't.
He is my code of that property:
...
getRowNodeId: (data: any) => any = (data) => {
return (data.parent !== undefined ? data.parent * 1000 : 0) + data.properties.id;
},
...
As you can see, I want to add 2 nodes with the same name but only 1 renders. What can I do?
UPDATE: Code added
My AgGrid component:
import { Component, ViewChild, ElementRef, OnDestroy, Input, Output, EventEmitter } from '#angular/core';
import { Events, ToastController, AlertController } from 'ionic-angular';
import * as _ from 'lodash';
import 'ag-grid-enterprise';
import { Http } from '#angular/http';
import { GridApi, TemplateService, GridOptions, ColDef } from 'ag-grid';
import { TemplateServiceProvider } from '../../providers/services';
import { NgModel } from '#angular/forms';
import { Toast } from '../../classes/classes';
import { EventSubscriber } from '../../classes/eventSubscriber/eventSubscriber';
#Component({
selector: 'page-datatableWithGroups',
templateUrl: 'datatableWithGroups.html'
})
export class DatatableWithGroupsPage extends EventSubscriber implements OnDestroy {
#ViewChild('quickFilterInput') quickFilterInput: ElementRef;
#Input() rowData: any[] = [];
#Input() columnDefs = [];
#Input() title: string;
#Output() onLoadRow: EventEmitter<any> = new EventEmitter();
#Output() onAddRow: EventEmitter<any> = new EventEmitter();
#Output() onDeleteRow: EventEmitter<any> = new EventEmitter();
#Output() onDuplicateRow: EventEmitter<any> = new EventEmitter();
#Output() onSelectedRow: EventEmitter<any> = new EventEmitter();
public gridApi: GridApi;
public gridColumnApi;
public printPending: boolean = true;
public showColumnTools: boolean;
public showColumnVisibilityTools: boolean;
public showFilterTools: boolean = true;
private groupDefaultExpanded;
private hoverNode = null;
private isControlPressed: boolean;
private newGroupsCounter: number = 0;
private autoGroupColumnDef: ColDef = {
rowDrag: true,
headerName: "Group",
width: 250,
suppressMovable: true,
cellRendererParams: {
suppressCount: true,
},
valueFormatter: (params) => {
if (!params.data.properties.checkpointList) return params.value;
return params.value + ' (' + (params.data.subGroups ? params.data.subGroups.length : 0) + ')'
+ '(' + (params.data.properties.checkpointList ? params.data.properties.checkpointList.length : 0) + ') ';
},
cellClassRules: {
"hover-over": (params) => {
return params.node === this.hoverNode;
},
"checkpointGroup-title": (params) => {
return this.isCheckpointGroup(params.node.data);
}
}
};
addRowFunction: () => void;
isCheckpointGroup: (nodeData) => boolean = (nodeData) => {
return nodeData && nodeData.properties.checkpointList;
}
getDataPath: (data: any) => any = (data) => {
return data.properties.orgHierarchy;
};
getRowNodeId: (data: any) => any = (data) => {
return (data.properties.parent !== undefined ? data.properties.parent * 1000 : 0) + data.properties.id + (data.properties.component !== undefined ? data.properties.component.id * 100000 : 0);
}
getRowNodeWithUpdatedId: (data: any) => any = (data) => {
return (data.properties.parentId !== undefined ? data.properties.parentId * 1000 : (data.properties.parent !== undefined ? data.properties.parent * 1000 : 0)) + data.properties.id + (data.properties.component !== undefined ? data.properties.component.id * 100000 : 0);
}
public gridOptions: GridOptions = {
enableFilter: this.showFilterTools,
enableColResize: true,
animateRows: true,
cacheQuickFilter: this.showFilterTools,
treeData: true,
quickFilterText: this.quickFilterInput ? this.quickFilterInput.nativeElement.value : '',
colResizeDefault: 'shift',
groupDefaultExpanded: this.groupDefaultExpanded,
rowSelection: 'single',
rowDeselection: true,
defaultColDef: {
filter: "agTextColumnFilter"
},
// components: this.components,
getDataPath: this.getDataPath,
getRowNodeId: this.getRowNodeId,
autoGroupColumnDef: this.autoGroupColumnDef,
deltaRowDataMode: true,
onModelUpdated: () => {
if (this.gridApi && this.columnDefs) {
this.gridColumnApi.autoSizeColumn('ag-Grid-AutoColumn');
this.adjustColumnsToFitWindow();
}
},
onSelectionChanged: (event) => {
let selectedRows = event.api.getSelectedNodes();
let selectedRow = selectedRows && selectedRows.length > 0 ? selectedRows[0] : undefined;
if (selectedRow && this.isCheckpointGroup(selectedRow.data)) {
this.onSelectedRow.emit(selectedRow);
} else {
this.onSelectedRow.emit(undefined);
}
},
};
constructor(
events: Events,
public http: Http,
public templateService: TemplateServiceProvider,
public toastController: ToastController,
public alertCtrl: AlertController,
) {
super(events, [
{ eventName: 'datatable:updateList', callbackFunction: () => this.onLoadRow.emit() },
{ eventName: 'datatable:resizeTable', callbackFunction: () => this.gridApi.sizeColumnsToFit() }
])
this.groupDefaultExpanded = -1;
}
ngOnInit() {
super.subscribeEvents();
this.subscribeEvents();
}
subscribeEvents() {
this.addRowFunction = () => { this.onAddRow.emit() };
window.onresize = () => this.adjustColumnsToFitWindow();
document.addEventListener('keydown', (evt: any) => {
evt = evt || window.event;
if ((evt.keyCode && evt.keyCode === 17) || (evt.which && evt.which === 17)) this.isControlPressed = !this.isControlPressed;
});
}
updateRowData(rowData) {
this.gridApi.setRowData(rowData);
this.gridApi.clearFocusedCell();
this.reAssignSortProperty();
}
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
this.adjustColumnsToFitWindow();
if (!this.rowData || this.rowData.length === 0) this.gridApi.hideOverlay()
}
onRowDragMove(event) {
this.setHoverNode(event);
}
onRowDragLeave() {
this.setHoverNode(undefined);
}
setHoverNode(event) {
let overNode = event ? event.overNode : undefined;
if (this.hoverNode === overNode) return;
var rowsToRefresh = [];
if (this.hoverNode) {
rowsToRefresh.push(this.hoverNode);
}
if (overNode) {
rowsToRefresh.push(overNode);
}
this.hoverNode = overNode;
this.refreshRows(rowsToRefresh);
}
refreshRows(rowsToRefresh) {
var params = {
rowNodes: rowsToRefresh,
force: true
};
this.gridApi.refreshCells(params);
}
onRowDragEnd(event) {
let toIndex;
let overNode = event.overNode;
let movingNode = event.node;
let movingData = movingNode.data;
if (overNode === movingNode) return;
if (!overNode) {
if (this.isCheckpointGroup(movingData)) {
overNode = (<any>this.gridApi.getModel()).rootNode;
toIndex = this.rowData.length;
} else {
return;
}
}
let overData = overNode.data;
let fromIndex = this.rowData.indexOf(movingData);
toIndex = toIndex ? toIndex : this.rowData.indexOf(overData) + 1;
let newParentNode = (!overNode.data || this.isCheckpointGroup(overNode.data)) ? overNode : overNode.parent;
if (overData && (overData.properties.parentId === 0 || (!overData.properties.parentId && overData.properties.parent === 0)) && this.isCheckpointGroup(movingData) && this.isControlPressed) {
overNode = (<any>this.gridApi.getModel()).rootNode;
newParentNode = (!overNode.data || this.isCheckpointGroup(overNode.data)) ? overNode : overNode.parent;
} else if (overData && this.isControlPressed) {
newParentNode = overNode.parent;
}
if (toIndex > fromIndex) toIndex--;
let oldParentNode = movingNode.parent;
let newParentPath = (newParentNode.data && this.isCheckpointGroup(newParentNode.data)) ? newParentNode.data.properties.orgHierarchy : [];
let needToChangeParent = !this.arePathsEqual(newParentPath, movingData.properties.orgHierarchy);
if (fromIndex === toIndex && !needToChangeParent) return;
if (this.isInvalidMoveTo(movingNode, newParentNode)) {
new Toast(this.toastController).showToast('Invalid Move');
return;
}
if (needToChangeParent) {
if (this.checkNodeExistsInParent(movingData, newParentNode)) return;
let updatedRows = [];
this.moveToPath(newParentPath, movingNode, updatedRows, oldParentNode.data, newParentNode.data || { properties: { id: 0 } });
this.gridApi.updateRowData({ update: updatedRows });
this.refreshRows(this.rowData);
}
let newStore = this.rowData.slice();
this.moveInArray(newStore, fromIndex, toIndex);
this.gridApi.setRowData(newStore);
this.rowData = newStore;
this.setHoverNode(undefined);
}
checkNodeExistsInParent(newRow, newParentNode) {
let cloneRow = _.cloneDeep(newRow);
cloneRow.properties.parentId = newParentNode && newParentNode.data ? newParentNode.data.properties.id : 0;
if (this.isCheckpointGroup(cloneRow) && this.existsInParent(cloneRow)) {
new Toast(this.toastController).showToast('"' + cloneRow.properties.name + '" already exists on "' + (newParentNode.data ? newParentNode.data.properties.name : 'root') + '"');
return true;
}
return false
}
checkNodeExistsInTemplate(newRow) {
if (!this.isCheckpointGroup(newRow) && this.existsInTemplate(newRow)) {
new Toast(this.toastController).showToast('"' + newRow.properties.name + '" already exists on the template');
return true;
}
return false
}
existsInParent(newRow) {
return this.rowData.find(row => this.getRowNodeWithUpdatedId(row) === this.getRowNodeWithUpdatedId(newRow));
}
existsInTemplate(newRow) {
return this.rowData.find(row => row.properties.id === newRow.properties.id);
}
moveInArray(arr, old_index, new_index) {
if (new_index >= arr.length) {
var k = new_index - arr.length + 1;
while (k--) {
arr.push(undefined);
}
}
arr.splice(new_index, 0, arr.splice(old_index, 1)[0]);
};
moveToPath(newParentPath, node, allUpdatedNodes, oldParentData?, newParentData?) {
var newChildPath = this.updateNodePath(node.data, newParentPath);
if (newParentData) this.updateParents(node.data, oldParentData, newParentData);
allUpdatedNodes.push(node.data);
if (oldParentData) allUpdatedNodes.push(oldParentData);
if (newParentData) allUpdatedNodes.push(newParentData);
if (node.childrenAfterGroup) this.moveChildrenPaths(node.childrenAfterGroup, newChildPath, allUpdatedNodes);
}
updateNodePath(nodeData, newParentPath) {
var oldPath = nodeData.properties.orgHierarchy;
var fileName = oldPath[oldPath.length - 1];
var newChildPath = newParentPath.slice();
newChildPath.push(fileName);
nodeData.properties.orgHierarchy = newChildPath;
return newChildPath;
}
updateParents(nodeData, oldParentData, newParentData) {
nodeData.properties.parentId = newParentData.properties.id;
if (this.isCheckpointGroup(nodeData)) {
if (oldParentData) oldParentData.subGroups = oldParentData.subGroups.filter(subGroup => subGroup.properties.id !== nodeData.properties.id);
if (newParentData && newParentData.subGroups !== undefined) newParentData.subGroups.push(nodeData);
} else {
if (oldParentData) oldParentData.properties.checkpointList = oldParentData.properties.checkpointList.filter(checkpoint => checkpoint.properties.id !== nodeData.properties.id);
if (newParentData && newParentData.properties.checkpointList) newParentData.properties.checkpointList.push(nodeData);
}
}
moveChildrenPaths(children, newChildPath, allUpdatedNodes) {
children.forEach((child) => {
this.moveToPath(newChildPath, child, allUpdatedNodes);
});
}
isInvalidMoveTo(selectedNode, targetNode) {
let isInvalid = false;
if (targetNode.parent && targetNode.parent.data) {
isInvalid = this.isInvalidMoveTo(selectedNode, targetNode.parent);
if (this.getRowNodeWithUpdatedId(targetNode.parent.data) === this.getRowNodeWithUpdatedId(selectedNode.data)) isInvalid = true;
}
if (targetNode && targetNode.data && this.getRowNodeWithUpdatedId(targetNode.data) === this.getRowNodeWithUpdatedId(selectedNode.data)) isInvalid = true;
return isInvalid;
}
arePathsEqual(path1, path2) {
let newPathLength = path1.length + 1;
let oldPathLength = path2.length;
if (this.isControlPressed && newPathLength === 2 && oldPathLength === 1) return true;
if (newPathLength !== oldPathLength) return false;
var equal = true;
path1.forEach(function (item, index) {
if (path2[index] !== item) {
equal = false;
}
});
return equal;
}
reAssignSortProperty() {
this.gridApi.forEachNode((node) => {
node.data.properties.sort = node.childIndex + 1
})
};
addRows(data) {
let selectedGroupNode = data.selectedGroup;
let groupIndex = selectedGroupNode ? this.rowData.indexOf(selectedGroupNode.data) + 1 : this.rowData.length;
data.selectedRows.forEach((selectedRow, index) => {
let newRowData = _.cloneDeep(selectedRow.data);
newRowData.orgHierarchy = selectedGroupNode ? _.cloneDeep(selectedGroupNode.data.properties.orgHierarchy) : [];
newRowData.orgHierarchy.push(newRowData.name);
newRowData = { properties: newRowData };
newRowData.properties.parent = selectedGroupNode ? selectedGroupNode.data.properties.id : 0;
if (this.isCheckpointGroup(newRowData)) {
if (!this.checkNodeExistsInParent(newRowData, selectedGroupNode)) {
this.newGroupsCounter--;
newRowData.properties.checkpointGroupId = newRowData.properties.id;
newRowData.properties.id = this.newGroupsCounter;
newRowData.subGroups = [];
if (selectedGroupNode && selectedGroupNode.data) selectedGroupNode.data.subGroups.push(newRowData);
this.rowData.splice(groupIndex + index + (selectedGroupNode ? selectedGroupNode.allChildrenCount : 0), 0, newRowData)
}
} else {
if (!this.checkNodeExistsInTemplate(newRowData)) {
newRowData.properties.templateCheckpointGroupCheckpointId = -1;
if (selectedGroupNode && selectedGroupNode.data) selectedGroupNode.data.properties.checkpointList.push(newRowData);
this.rowData.splice(groupIndex + index + (selectedGroupNode ? selectedGroupNode.allChildrenCount : 0), 0, newRowData)
}
}
});
if (selectedGroupNode) data.selectedGroup.expanded = true;
this.updateRowData(this.rowData);
}
deleteRow(row) {
if (row.properties.checkpointList && row.properties.checkpointList.length > 0) {
row.properties.checkpointList.forEach(checkpoint => {
this.deleteRow(checkpoint);
});
}
if (row.subGroups && row.subGroups.length > 0) {
row.subGroups.forEach(subGroup => {
this.deleteRow(subGroup);
});
}
this.deleteByUpdatedParentId(row);
this.updateRowData(this.rowData);
}
deleteByUpdatedParentId(row) {
this.rowData = this.rowData.filter(existingRow => this.getRowNodeWithUpdatedId(existingRow) !== this.getRowNodeWithUpdatedId(row));
}
adjustColumnsToFitWindow() {
if (this.gridApi) this.gridApi.sizeColumnsToFit();
}
onFilterTextBoxChanged() {
this.gridApi.setQuickFilter(this.quickFilterInput.nativeElement.value);
}
exportCsv() {
let params = {
fileName: 'Rows',
sheetName: 'Rows',
columnSeparator: ';'
};
this.gridApi.exportDataAsCsv(params);
}
print() {
this.setPrinterFriendly();
this.printPending = true;
// if (this.gridApi.isAnimationFrameQueueEmpty()) {
this.onAnimationQueueEmpty({ api: this.gridApi });
// }
}
onAnimationQueueEmpty(event) {
if (this.printPending) {
this.printPending = false;
this.printTable();
}
}
printTable() {
// let rest = <any>document.querySelector("ion-content :not(#grid)");
// rest.style.display = 'none';
print();
}
setPrinterFriendly() {
let eGridDiv = <any>document.getElementById("grid");
let preferredWidth = this.gridApi.getPreferredWidth();
preferredWidth += 2;
eGridDiv.style.width = preferredWidth + "px";
eGridDiv.style.height = "";
this.gridApi.setGridAutoHeight(true);
}
}
The html:
<ion-content padding>
<ion-item class="generic-tools-wrapper">
<div>
<input type="text" #quickFilterInput placeholder="Filter..." (input)="onFilterTextBoxChanged()" />
<button ion-button small type="button" icon-only (click)="exportCsv()">
<ion-icon name="list-box"></ion-icon>
</button>
<button ion-button small type="button" icon-only (click)="print()">
<ion-icon name="print"></ion-icon>
</button>
<button ion-button small [color]="isControlPressed ? 'danger': 'primary'" type="button" icon-only (click)="isControlPressed = !isControlPressed">
<ion-icon name="return-right"></ion-icon>
</button>
</div>
</ion-item>
<ion-label> <strong>Attention!</strong> The grouping option is <strong>{{isControlPressed ? 'deactivated':
'activated'}}</strong>,
elements
dragged over a group will be put <strong>{{isControlPressed ? 'next to': 'inside'}} that group</strong>. Press
<strong>Control</strong> to switch this option. </ion-label>
<div style="height: 90%;box-sizing: border-box; ">
<ag-grid-angular id="grid" style="width: 100%; height: 100%;" class="ag-theme-material" [rowData]="rowData"
[columnDefs]="columnDefs" [gridOptions]="gridOptions" (gridReady)="onGridReady($event)" (rowDragMove)="onRowDragMove($event)"
(rowDragEnd)="onRowDragEnd($event)" (rowDragLeave)="onRowDragLeave($event)" (viewportChanged)="adjustColumnsToFitWindow()">
</ag-grid-angular>
</div>
</ion-content>

Related

Javascript class how to make it, with a constructor?

Also what are these:
`from selenium import webdriver`
\`def hladaj(vyraz):
driver = webdriver.Chrome()
driver.get('https://www.bing.com/')
search_bar = driver.find_element_by_name('q')
search_bar.send_keys(vyraz)
search_bar.submit()
def otvor(n):
result_links = driver.find_elements_by_css_selector('.b_algo a')
result_links\[n-1\].click()
class Laptop {
constructor(vyrobca, model, rocnik) {
this.vyrobca = vyrobca;
this.model = model;
this.rocnik = rocnik;
}
vypis() {
return `${this.vyrobca},${this.model},${this.rocnik}`;
}
}\`
`<script> // Ziskanie tlacidla var button = document.getElementById("remove"); // Pridanie event listenera na stlacenie tlacidla button.addEventListener("click", function() { // Ziskanie vsetkych elementov s triedou "col" var elements = document.getElementsByClassName("col"); // Prechod cez vsetky elementy for (var i = 0; i < elements.length; i++) { // Zistenie textu v elemente var text = elements[i].textContent; // Ak element obsahuje retazec "Row" if (text.includes("Row")) { // Zmazanie elementu elements[i].parentNode.removeChild(elements[i]); } } }); </script>`
`function o2a(obj){ let final= []; final = Object.keys(objs).map(key=> { let arr = []; arr.push(key); arr.push(obj[key]); return arr; }) return final; }`
`function a2o(arr){ let obj = {} arr.forEach(item=> { obj[item[0]] = item[1]; }); return obj; `
\`import React, { useState } from "react";
function Inp() {
const \[value, setValue\] = useState("");
return (
\<input
value={value}
onChange={(e) =\> setValue(e.target.value)}
placeholder="Input value"
/\>
);
}
function But() {
const \[clicked, setClicked\] = useState(false);
return (
\<button onClick={() =\> setClicked(true)}\>Submit\</button\>
);
}
function Out({ value }) {
return \<div\>{value}\</div\>;
}
function App() {
const \[inputValue, setInputValue\] = useState("");
const handleClick = () =\> {
setInputValue(inputValue);
};
return (
\<div\>
\<Inp /\>
\<But onClick={handleClick} /\>
\<Out value={inputValue} /\>
\</div\>
);
}
export default App;\`
Jazyk javascript bezi defaultne na jednom jadre. Na rozhodnutie toho kedy spusti ktoru funkciu, pouziva event Loop.
Multi-threaded funkcionalitu vieme dosiahnut ponocou builtin API ktore sa nazyva Webliorker. Tento API umoznuje beh paralelnych procesov.
A este tab a browser maju vlastne javascript thready a tak aj prehliadaci je multi thread
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8084 });
let counter = 0;
setInterval(() => {
counter += 2;
}, 2000);
wss.on('connection', (ws) => {
console.log('Connected');
setInterval(() => {
ws.send(counter.toString());
}, 2000);
});
const WebSocket = require('ws');
const ws = new WebSocket('ws://localhost:8084');
ws.on('message', (message) => {
const counter = parseInt(message);
const span = document.createElement('span');
span.textContent = `Aktualny stav pocitadla: ${counter}`;
document.body.appendChild(span);
});
1.prehliada precita kod ako text
2. parsuje to na AST (Abstract Syntax Tree)
3.optimalizuje to
4. vnutorne si ho za behu to kompiluje do bytecode a za behu kontroluje. Pocas kontroly sa engine pozera ze ktore funkcie su ako casto volane, alebo vypoctovo narocne a podla toho ich optimalizuje
const http = require('http');
const fs = require('fs');
const server = http.createServer((req, res)=>{
if (req.method !== 'GET') {
res.end({"error": "error"}")
}else{
if (req.url === '/indexFile'){
fs.readFile('./index.html', function (err, html) {
if (err) {
throw err;
res.writeHeader (200, "Content-Type": "text/html"});
res.write(html);
}
res.end();
if(req.url === '/response'){
const data = fetch('http://esh.op/order').then(res => res.data);
res.writeHead (200, {'Content-Type': 'application/json'});
res.end(JSON.stringify(data));
}
}
});
server.listen(8080)
function conv(str){
str = str.replaceAll(',',"");
str = str.replaceAll(';', "");
str = str.replaceAll('.',"");
return str.split('').sort((a,b) => b.localeCompare(a))
}

Updating data doesnt expand the data tree inside material-table

Im trying to build a table with nested tree folder inside.
When trying to add nested data into the datasource data the structure will not updated and will not toggle anymore.
Code below:
https://stackblitz.com/edit/angular-table-tree-example-k2zqmt?file=app%2Ftable-basic-example.ts&file=app%2Ftable-basic-example.html,app%2Ftable-basic-example.ts
Environment
Angular:
Material Table
Material tree system
These are the things that are happening when logNode method is called
The item is getting added but the treeControl.toggle method does not work anymore.
When you are assigning a new dataset to the dataSource all the nodes get reset and the tree closes, so this.treeControl.toggle is trying to toggle a node that does not exist.
You need to find the node to be toggled from the list you get from treeControl.dataNodes
I would suggest having the toggle code in a separate method and adding a node code in a separate method, and a separate button to add the node.
The below code should work for your scenario, also remove this line from your HTML, (click)="treeControl.toggle(data)"
interface ExampleFlatNode {
expandable: boolean;
RoleName: string;
Access: boolean;
level: number;
CatId: number;
}
private transformer = (node: FoodNode, level: number) => {
return {
expandable:
!!node.CategoryPermissions && node.CategoryPermissions.length > 0,
RoleName: node.RoleName,
Access: node.Access,
level: level,
CatId: node.CatId,
};
};
tempNodes = []
constructor() {
this.dataSource.data = TREE_DATA;
}
logNode(clickedNode) {
this.tempNodes = [];
this.treeControl.dataNodes.forEach((node) =>
this.tempNodes.push({
...node,
expanded: this.treeControl.isExpanded(node),
})
);
if (!this.treeControl.isExpanded(clickedNode)) {
const temp = {
Access: true,
RoleName: 'test 1 2',
CatId: 113,
};
const clickedNodeIdx = this.treeControl.dataNodes.findIndex(
(node: any) =>
node.CatId === clickedNode.CatId &&
node.RoleName === clickedNode.RoleName &&
node.level === clickedNode.level
);
const childIdx = 1;
let child;
if (clickedNode.level === 0) {
child =
this.dataSource.data[clickedNodeIdx].CategoryPermissions[childIdx];
} else {
this.dataSource.data.forEach(
(item) => (child = this.findDataSource(item, clickedNode))
);
}
child.CategoryPermissions.push(temp);
this.dataSource.data = this.dataSource.data;
const addedNode = this.treeControl.dataNodes.find(
(node: any) =>
node.CatId === temp.CatId && node.RoleName === temp.RoleName
);
this.expandParent(addedNode);
this.setPreviousState();
} else {
this.treeControl.collapse(clickedNode);
}
}
findDataSource(item, node) {
if (item.RoleName === node.RoleName) {
return item;
} else if (item.CategoryPermissions) {
let matchedItem;
item.CategoryPermissions.forEach((e) => {
const temp = this.findDataSource(e, node);
if (temp) {
matchedItem = temp;
}
});
return matchedItem;
}
}
setPreviousState() {
for (let i = 0, j = 0; i < this.treeControl.dataNodes.length; i++) {
if (
this.tempNodes[j] &&
this.treeControl.dataNodes[i].RoleName === this.tempNodes[j].RoleName &&
this.treeControl.dataNodes[i].CatId === this.tempNodes[j].CatId &&
this.treeControl.dataNodes[i].level === this.tempNodes[j].level
) {
if (this.tempNodes[j].expanded) {
this.treeControl.expand(this.treeControl.dataNodes[i]);
}
j++;
}
}
}
expandParent(node: ExampleFlatNode) {
const { treeControl } = this;
const currentLevel = treeControl.getLevel(node);
const index = treeControl.dataNodes.indexOf(node) - 1;
for (let i = index; i >= 0; i--) {
const currentNode = treeControl.dataNodes[i];
if (currentLevel === 0) {
this.treeControl.expand(currentNode);
return null;
}
if (treeControl.getLevel(currentNode) < currentLevel) {
this.treeControl.expand(currentNode);
this.expandParent(currentNode);
break;
}
}
}

Refresh sticky mobile leaderboard ad slot by changing the content URL in infinite scroll

I want to refresh sticky mobile leaderboard slot when the URL changes in infinite scroll. What do you think is the best way to go? Please let me know if you have any suggestions.
class DFPAds {
constructor() {
this.slots = [];
this.onScroll = throttle(this.loopAds, 300);
this.addEvents();
this.createAdObject = this.createAdObject.bind(this);
this.createSlotForAd = this.createSlotForAd.bind(this);
this.displayAd = this.displayAd.bind(this);
}
static get() {
return DFPAds._instance;
}
static set() {
if (!DFPAds._instance) {
DFPAds._instance = new DFPAds();
return DFPAds._instance;
} else {
throw new Error("DFPAds: instance already initialized");
}
}
addEvents() {
window.addEventListener("scroll", e => this.onScroll());
}
loopAds() {
this.slots.map(slot => this.displayAd(slot));
}
createAdObject(ad) {
let id = ad.id;
let attributes = getDataSet(ad);
let sizes = JSON.parse(attributes.sizes);
let sizeMapping;
if (attributes.sizemapping) {
attributes.sizemapping.length
? (sizeMapping = JSON.parse(attributes.sizemapping))
: (sizeMapping = null);
}
attributes.id = id;
attributes.sizes = sizes;
attributes.sizemapping = sizeMapping;
return attributes;
}
createSlotForAd(adObject) {
let {
id,
adtype,
position,
slotname,
sizes,
sizemapping,
shouldlazyload,
pagenumber,
pageid
} = adObject;
googletag.cmd.push(() => {
let slot = googletag.defineSlot(slotname, sizes, id);
if(position){
slot.setTargeting("position", position);
}
if(pagenumber){
slot.setTargeting("pagenumber", pagenumber);
}
if(pageid){
slot.setTargeting("PageID", pageid)
}
if (sizemapping) {
let mapping = googletag.sizeMapping();
sizemapping.map(size => {
mapping.addSize(size[0], size[1])
});
slot.defineSizeMapping(mapping.build());
}
slot.addService(googletag.pubads());
googletag.display(id);
shouldlazyload
? this.slots.push({ slot: slot, id: id })
: googletag.pubads().refresh([slot]);
console.log("SlotTop", slot)
});
}
displayAd(slot) {
console.log("Slottwo", slot)
let item = document.getElementById(slot.id);
if (item) {
let parent = item.parentElement;
let index = this.slots.indexOf(slot);
let parentDimensions = parent.getBoundingClientRect();
if (
(parentDimensions.top - 300) < window.innerHeight &&
parentDimensions.top + 150 > 0 &&
parent.offsetParent != null
) {
googletag.cmd.push(function() {
googletag.pubads().refresh([slot.slot]);
});
this.slots.splice(index, 1);
}
}
}
setUpAdSlots(context = document) {
let ads = [].slice.call(context.getElementsByClassName("Ad-data"));
if (ads.length) {
ads.map(ad => compose(this.createSlotForAd, this.createAdObject)(ad));
}
}
}
export default DFPAds;
And this is my Infinite Scroll code:
export default class InfiniteScroll {
constructor() {
this._end = document.getElementById('InfiniteScroll-End');
this._container = document.getElementById('InfiniteScroll-Container');
if(!this._end || !this._container)
return;
this._articles = { };
this._triggeredArticles = [];
this._currentArticle = '';
this._apiUrl = '';
this._count = 1;
this._timedOut = false;
this._loading = false;
this._ended = false;
this._viewedParameter = "&alreadyViewedContentIds=";
this._articleSelector = "InfiniteScroll-Article-";
this._articleClass = "Article-Container";
this.setStartData();
this.onScroll = throttle(this.eventScroll, 200);
this.addEvents();
}
addEvents(){
setTimeout(()=>{
window.addEventListener('scroll', (e) => this.onScroll() );
}, 2000);
}
addToStore(article){
this._articles["a" + article.id] = article;
}
addToTriggeredArticles(id){
this._triggeredArticles.push(id);
}
getRequestUrl(){
return this._apiUrl + this._viewedParameter + Object.keys(this._articles).map(key => this._articles[key].id).join();
}
getLastArticle(){
return this._articles[Object.keys(this._articles)[Object.keys(this._articles).length-1]];
}
setStartData() {
let dataset = getDataSet(this._container);
if(dataset.hasOwnProperty('apiurl')){
let article = Article.get();
if(article){
this._apiUrl = dataset.apiurl;
this._currentArticle = "a" + article.id;
this.addToStore(article);
}else{
throw(new Error('Infinite Scroll: Article not initialized.'));
}
}else{
throw(new Error('Infinite Scroll: Start object missing "apiurl" property.'));
}
}
eventScroll() {
if(this.isApproachingNext()){
this.requestNextArticle();
}
if(!this.isMainArticle(this._articles[this._currentArticle].node)){
this.updateCurrentArticle();
}
}
eventRequestSuccess(data){
this._loading = false;
if(data != ''){
if(data.hasOwnProperty('Id') && data.hasOwnProperty('Html') && data.hasOwnProperty('Url')){
this.incrementCount();
let node = document.createElement('div');
node.id = this._articleSelector + data.Id;
node.innerHTML = data.Html;
node.className = this._articleClass;
this._container.appendChild(node);
this.initArticleUpNext({
img: data.Img,
title: data.ArticleTitle,
category: data.Category,
target: node.id
});
this.addToStore(
new Article({
id: data.Id,
node: node,
url: data.Url,
title: data.Title,
count: this._count,
nielsenProps: {
section: data.NeilsenSection,
sega: data.NeilsenSegmentA,
segb: data.NeilsenSegmentB,
segc: data.NeilsenSegmentC
}
})
);
}else{
this._ended = true;
throw(new Error('Infinite Scroll: Response does not have an ID, Url and HTML property'));
}
}else{
this._ended = true;
throw(new Error('Infinite Scroll: No new article was received.'));
}
}
eventRequestError(response){
this._loading = false;
this._ended = true;
throw(new Error("Infinite Scroll: New article request failed."));
}
requestNextArticle(){
if(!this._loading){
this._loading = true;
return API.requestJSON(this.getRequestUrl())
.then(
(response)=>{this.eventRequestSuccess(response)},
(response)=>{this.eventRequestError(response)}
);
}
}
triggerViewEvent(article){
if(article.count > 1 && !this.isAlreadyTriggeredArticle(article.id)){
this.addToTriggeredArticles(article.id);
Tracker.pushView(article.url, article.count);
if(isFeatureEnabled('Nielsen') && Nielsen.get()){
Nielsen.get().eventInfiniteScroll({
id: article.id,
url: article.url,
section: article.nielsenProps.section,
sega: article.nielsenProps.sega,
segb: article.nielsenProps.segb,
segc: article.nielsenProps.segc
});
NielsenV60.trackEvent(article.title);
}
}
}
updateCurrentArticle(){
Object.keys(this._articles).map( key => {
if(this._currentArticle !== key && this.isMainArticle(this._articles[key].node)){
this._currentArticle = key;
this.updateUrl(this._articles[key]);
this.triggerViewEvent(this._articles[key]);
}
});
}
updateUrl(article){
try{
if(history.replaceState){
if(window.location.pathname !== article.url){
history.replaceState('', article.title, article.url);
}
}
}catch(e){}
}
incrementCount(){
this._count = this._count + 1;
}
initArticleUpNext(data){
this.getLastArticle().initUpNext(data);
}
isApproachingNext(){
return window.pageYOffset > this._end.offsetTop - (window.innerHeight * 2) && !this._ended && this._end.offsetTop >= 100;
}
isMainArticle(node){
if(node.getBoundingClientRect){
return (node.getBoundingClientRect().top < 80 && node.getBoundingClientRect().bottom > 70);
}else{
return false;
}
}
isAlreadyTriggeredArticle(id){
return this._triggeredArticles.indexOf(id) > -1;
}
}

Number filter custom comparator

Briefly: Is there any way to implement a custom comparator for the number column filters?
Long story:
I use ag-grid in Angular (2). I provide my own components for cells in my ag-grid:
let column = {
headerName: column.header,
field: column.id,
cellRendererFramework: DynamicComponent,
filter: this.convertFormatToFilterType(column.format) // returns "text", "number" or "date"
}
In order to make the filters get values from my cells properly I provide custom comparators (this one is for the text column filters):
if (c.filter === "text")
c['filterParams'] = {
textCustomComparator: (filter, cell, filterText): boolean => {
var filterTextLowerCase = filterText.toLowerCase();
var valueLowerCase = cell.value.toString().toLowerCase();
switch (filter) {
case 'contains':
return valueLowerCase.indexOf(filterTextLowerCase) >= 0;
case 'notContains':
return valueLowerCase.indexOf(filterTextLowerCase) === -1;
case 'equals':
return valueLowerCase === filterTextLowerCase;
case 'notEqual':
return valueLowerCase != filterTextLowerCase;
case 'startsWith':
return valueLowerCase.indexOf(filterTextLowerCase) === 0;
case 'endsWith':
var index = valueLowerCase.lastIndexOf(filterTextLowerCase);
return index >= 0 && index === (valueLowerCase.length - filterTextLowerCase.length);
default:
// should never happen
console.warn('invalid filter type ' + filter);
return false;
}
}
};
You can see I need to access the value of the cell by using "cell.value". The code above works fine.
What I have troubles with is providing similar functionality for the number column filters - they don't seem to use any custom comparator. Therefore, what is happening, the filter tries to access the cell's value directly instead of using "cell.value".
So, is there any way to implement a custom comparator for the number column filters? Or, if not, any other way I can get the value from my cells correctly in this case?
What I ended up doing is implementing a ag-grid custom filter component
import { Component, ViewChild, ViewContainerRef } from '#angular/core';
import { IFilterParams, IDoesFilterPassParams, RowNode, IAfterGuiAttachedParams } from 'ag-grid/main';
import { IFilterAngularComp } from 'ag-grid-angular/main';
// https://www.ag-grid.com/javascript-grid-filter-component/#gsc.tab=0 / Angular Filtering
// create your filter as a Angular component
#Component({
selector: 'filter-cell',
template: `
<select #select (ngModelChange)="onSelectChange($event)" [ngModel]="operator">
<option value="eq">Equals</option>
<option value="neq">Not equal</option>
<option value="lt">Less than</option>
<option value="lte">Less than or equals</option>
<option value="gt">Greater than</option>
<option value="gte">Greater than or equals</option>
<option value="inrange">In range</option>
</select>
<br>
<input #input (ngModelChange)="onChange($event)" [ngModel]="text">
<br>
<div *ngIf='operator === "inrange"'>
<input #input2 (ngModelChange)="onChange2($event)" [ngModel]="text2">
</div>
`,
styles: ['select { margin: 2px 4px; }', 'input { height: 26px; margin: 2px 4px; }']
})
export class GridNumberFilterComponent implements IFilterAngularComp {
private params: IFilterParams;
private valueGetter: (rowNode: RowNode) => any;
public operator: string = 'eq';
public text: string = '';
public text2: string = '';
#ViewChild('select', { read: ViewContainerRef }) public select;
#ViewChild('input', { read: ViewContainerRef }) public input;
#ViewChild('input2', { read: ViewContainerRef }) public input2;
agInit(params: IFilterParams): void {
this.params = params;
this.valueGetter = params.valueGetter;
}
isFilterActive(): boolean {
return this.text !== null && this.text !== undefined && this.text !== '';
}
doesFilterPass(params: IDoesFilterPassParams): boolean {
let cellNumber = Number(this.valueGetter(params.node).value);
let filterNumber = this.text ? Number(this.text) : -Infinity;
let filterNumber2 = this.text2 ? Number(this.text2) : Infinity;
switch (this.operator) {
case 'eq': return cellNumber === filterNumber;
case 'neq': return cellNumber !== filterNumber;
case 'lt': return cellNumber < filterNumber;
case 'lte': return cellNumber <= filterNumber;
case 'gt': return cellNumber > filterNumber;
case 'gte': return cellNumber >= filterNumber;
case 'inrange': return cellNumber >= filterNumber && cellNumber <= filterNumber2;
default: return true;
}
}
getModel(): any {
return { value: this.text };
}
setModel(model: any): void {
this.text = model ? model.value : '';
}
afterGuiAttached(params: IAfterGuiAttachedParams): void {
this.input.element.nativeElement.focus();
}
componentMethod(message: string): void {
alert(`Alert from PartialMatchFilterComponent ${message}`);
}
onSelectChange(newValue): void {
if (this.operator !== newValue) {
this.operator = newValue;
this.params.filterChangedCallback();
}
}
onChange(newValue): void {
if (this.text !== newValue) {
this.text = newValue;
this.params.filterChangedCallback();
}
}
onChange2(newValue): void {
if (this.text2 !== newValue) {
this.text2 = newValue;
this.params.filterChangedCallback();
}
}
}
which I add to my column like this:
let column = {
headerName: column.header,
field: column.id,
cellRendererFramework: DynamicComponent,
filterFramework: GridNumberFilterComponent
}

TypeError: 'undefined' is not a function (evaluating 'ime.registIMEKey()')

Despite of setting and defining everything in Samsung smart TV SDK 4.0 I am getting this error:
TypeError: 'undefined' is not a function (evaluating 'ime.registIMEKey()')
Please help!
CODE:
var widgetAPI = new Common.API.Widget();
var tvKey = new Common.API.TVKeyValue();
var wapal_magic =
{
elementIds: new Array(),
inputs: new Array(),
ready: new Array()
};
/////////////////////////
var Input = function (id, previousId, nextId) {
var previousElement = document.getElementById(previousId),
nextElement = document.getElementById(nextId);
var installFocusKeyCallbacks = function () {
ime.setKeyFunc(tvKey.KEY_UP, function (keyCode) {
previousElement.focus();
return false;
});
ime.setKeyFunc(tvKey.KEY_DOWN, function (keyCode) {
nextElement.focus();
return false;
});
ime.setKeyFunc(tvKey.KEY_RETURN, function (keyCode) {
widgetAPI.blockNavigation();
return false;
});
ime.setKeyFunc(tvKey.KEY_EXIT, function (keyCode) {
widgetAPI.blockNavigation();
return false;
});
}
var imeReady = function (imeObject) {
installFocusKeyCallbacks();
wapal_magic.ready(id);
},
ime = new IMEShell(id, imeReady, 'en'),
element = document.getElementById(id);
}
wapal_magic.createInputObjects = function () {
var index,
previousIndex,
nextIndex;
for (index in this.elementIds) {
previousIndex = index - 1;
if (previousIndex < 0) {
previousIndex = wapal_magic.inputs.length - 1;
}
nextIndex = (index + 1) % wapal_magic.inputs.length;
wapal_magic.inputs[index] = new Input(this.elementIds[index],
this.elementIds[previousIndex], this.elementIds[nextIndex]);
}
};
wapal_magic.ready = function (id) {
var ready = true,
i;
for (i in wapal_magic.elementIds) {
if (wapal_magic.elementIds[i] == id) {
wapal_magic.ready[i] = true;
}
if (wapal_magic.ready[i] == false) {
ready = false;
}
}
if (ready) {
document.getElementById("txtInp1").focus();
}
};
////////////////////////
wapal_magic.onLoad = function()
{
// Enable key event processing
//this.enableKeys();
// widgetAPI.sendReadyEvent();
this.initTextBoxes(new Array("txtInp1", "txtInp2"));
};
wapal_magic.initTextBoxes = function(textboxes){
this.elementIds = textboxes;
for(i=0;i<this.elementIds.length;i++){
this.inputs[i]=false;
this.ready[i]=null;
}
this.createInputObjects();
widgetAPI.registIMEKey();
};
wapal_magic.onUnload = function()
{
};
wapal_magic.enableKeys = function()
{
document.getElementById("anchor").focus();
};
wapal_magic.keyDown = function()
{
var keyCode = event.keyCode;
alert("Key pressed: " + keyCode);
switch(keyCode)
{
case tvKey.KEY_RETURN:
case tvKey.KEY_PANEL_RETURN:
alert("RETURN");
widgetAPI.sendReturnEvent();
break;
case tvKey.KEY_LEFT:
alert("LEFT");
break;
case tvKey.KEY_RIGHT:
alert("RIGHT");
break;
case tvKey.KEY_UP:
alert("UP");
break;
case tvKey.KEY_DOWN:
alert("DOWN");
break;
case tvKey.KEY_ENTER:
case tvKey.KEY_PANEL_ENTER:
alert("ENTER");
break;
default:
alert("Unhandled key");
break;
}
};
The registIMEKey method is part of the Plugin API.
var pluginAPI = new Common.API.Plugin();
pluginAPI.registIMEKey();
See: http://www.samsungdforum.com/Guide/ref00006/common_module_plugin_object.html#ref00006-common-module-plugin-object-registimekey
Edit: Updated to add code solution.
widgetAPI no contains method registIMEKey();, it contains in IMEShell.