infinite scrolling using AgGridReact - react-redux

I'm trying to achieve infinite scrolling using ag grid react component, but it doesn't seems to be working.
here is my implementation :
import { AgGridReact } from 'ag-grid-react';
import 'ag-grid/dist/styles/ag-grid.css';
import 'ag-grid/dist/styles/ag-theme-balham.css';
class TasksGridContainer extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: true,
gridOptions: {
//virtual row model
rowModelType: 'infinite',
paginationPageSize: 100,
cacheOverflowSize: 2,
maxConcurrentDatasourceRequests: 2,
infiniteInitialRowCount: 1,
maxBlocksInCache: 2,
components: {
loadingRenderer: function(params) {
console.log('loadingCellRenderer', params);
if (params.value !== undefined) {
return params.value;
} else {
return '<img src="https://raw.githubusercontent.com/ag-grid/ag-grid-docs/master/src/images/loading.gif">';
}
}
},
defaultColDef: {
editable: false,
enableRowGroup: true,
enablePivot: true,
enableValue: true
}
}
};
}
componentDidMount() {
this.props.actions.getAssignedTasks();
this.props.actions.getTeamTasks();
}
componentWillReceiveProps(newProps) {
if (this.props.taskView.taskGrid.listOfTasks.length > 0) {
this.setState({
loading: false ,
gridOptions: {
datasource: this.props.taskView.taskGrid.listOfTasks
}
});
}
}
render() {
return (
<div id="tasks-grid-container">
<div style={Style.agGrid} id="myGrid" className="ag-theme-balham">
<AgGridReact
columnDefs={this.props.taskView.taskGrid.myTaskColumns}
rowData={this.props.taskView.taskGrid.listOfTasks}
gridOptions={this.state.gridOptions}>
</AgGridReact>
</div>
</div>
);
}
}
TasksGridContainer.propTypes = {
listOfTasks: PropTypes.array,
actions: PropTypes.object
};
const mapStateToProps = ({ taskView }) => {
return {
taskView: {
taskGrid: {
listOfTasks: taskView.taskGrid.listOfTasks,
myTaskColumns: taskView.taskGrid.myTaskColumns,
teamTaskColumns: taskView.taskGrid.teamTaskColumns
}
}
}
};
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(taskGridActions, dispatch)
};
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(TasksGridContainer);
columnDefs are being set once props.taskView.taskGrid.myTaskColumns is available.
a sample columndef:
[
{
cellRenderer: "loadingRenderer", checkboxSelection: true, field: "action", headerCheckboxSelection: true, headerCheckboxSelectionFilteredOnly: true, headerName: "Action"
},
{
"activity"headerName: "Activity Name"
}
]
Although grid is loading fine, but when i scroll it should call "loadingRenderer" component. But,I'm not able to see any loading gif when i scroll.
Am i doing something wrong in implementation?

Actual issue was not calling the the props properly and was not having onGridReady function to use gridAPi.
I modified the code and it starts working:
<AgGridReact
components={this.state.components}
enableColResize={true}
rowBuffer={this.state.rowBuffer}
debug={true}
rowSelection={this.state.rowSelection}
rowDeselection={true}
rowModelType={this.state.rowModelType}
paginationPageSize={this.state.paginationPageSize}
cacheOverflowSize={this.state.cacheOverflowSize}
maxConcurrentDatasourceRequests={this.state.maxConcurrentDatasourceRequests}
infiniteInitialRowCount={this.state.infiniteInitialRowCount}
maxBlocksInCache={this.state.maxBlocksInCache}
columnDefs={this.props.columns}
rowData={this.props.rowData}
onGridReady={this.onGridReady}
>
</AgGridReact>
state :
this.state = {
components: {
loadingRenderer: function(params) {
if (params.value !== undefined) {
return params.data.action;
} else {
return '<img src="https://raw.githubusercontent.com/ag-grid/ag-grid-docs/master/src/images/loading.gif">';
}
}
},
rowBuffer: 0,
rowSelection: "multiple",
rowModelType: "infinite",
paginationPageSize: 100,
cacheOverflowSize: 2,
maxConcurrentDatasourceRequests: 2,
infiniteInitialRowCount: 1,
maxBlocksInCache: 2
};
onGridReady function :
onGridReady = (params, data = []) => {
this.gridApi = params;
this.gridColumnApi = params.columnApi;
this.updateData(params,data);
}

Related

Pagination Apollo Client 3 - Cache merges but does not render new results when paginating

I'd love to implement nested pagination within my application. I have been reading the docs and looking at several other examples but I just can't get this to work - any help is appreciated! Thanks!
React component:
I am clicking the button to run the fetchMore function provided by the useQuery hook (apollo). The network request is going through and the new products are merged into the cache... but no new products render on the page.
export const FilterableKit = () => {
const selectedKitId = useReactiveVar(selectedKitIdVar);
const [
getKitProducts,
{ data: getKitProductsData, loading: getKitProductsLoading, fetchMore },
] = useGetKitProductsLazyQuery();
useEffect(() => {
if (selectedKitId) {
getKitProducts({
variables: {
getKitsInput: {
_id: {
string: selectedKitId,
filterBy: "OBJECTID" as StringFilterByEnum,
},
},
getProductsInput: {
config: {
pagination: {
reverse: true,
limit: 3,
},
},
},
},
});
}
}, [getKitProducts, selectedKitId]);
const kitProducts = getKitProductsData?.getKits.data?.find(
(kit) => kit?._id === selectedKitId
)?.products.data;
const handleLoadMore = () => {
if (kitProducts && kitProducts?.length > 0) {
const remaining =
getKitProductsData?.getKits.data[0]?.products.stats?.remaining;
if (remaining && remaining > 0) {
const cursor =
kitProducts[kitProducts.length - 1] &&
kitProducts[kitProducts.length - 1]?.createdAt;
fetchMore({
variables: {
getProductsInput: {
config: {
pagination: {
reverse: true,
createdAt: cursor,
},
},
},
},
});
}
}
};
return (
<CContainer>
<KitItemCards products={kitProducts} loading={getKitProductsLoading} />
<CContainer className="d-flex justify-content-center my-3">
<CButton color="primary" className="w-100" onClick={handleLoadMore}>
Load More
</CButton>
</CContainer>
</CContainer>
);
};
Type Policies: I define the "Kit" typePolicy to merge products into the correct field.
export const cache: InMemoryCache = new InMemoryCache({
typePolicies: {
Kit: {
fields: {
products: {
keyArgs: false,
merge(existing = [] as Product[], incoming: GetProductsResponse) {
if (!incoming) return existing;
if (!existing) return incoming;
const { data: products, ...rest } = incoming;
let result: any = rest;
result = [...existing, ...(products ?? [])];
return result;
},
},
},
},
});
Thanks for any pointers in the right direction! Let me know if there is something else you'd like to see.

Angular unit testing for the promises

I am trying to write the test cases for below method :-
The below is the component code for adding the product
COMPONENT:
import { HttpClient } from '#angular/common/http';
import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '#angular/forms';
import { Observable } from 'rxjs';
import { Constants } from '../../../../../../utils/constants';
import { FileUploadService } from '../../../services/file-upload.service';
import { Component, EventEmitter, Input, OnInit, Output } from '#angular/core';
import { ManagePackageService } from '../../../services/manage-package.service';
import { DomSanitizer } from '#angular/platform-browser';
import { Package } from '../../../models/package.model';
import { ProductPackageMapping } from '../../../models/product-package-mapping.model';
import { Product } from '../../../models/product.model';
import { NotificationAlertService } from '../../../../shared/services/notification-alert.service';
import { ManageProductService } from '../../../services/manage-product.service';
import { DocumentDTO } from '../../../models/document-dto';
import { saveAs } from 'file-saver';
import { ConfirmationDialogService } from '../../../../shared/services/confirmation-dialog.service';
#Component({
selector: 'app-add-product',
templateUrl: './add-product.component.html',
styleUrls: ['./add-product.component.scss']
})
export class AddProductComponent implements OnInit {
productDetailForm: FormGroup;
addTitle: boolean = true;
isView: boolean = true;
submitted: boolean = false;
status: boolean = true;
check: boolean = false;
changevar: string;
#Input() id: Number;
#Output() changeIndicator = new EventEmitter<string>();
#Input() list: ProductPackageMapping;
#Output() dataLoaded = new EventEmitter<string>();
fileToUpload: File | null = null;
fileArray: Array<any> = [];
productData: any = [];
productDataTemp: any[];
fData = new FormData();
packagePlan: Array<any> = [];
productDto = new Product();
packageDto = new Package();
productPackageMappingDto = new ProductPackageMapping();
documentDto: Array<any> = [];
isDocId: boolean = false;
productForm: FormGroup;
activeStatus: boolean = true;
requiredFileType: string;
public NAME_MAX_LENGTH = 50;
isValidFormSubmitted: boolean;
#Input() allRolesFromParent: Product[] = [];
selectedFiles?: FileList;
message: string[] = [];
fileInfos?: Observable<any>;
documentList: Array<any> = [];
ppm: any;
fileUrl: any;
constructor(private http: HttpClient,
private formBuilder: FormBuilder,
private uploadService: FileUploadService,
private manageProduct: ManageProductService,
private managePackageService: ManagePackageService,
private sanitizer: DomSanitizer,
private notifyService: NotificationAlertService,
private confirmationDialogService: ConfirmationDialogService
) { }
ngOnInit(): void {
this.createForm();
this.fileInfos = this.uploadService.getFiles();
this.fData = new FormData();
this.subscriptionPackages()
}
closeModal() {
this.status = true;
this.check = false;
this.changevar = 'cancel';
this.changeIndicator.emit(this.changevar);
this.productForm.reset();
this.fData.delete('ppm');
this.fData.delete('file');
this.emit();
}
closeEditModel() {
this.status = true;
this.documentList = [];
this.changevar = "cancel";
this.changeIndicator.emit(this.changevar);
this.addTitle = true;
this.productForm.reset();
this.fData.delete("ppm");
this.fData.delete("file");
this.emit();
}
onChangeSwitch(event) {
if (event.target.checked == true) {
this.activeStatus = true;
} else {
this.activeStatus = false;
}
}
createForm() {
this.productForm = new FormGroup({
name: new FormControl(this.productDto.name, [
Validators.required,
Validators.maxLength(this.NAME_MAX_LENGTH),
Validators.pattern(Constants.NO_WHITE_SPACE_PATTERN),
]),
url: new FormControl(this.productDto.demoURL, Validators.required),
version: new FormControl(this.productDto.productVersion, Validators.required),
projectType: new FormControl(this.productDto.projectType, Validators.required),
plansName: new FormControl(this.packageDto.id, Validators.required),
desc: new FormControl(this.productDto.productDescription, Validators.required),
active: new FormControl(this.productDto.active, Validators.required),
documents: this.formBuilder.array([]),
});
}
ngOnChanges() {
this.productData = this.list;
if (this.id) {
this.addTitle = false;
this.productDataTemp = this.productData.filter((vl) => (this.id == vl.id));
this.productDataTemp.forEach(obj => {
this.productDto.ppmId = obj.ppmId;
this.productDto.id = obj.id;
this.productDto.name = obj.name;
this.productDto.demoURL = obj.demoURL;
this.productDto.productDescription = obj.productDescription;
this.productDto.projectType = obj.type;
this.productDto.active = obj.active;
this.productDto.productVersion = obj.version;
this.packageDto.id = obj.pkgInfo.id;
this.documentList = obj.listDocuments
})
this.createForm();
} else {
this.productDto = new Product();
this.packageDto = new Package();
this.documentDto = [];
this.documentList = [];
}
this.documentList.length == 0 ? this.isDocId = false : this.isDocId = true;
}
get f() {
return this.productForm.controls;
}
choosePackages(e) {
}
subscriptionPackages() {
this.managePackageService.getPackages().subscribe(
(response) => {
this.packagePlan = response;
},
(httpErrorRes) => {
}
);
}
get documentsFormArray(): FormArray {
return this.f['documents'] as FormArray;
}
addDocument() {
if (this.documentsFormArray.length < 5) {
this.documentsFormArray.push(this.formBuilder.group({
id: new FormControl(''),
docName: new FormControl('', Validators.required),
docType: new FormControl('', Validators.required),
attachment: new FormControl('', Validators.required)
}))
}
}
deleteRow(i: number) {
if (this.id) {
delete this.fileArray[i];
this.documentsFormArray.removeAt(i);
this.fileArray.splice(i, 1);
}
else {
delete this.documentsFormArray[i];
this.documentsFormArray.removeAt(i);
this.fileArray.splice(i, 1);
}
}
getFiles(fileArray) {
this.fileArray.forEach(files => {
this.fData.append("file", files);
})
}
selectFiles(event): void {
this.selectedFiles = event.target.files;
}
onFormSubmit() {
this.submitted = true;
if (this.fileArray.length > 0) {
this.getFiles(this.fileArray);
}
if (this.productForm.value.id === null) {
this.productDto.id = null;
}
this.productDto.name = this.productForm.value.name;
this.productDto.demoURL = this.productForm.value.url;
this.productDto.productVersion = this.productForm.value.version;
this.productDto.projectType = this.productForm.value.projectType;
this.productDto.productDescription = this.productForm.value.desc;
this.productDto.active = this.productForm.value.active;
this.packageDto.id = this.productForm.value.plansName;
if (this.documentsFormArray && this.documentsFormArray.value && this.documentsFormArray.value.length > 0) {
this.documentsFormArray.value.forEach(element => {
this.documentDto.push(element);
});
}
this.productPackageMappingDto.id = this.productDto.ppmId ? this.productDto.ppmId : null;
this.productPackageMappingDto.products = this.productDto;
this.productPackageMappingDto.packages = this.packageDto;
this.productPackageMappingDto.document = this.documentDto;
this.ppm = new Blob([JSON.stringify(this.productPackageMappingDto)], { type: "application/json" });
this.fData.has("file") ? true : this.fData.append("file", '');
this.fData.append("ppm", this.ppm);
this.confirmationDialogService.confirm('Are you sure ?', 'Do you really want to Activate/ Deactivate Package ?')
.then((confirmed) => {
if (confirmed) {
this.manageProduct.createProduct(this.fData).subscribe(
(response) => {
this.notifyService.showSuccess("Data saved successfully !!", "Success")
this.closeModal();
this.fData.delete("ppm");
this.fData.delete("file");
this.productDto = new Product();
this.packageDto = new Package();
this.documentDto = [];
this.documentList = [];
this.fileArray = [];
this.emit();
},
(httpErrorRes) => {
this.fData.delete("ppm");
this.fData.delete("file");
this.notifyService.showError(httpErrorRes.error, "Error");
}
);
}
else {
this.fData.delete("ppm");
this.fData.delete("file");
}
})
}
upload(event: any) {
let files = event.target.files[0];
this.fileArray.push(files);
}
downloadFile(fileData: DocumentDTO): void {
this.manageProduct.download(fileData.fileName)
.subscribe(blob => saveAs(blob, fileData.fileName));
}
emit() {
this.dataLoaded.emit('AddProductComponent')
}
}
The below is the spec file for the above component
COMPONENT.SPEC.TS
import { HttpClientTestingModule } from '#angular/common/http/testing';
import { ComponentFixture, TestBed } from '#angular/core/testing';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { NgSelectModule } from '#ng-select/ng-select';
import { TranslateModule } from '#ngx-translate/core';
import { ToastrModule } from 'ngx-toastr';
import { Observable, of, throwError } from 'rxjs';
import { AppConfig } from '../../../../../app.config';
import { ConfirmationDialogService } from '../../../../configurations/services/confirmation-dialog.service';
import { NotificationAlertService } from '../../../../shared/services/notification-alert.service';
import { DocumentDTO } from '../../../models/document-dto';
import { ManagePackageService } from '../../../services/manage-package.service';
import { ManageProductService } from '../../../services/manage-product.service';
import { AddProductComponent } from './add-product.component';
describe('AddProductComponent', () => {
let component: AddProductComponent;
let fixture: ComponentFixture<AddProductComponent>;
let packageServiceStub = jasmine.createSpyObj('ManagePackageService', ['getPackages']);
let confirmationDialougeStub = jasmine.createSpyObj('ConfirmationDialogService', ['confirm']);
let manageProductStub = jasmine.createSpyObj('ManageProductService', ['createProduct','download']);
let notificationStub = jasmine.createSpyObj('NotificationAlertService', ['showSuccess', 'showError']);
let fileData = new DocumentDTO()
class MockUserService {
getPackages() {
return [{'name':'basic', 'id':1}, {'name':'advance', 'id':2}, {'name':'premium', 'id':3}];
}
}
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [HttpClientTestingModule,ReactiveFormsModule,FormsModule,ToastrModule.forRoot(),
TranslateModule.forRoot(), NgSelectModule],
declarations: [ AddProductComponent ],
providers :[AppConfig,
{ provide: ManageProductService, useValue: manageProductStub },
{ provide: ManagePackageService, useValue: packageServiceStub }]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(AddProductComponent);
component = fixture.componentInstance;
const mockpackageResponse = [];
packageServiceStub.getPackages.and.returnValue(of(mockpackageResponse));
component.activeStatus = true;
component.productDataTemp = [{'ppmId': 2, 'id': 2, 'name': 'ttvrtv', 'productDescription': 'vttvt', 'projectType': null}]
component.productData = [{'ppmId': 1, 'id': 1, 'name': 'tvrtv', 'productDescription': 'vtvt', 'projectType': null},
{'ppmId': 2, 'id': 2, 'name': 'ttvrtv', 'productDescription': 'vttvt', 'projectType': null}]
component.id = 2;
component.fileArray = ['dfdfdf','dfdsdf']
component.packagePlan = [{'name':'basic', 'id':1}, {'name':'advance', 'id':2}, {'name':'premium', 'id':3}];
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should create closeModal()', () => {
const eventTrue = {
target: {
checked: true,
}
};
const eventFalse = {
target: {
checked: false,
}
};
component.closeModal();
component.closeEditModel();
component.onChangeSwitch(eventTrue)
component.onChangeSwitch(eventFalse)
});
// it('should create ngOnChanges', () => {
// component.ngOnChanges();
// });
it('should create choosePackages', () => {
const event = {};
component.choosePackages(event);
});
it('should create subscriptionPackages()', () => {
component.subscriptionPackages();
expect(component.packagePlan).toBeTruthy();
});
// it('should handle error subscriptionPackages', () => {
// spyOn(window, 'alert');
// packageServiceStub.getPackages.and.returnValue(throwError({ error: 'some error'}));
// component.subscriptionPackages();
// });
it('should create addDocument()', () => {
component.addDocument();
});
it('should create deleteRow(i: number)', () => {
component.id = 1;
component.deleteRow(1);
});
it('should create deleteRow(i: number) nullity check', () => {
component.id = null;
component.deleteRow(null);
});
it('should create selectFiles', () => {
const eventSelectFiles = {
target: {
files: 'ss',
}
};
component.selectFiles(eventSelectFiles);
});
it('should create onFormSubmit length>0', () => {
component.fileArray.length = 2;
component.onFormSubmit();
});
it('should create onFormSubmit nullity check', () => {
component.productForm.value.id = null
component.onFormSubmit();
});
// it('should create onFormSubmit else part', (done) => {
// const confirmationResponse = {}; // Keep is as your response
// let spy = spyOn( confirmationDialougeStub,'confirm').and.returnValue(Promise.resolve(true));
// // fixture.whenStable().then(confirmed => {
// // fixture.detectChanges();
// // component.onFormSubmit();
// // })
// spy.calls.mostRecent().returnValue.then(() => {
// fixture.detectChanges();
// //component.onFormSubmit();
// done();
// });
// });
// it('should create onFormSubmit else part', () => {
// confirmationDialougeStub.confirm.and.returnValue({
// closePromise : {
// then : function(callback) {
// callback({value: true});
// }
// }
// });
// });
it('should create upload download', () => {
const eventFile = {
target: {
files: true,
}
};
component.upload(eventFile);
});
});
PROBLEM :- I have some below pointers regarding Jasmine and trying to figure it out the way to achieve the same.
I am not able to figured out the way to cover or test what the promises return
this.confirmationDialogService.confirm('Are you sure ?', 'Do you really want to Activate/ Deactivate Package ?')
.then((confirmed) => {
In the code coverage, the above lines are not covered and I don't know what went wrong, I have tried so many ways and referred many of the posts from stackoverflow itself but that weren't work for my use case.
how to write test cases for a method having foreach(), filter(), map()
how to write test cases for a method having observables, subscribe and promises
Please suggest the best practices.
Your component is quite long, I will show you how to you can cover the following lines with Promises and Observables.
this.confirmationDialogService.confirm('Are you sure ?', 'Do you really want to Activate/ Deactivate Package ?')
.then((confirmed) => {
if (confirmed) {
this.manageProduct.createProduct(this.fData).subscribe(
Make the following changes:
// dialog is spelt wrong here
let confirmationDialogStub = jasmine.createSpyObj('ConfirmationDialogService', ['confirm']);
....
providers: [
...
// provide the mock for confiramtion dialog service
{ provide: ConfirmationDialogService, usevalue: confirmationDialogStub },
{ provide: ManageProductService, useValue: manageProductStub },
{ provide: ManagePackageService, useValue: packageServiceStub }
],
// fakeAsync because we need tick (wait for promises to resolve before asserting)
it('does abc', fakeAsync(() => {
// mock the value for confirmation with a promise
confirmationDialogStub.confirm.and.returnValue(Promise.resovle(true));
manageProductStub.createProduct.and.returnValue(of({/* mock however you wish */}));
// call the method
component.onFormSubmit();
// wait for all promises to resolve with tick
tick();
// do your assertions
}));
For forEach, filter, map, make sure you mock an array. That the array is there.
That being said, I highly recommend you check out the following resources, they will help you a lot.
https://testing-angular.com/
^ A well written e-book.
https://www.pluralsight.com/courses/unit-testing-angular
^ A really good class on unit testing in Angular.

Vue3 composition API refactor computed favoritesRecipes

I am new to composition API with vue3. I have created that computed property and I would like to have that computed variable in a different file, I'm not sure if I should create a new component or I could achieve it from a js file.
Here is the component working (I did it with setup()):
export default {
name: "Recipes",
setup() {
const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
const favoritesRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter(item => {
return item.favorite;
});
}
return tempFavs;
...
});
...
}
return {
...toRefs(state),
favoriteRecipes
}
// end of setup
}
You can split it into two files
state.js
export const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
export const favoriteRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter(item => {
return item.favorite;
});
}
return tempFavs;
})
and recipes.vue
import { state, favoriteRecipes } from "state.js";
export default {
name: "Recipes",
setup() {
return {
...toRefs(state),
favoriteRecipes,
};
},
};
But this will make the state persistent, so if you have multiple components, they will all have the same favoriteRecipes and state values.
If you want them to be unique for each component...
state.js
export const withState = () => {
const state = reactive({
recipes: [],
sortBy: "alphabetically",
ascending: true,
searchValue: "",
});
const favoriteRecipes = computed(() => {
let tempFavs = state.recipes;
// Show only favorites
if (state.heart) {
tempFavs = tempFavs.filter((item) => {
return item.favorite;
});
}
return tempFavs;
});
return { state, favoriteRecipes };
};
and recipes.vue
import { withState } from "state.js";
export default {
name: "Recipes",
setup() {
const {state, favoriteRecipes} = withState()
return {
...toRefs(state),
favoriteRecipes,
};
},
};

d3-drag - “Cannot read property 'button' of null”

I am using the neo4j-d3 charts in my application as follows. I did checkout the git repo of Neo4j-d3 and created seperate repo as 'neo4jd3'
import { Component } from 'react'
import { Redirect } from 'react-router-dom';
import Neo4jd3 from 'neo4jd3';
import * as d3 from 'd3';
class xxx extends Component {
constructor(props) {
super(props);
this.getAllValues = this.getAllValues.bind(this);
this.nodeRadius = 30
}
componentWillMount() {
this.getAllValues ();
}
createGraphObj(data) {
let nodeRadius = this.nodeRadius
let neo4jd3Obj = new Neo4jd3('#graphId', {
nodeLetters: {
'Test': (node) => {
return 'Check'
},
'Check': function (node) {
return 'Check'
}
},
nodeColors: {
'TestColor': 'rgba(250,204,163,1)',
'CheckColor': 'rgba(124,212,248,1)',
},
nodeOutlineColors: {
'TestColor': 'rgba(245,151,67,1)',
'CheckColor': 'rgba(18,147,212,1)',
},
infoPanel: false,
minCollision: nodeRadius,
neo4jData: data,
nodeRadius: nodeRadius,
nodeletterSize: 16,
onNodeClick: (node) => {
}
})
}
getAllValues () {
this.xxx.getAllValues().then(res => {
// console.log('----- getAllDomains -----', res)
let temp = {
"results": [{
"data": [{
"graph": res[0]
}]
}]
}
this.createGraphObj(temp);
})
}
render() {
return (
<div>
<div>
<div id="graphId"></div>
</div>
</div>
)
}
}
export default xxx
. Grpah wise all are fine, but at the time of dragging I am getting the following error
Cannot read property 'button' of null
from the following code
function defaultFilter() {
return !d3Selection.event.button;
}
I saw the following link
d3-drag 0.3.0 - "Cannot read property 'button' of null"
but no luck :(
Help me out for this one.
Thanks in advance

Is there a simple way of implementing a column picker for a List?

We are going to implement a columnpicker and currently the only idea I have is to implement a ColumnPickableList that wraps a List. This would also hold a list of checkboxes that will enable the user to hide a column.
But before I go ahead do that I just wondered if I'm reinveting the wheel and if there is a simpler approach to solving this?
No simpler way. You'll have to implement your own List component for that
I'm following up on this since I'm struggling to make this work. Maybe it is because I have chosen to create a wrapper that filters the children to be displayed. So techically this approach doesn't implement its own List.
I have made a naive draft which I was hoping would work, but it fails to re-render the children even though they are changed/filtered in the parent component.
The console.log(..) in ColumnPickableList render()-function does print the correct children/props, but still the children won't update/re-render. Any clues as to why? Is this approach too naive?
So here is the current draft:
ColumnPicker.js
import React, { PropTypes } from 'react';
import Checkbox from 'material-ui/Checkbox';
export default class ColumnPicker extends React.Component {
constructor(props) {
super(props);
this.onCheck = this.onCheck.bind(this);
}
onCheck(column, isChecked) {
return this.props.onCheckboxChanged(column, isChecked);
}
renderCheckbox(column, onCheck) {
const disabled = (column.source === 'id');
return (<Checkbox key={column.source} label={column.source.toUpperCase()} onCheck={(event, checked) => onCheck(column, checked)} defaultChecked disabled={disabled} />);
}
render() {
const columns = this.props.columns || [];
return (
<div className="column-picker">
{columns.map((column) => {
return this.renderCheckbox(column, this.onCheck);
})}
</div>
);
}
}
ColumnPicker.propTypes = {
columns: PropTypes.array,
onCheckboxChanged: PropTypes.func,
};
ColumnPicker.defaultProps = {
columns: [], // [{source: myField, checked: true} ...]
};
ColumnPickableList.js:
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import { List, Datagrid } from 'admin-on-rest';
import ColumnPicker from './ColumnPicker';
import { toggleColumnPickerStatusAction, initializeColumnPickerAction } from './actions';
export class ColumnPickableList extends React.Component {
componentWillMount() {
let columnSourceNames = [];
if (this.props.children) {
columnSourceNames = React.Children.map(this.props.children, (child) => {
return ({ source: child.props.source, checked: true });
});
}
const columnsDisplayed = columnSourceNames.filter((column) => column.source);
this.props.initializeColumnPicker(this.props.resource, columnsDisplayed);
}
shouldComponentUpdate(nextProps) {
const diff = nextProps.columnsDisplayed.filter((currentColumn) => {
return !this.props.columnsDisplayed.some((prevColumn) => {
return currentColumn.source === prevColumn.source && currentColumn.checked === prevColumn.checked;
});
});
return diff.length > 0;
}
removeHiddenColumns(children) {
return React.Children.map(children, (child) => {
if (!child.props.source) {
return child;
}
const column = this.props.columnsDisplayed.find((columnDisplayed) => {
return columnDisplayed.source === child.props.source;
});
if (this.props.columnsDisplayed.length === 0 || (column && column.checked)) {
return React.cloneElement(child);
}
return null;
});
}
render() {
const { children, ...rest } = this.props;
const displayedChildren = this.removeHiddenColumns(children);
console.log('Does it render? Rendering children', displayedChildren.map((child) => child.props.source));
return (
<div className="columnpickable-list">
<ColumnPicker columns={this.props.columnsDisplayed} onCheckboxChanged={this.props.handleCheckboxChanged} />
<List {...rest}>
<Datagrid>
{displayedChildren}
</Datagrid>
</List>
</div>
);
}
}
ColumnPickableList.propTypes = {
resource: PropTypes.string,
columnsDisplayed: PropTypes.array,
children: PropTypes.node,
initializeColumnPicker: PropTypes.func,
handleCheckboxChanged: PropTypes.func,
};
ColumnPickableList.defaultProps = {
columnsDisplayed: [],
};
function mapStateToProps(state) {
return {
columnsDisplayed: state.columnsDisplayed || [],
};
}
actions.js:
export const actions = {
INIT_COLUMNPICKER: 'INIT_COLUMNPICKER',
TOGGLE_COLUMNPICKER_STATUS: 'UPDATE_COLUMNPICKER_STATUS',
UPDATE_COLUMNPICKER_STATUSES: 'UPDATE_COLUMNPICKER_STATUSES',
}
export function initializeColumnPickerAction(resource, columns) {
return {
type: actions.INIT_COLUMNPICKER,
columns,
meta: { resource },
};
}
export function toggleColumnPickerStatusAction(column) {
return {
type: actions.TOGGLE_COLUMNPICKER_STATUS,
column,
};
}
reducers.js:
import { actions } from './actions';
function columnPickerReducer(state = [], action) {
switch (action.type) {
case actions.INIT_COLUMNPICKER: {
console.log('Init columnopicker reducer');
return action.columns;
}
case actions.TOGGLE_COLUMNPICKER_STATUS: {
const columns = state.map((column) => {
if (column.source === action.column.source) {
return { ...column, checked: !column.checked };
}
return column;
});
return columns;
}
default:
return state;
}
}
export default columnPickerReducer;
Example snippet of parent component:
...
<ColumnPickableList title="SillyStuff" {...props}>
<TextField source="id" />
<TextField source="NAME" />
<TextField source="SILLY_NAME" />
<TextField source="CHANGED_BY" />
<DateField source="CHANGED_TS" showTime />
<EditButton />
<DeleteButton />
</ColumnPickableList>
...

Resources