How to get ag-grid table to break up an array of strings into multiple rows in same column - ag-grid

Learning ag-grid and ran into this issue. Calling a service to fill in a table. But my last column "Mode" returns an array. I want that to be broken so "Mode1" is on first row. Then "Mode2" on second row. All the previous columns for can be blank on the "Mode1" row.
Currently it print out Mode1Mode2 cause I concatenate the mode strings on the first row. I did use a rowspan that does evaluate to 2. But not sure how to force it to force the text to actually take advantage of that. Any help is appreciated and thank you.
public columnDefs;
public defaultColDef;
public rowData: DataObj[];
public title = 'Grid';
constructor(private service: DataService) {
this.columnDefs = this.createColumnDefs();
}
// on init, subscribe to the athelete data
ngOnInit() {
this.service.loadProcesses().subscribe(data => {
this.rowData = data;
console.log('data',data);
},
error => {
console.log(error);
}
)
}
onGridReady(params) {
this.gridApi = params.api;
this.gridColumnApi = params.columnApi;
this.gridApi.sizeColumnsToFit();
} // on GridReady ends here
private createColumnDefs(){
return [
{ headerName:"IDs", field:"id", width:80 },
{ headerName:"Process", field:"content", width:120,rowSpan: calculateRowSpan },
{ headerName:"Requirement", field:"req", width:160,rowSpan: calculateRowSpan },
{ headerName:"Comment", field:"comment", width:120,rowSpan: calculateRowSpan },
{ headerName:"Mode",
field:"fModes",
valueGetter: function generateModeField(params) {
let modesCount = params.data.fModes.length;
let returnValue = '';
for (let x=0;x<modesCount;x++){
returnValue = returnValue + params.data.fModes[x].name;
}
return returnValue;
},
rowSpan: calculateRowSpan,
width:200 }
];
}
Notice it takes up rowspan of 2

Related

ag-grid: highlight duplicated cell values

I am using ag-grid and I would like to highlight cells in a column if the cell values for a particular column are duplicated. The duplicated cells should be highlighted using a red border.
You can achieve the duplicate highlighting by iterating through the row data to detect the duplicate values and then pass the detected duplicates to a custom cellStyle function.
defaultColDef = {
cellStyle: function(params) {
const columnId = params.colDef.field;
const currentValue = params.value;
const duplicates = params.context.duplicates;
if (columnId in duplicates && duplicates[columnId] == currentValue){
return { 'background-color': 'red' };
}
return { 'background-color': null};
}
}
ngOnInit() {
for (let key in this.rowData[0]) {
let seenValues = new Set<string>()
for (let i in this.rowData){
const item = this.rowData[i]
if (seenValues.has(item[key])){
this.duplicates[key] = item[key];
} else {
seenValues.add(item[key]);
}
}
}
}
Here is an example:
Here is the code for AngularJS:
https://stackblitz.com/edit/ag-grid-duplicates-highlighting-6adsz6
Here is the code for Vue:
https://stackblitz.com/edit/ag-grid-highlight-duplicates-vue

w2ui filter option "contains not" possible?

I am using w2ui (1.5) and I would be interested in whether it is possible to use a filter that only delivers negative results
That means only records/keys which not fullfilling a certain criteria.
Like a condition "contains not" or "is not" in addition to
http://w2ui.com/web/docs/1.5/w2grid.textSearch.
Thanks!
Gordon
okay, a possible solution is
w2ui['grid'].search([{ field: var1, value: var2, operator: 'not in'}], 'OR');
I coded my own solution to this problem. This adds a way to use "not" for string and "!=" for number searches.
This function does the search and it is also used to store the grid advanced search popup in history state.
I'm sure this can be even more optimized, so please use this more like a guideline. Hope this helps somebody.
function searchExtend(event, grid) {
// if event is null, we do just the local search
var searchObj;
if (event == null) {
searchObj = grid;
} else {
searchObj = event;
}
// make a copy of old data
const oldSearchData = structuredClone(searchObj.searchData);
const oldSearchLogic = structuredClone(searchObj.searchLogic);
var searchData = searchObj.searchData;
var invertedSdata = [];
var toSplice = [];
// check operator if it's "not" or "!="
for (var i = 0; i < searchData.length; i++) {
var sdata = searchData[i];
// invert the condition
if (sdata.operator == "not") {
toSplice.push(i);
invertedSdata.push({
field: sdata.field,
type: sdata.type,
operator: "contains",
value: sdata.value
});
}
if (sdata.operator == "!=") {
toSplice.push(i);
invertedSdata.push({
field: sdata.field,
type: sdata.type,
operator: "=",
value: sdata.value
});
}
}
// remove all "not" and "!=" from searchData
for (var i in toSplice) {
searchData.splice(i, 1);
}
var foundIds = [];
// use inverted criteria to search
if (invertedSdata.length > 0) {
grid.searchData = invertedSdata;
grid.searchLogic = "OR";
grid.localSearch();
grid.searchLogic = oldSearchLogic;
// store found ids
foundIds = structuredClone(grid.last.searchIds);
}
if (foundIds.length > 0) {
// perform a search with original criteria - spliced "not" and "!="
grid.searchData = searchData;
grid.localSearch();
var allRecIds = structuredClone(grid.last.searchIds);
// if there's not any results, push push all recIds
if (grid.last.searchIds.length == 0) {
for (let i = 0; i < grid.records.length; i++) {
allRecIds.push(i);
}
}
// remove all ids found with inverted criteria from results. This way we do the "not" search
for (const id of foundIds) {
allRecIds.splice(allRecIds.indexOf(id), 1);
}
if (event != null) {
// let the search finish, then refresh grid
event.onComplete = function() {
refreshGrid(grid, allRecIds, oldSearchData);
setSearchState(grid);
}
} else {
// refresh the grid
refreshGrid(grid, allRecIds, oldSearchData);
setSearchState(grid);
}
return;
}
if (event != null) {
event.onComplete = function() {
setSearchState(grid); // store state
}
} else {
// refresh whole grid
refreshGrid(grid, allRecIds, oldSearchData);
setSearchState(grid);
}
}
function refreshGrid(grid, allRecIds, oldSearchData) {
grid.last.searchIds = allRecIds;
grid.total = grid.last.searchIds.length;
grid.searchData = oldSearchData;
grid.refresh();
}
function setSearchState(grid) {
history.replaceState(JSON.stringify(grid.searchData), "Search");
}
To use this, you have to call it from the grid's onSearch:
onSearch: function(event) {
searchExtend(event, w2ui["grid"]);
}
Also, if you want to use the history.state feature, it needs to be called from onLoad function:
onLoad: function(event) {
event.onComplete = function() {
console.log("History state: " + history.state);
if (history.state != null) {
w2ui["grid"].searchData = JSON.parse(history.state);
searchExtend(null, w2ui["grid"]);
}
}
To add operators, please use this reference.
This is my solution to the problem:
operators: {
'text': ['is', 'begins', 'contains', 'ends', 'not'], // could have "in" and "not in"
'number': ['=', 'between', '>', '<', '>=', '<=', '!='],
'date': ['is', 'between', {
oper: 'less',
text: 'before'
}, {
oper: 'more',
text: 'after'
}],
'list': ['is'],
'hex': ['is', 'between'],
'color': ['is', 'begins', 'contains', 'ends'],
'enum': ['in', 'not in']
}

Problem inserting checkbox in select using DataTables

I'm looking for a way to insert a checkbox into a select and fetch each column individually, all using DataTables. I found an excellent example in https://jsfiddle.net/Lxytynm3/2/ but for some reason, when selecting all records, filtering does not display the data as expected. Would anyone have a solution to work properly?
Thanks in advance.
The link application code is as follows:
$(document).ready(function() {
function cbDropdown(column) {
return $('<ul>', {
'class': 'cb-dropdown'
}).appendTo($('<div>', {
'class': 'cb-dropdown-wrap'
}).appendTo(column));
}
$('#example').DataTable({
initComplete: function() {
this.api().columns().every(function() {
var column = this;
var ddmenu = cbDropdown($(column.header()))
// -------------------------------------------------------
.on('change', ':checkbox', function() {
var active;
var vals = $(':checked', ddmenu).map(function(index, element) {
active = true;
return $.fn.dataTable.util.escapeRegex($(element).val());
}).toArray().join('|');
column
.search(vals.length > 0 ? '^(' + vals + ')$' : '', true, false)
.draw();
// -------------------------------------------------------
// Highlight the current item if selected.
if (this.checked) {
$(this).closest('li').addClass('active');
// If 'Select all / none' clicked ON
if ($(this).val() === "1") {
$(ddmenu).find('input[type="checkbox"]').prop('checked', this.checked)
//$(".cb-dropdown li").prop('checked', true);
//$('.cb-dropdown').closest('li').find('input[type="checkbox"]').prop('checked', true);
// $('this input[type="checkbox"]').prop('checked', true); works!
// $( 'input[type="checkbox"]' ).prop('checked', this.checked);
// $(this).find('input[type="checkbox"]').prop('checked', this.checked)
//$('div.cb-dropdown-wrap.active').children().find('input[type="checkbox"]').prop('checked', this.checked)
}
} else // 'Select all / none' clicked OFF
{
$(this).closest('li').removeClass('active');
// test if select none
if ($(this).val() === "1") {
// code to untick all
$(ddmenu).find('input[type="checkbox"]').prop('checked', false)
}
}
// Highlight the current filter if selected.
var active2 = ddmenu.parent().is('.active');
if (active && !active2) {
ddmenu.parent().addClass('active');
// Change Container title to "Filter on" and green
//$(this).parent().find('.cb-dropdown li:nth-child(n+1)').css('color','red');
$('active2 li label:contains("Filter OFF")').text('Yeees');
} else if (!active && active2) {
ddmenu.parent().removeClass('active');
}
});
// -------------------------------------------------------
var mytopcount = '0'; // Counter that ensures only 1 entry per container
// loop to assign all options in container filter
column.data().unique().sort().each(function(d, j) {
// Label
var $label = $('<label>'),
$text = $('<span>', {
text: d
}),
// Checkbox
$cb = $('<input>', {
type: 'checkbox',
value: d
});
$text.appendTo($label);
$cb.appendTo($label);
ddmenu.append($('<li>').append($label));
// -----------------
// Add 'Select all / none' to each dropdown container
if (mytopcount == '0') // Counter that ensures only 1 entry per container
{
$label = $('<label>'), $text = $('<span>', {
text: "Select all / none"
}),
$cb = $('<input>', {
type: 'checkbox',
value: 1
});
$text.prependTo($label).css('margin-bottom', '6px');
$cb.prependTo($label);
ddmenu.prepend($('<li>').prepend($label).css({
'border-bottom': '1px solid grey',
'margin-bottom': '6px'
}));
mytopcount = '1' // This stops this section running again in cotainer
}
});
});
}
});
});
It seems as though the issue was with the select all checkbox. One solution would be to check for "1" within the vals initialisation, this seems to work:
var vals = $(':checked', ddmenu).map(function(index, element) {
if($(element).val() !== "1"){
return $.fn.dataTable.util.escapeRegex($(element).val());
}
}).toArray().join('|');
That should see you with some results upon the top checkbox being checked. Hope that helps.

NativeScript - how can I filter an observable array with SearchBar?

Hi I'm trying to filter an observable array of data fetched via a HTTP request on keypress of the SearchBar.
I managed to get the SearchBar property change to work but I can't seem to figure out what I'm doing wrong in the filtering logic.
Ideally I want to update the list as I type in the search term in the SearchBar. I've searched the API on the Telerik site, there wasn't really any examples I could find.
XML
<Page loaded="pageLoaded">
<ActivityIndicator busy="{{ isLoading }}" />
<ActionBar title="People">
</ActionBar>
<GridLayout>
<StackLayout>
<SearchBar id="searchBar" hint="Search for someone"></SearchBar>
<ListView items="{{ peopleList }}" itemTap="showDetail">
<ListView.itemTemplate>
<StackLayout>
<Label text="{{ fullName }}" horiztonalAlignment="left" verticalAlignment="center"></Label>
<Label text="{{ company }}" class="info"></Label>
</StackLayout>
</ListView.itemTemplate>
</ListView>
</StackLayout>
</GridLayout>
</Page>
JS
var frames = require("ui/frame");
var Observable = require("data/observable").Observable;
var PeopleListViewModel = require("../../shared/people-viewModel");
var activityIndicatorModule = require("ui/activity-indicator");
var page;
var userkey;
var peopleList = new PeopleListViewModel([]);
var pageData = new Observable({ peopleList: peopleList });
exports.pageLoaded = function(args) {
page = args.object;
page.bindingContext = pageData;
userkey = userkey || page.navigationContext.userkey;
peopleList.load(userkey); // fetch data from the backend
var searchBar = page.getViewById("searchBar");
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
if (searchText === "") {
// NOT SURE WHAT TO DO HERE.
} else {
peopleList.filter(function (element, index, array) {
// DOESN"T WORK PROPERLY
console.log("element: ", JSON.stringify(element));
return element.fullName == searchText;
});
console.log("Text types: ", searchText);
}
});
};
exports.showDetail = function(args) {
var person = peopleList.getItem(args.index);
var navigateEntry = {
moduleName: "views/people/people-detail",
context: { person: person },
animated: false
};
frames.topmost().navigate(navigateEntry);
};
PeopleListViewModel.js
var config = require("./config");
var fetchModule = require("fetch");
var ObservableArray = require("data/observable-array").ObservableArray;
function PeopleListViewModel(people) {
var viewModel = new ObservableArray(people);
viewModel.load = function (userKey) {
return fetchModule.fetch(config.baseUrl + "/api/people/all/" + userKey)
.then(function (response) {
return response.json();
})
.then(function (data) {
data.forEach(function (person) {
viewModel.push(person);
});
}, function (error) {
console.log("Error: ", error);
});
};
viewModel.empty = function () {
while (viewModel.length) {
viewModel.pop();
}
};
return viewModel;
}
function handleErrors(response) {
if (!response.ok) {
console.log("Error occurred");
}
}
module.exports = PeopleListViewModel;
Updated people-list
var frames = require("ui/frame");
var Observable = require("data/observable").Observable;
var ObservableArray = require("data/observable-array").ObservableArray;
var PeopleListViewModel = require("../../shared/people-viewModel");
var activityIndicatorModule = require("ui/activity-indicator");
var page;
var userkey;
var peopleList = new PeopleListViewModel([]);
var pageData = new Observable({ peopleList: peopleList });
var resultList = new ObservableArray([]);
exports.pageLoaded = function(args) {
page = args.object;
page.bindingContext = pageData;
userkey = userkey || page.navigationContext.userkey;
peopleList.load(userkey);
var searchBar = page.getViewById("searchBar");
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
if (searchText === "") {
} else {
while (resultList.length > 0) {
resultList.pop();
}
peopleList.forEach(function (element) {
if (element.fullName === searchText) {
resultList.push(element);
}
});
}
});
};
I had the same issue. If you want to filter your data after every character has changed in search-bar you can try my solution.
Definitions
My playerList is your peopleList. This is the data from view-model.
resultList is an array where the data will be pushed.
var observableArrayModule = require("data/observable-array").ObservableArray;
var playerList = new PlayerListViewModel([]);
var resultList = new observableArrayModule([]);
var pageData = new observableModule.Observable({
resultList: resultList,
player: ""
});
Inside expors.loaded()
page = args.object;
searchBar = page.getViewById("search-bar");
page.bindingContext = pageData;
Load Initial Data - inside expors.loaded()
We are loading initial data when user navigates to the screen for the first time. We are also pushing the same data to resultList since we are using {{resultList}} in xml. You can add loadingIndicator while the list is populated.
playerList
.load()
.then(function() {
setTimeout(function() {
playerList.forEach(function (element) {
pageData.resultList.push(element);
});
}, 1000);
})
.catch(function(error) {
dialogsModule.alert({
message: "An error occurred while loading players.",
okButtonText: "OK"
});
});
Clear autofocus - inside expors.loaded()
This is to prevent keyboard from opening on initial screen navigation.
if (searchBar.ios) {
searchBar.ios.endEditing(true);
} else if (searchBar.android) {
searchBar.android.clearFocus();
}
Search data when character has changed - inside expors.loaded()
I am calling filter functionality. Lodash _.debounce function is used to delay looping through resultList array. Without it, the app would loop every time letter is typed. Now we are waiting for user to stop typing to start looping.
searchBar.on('propertyChange', _.debounce(searchList, 500));
searchList Function
This is the actual loop. You can change element.name for your needs.
function searchList(args) {
var searchText = args.object.text;
while(resultList.length > 0) {
resultList.pop();
}
playerList.forEach(function (element) {
if (element.name.toLowerCase().indexOf(searchText) >= 0) {
resultList.push(element);
}
});
}
Hide keyboard if search-bar is cleared - inside exports.loaded()
And finally we want to hide the keyboard if user clears the search-bar.
searchBar.on(searchBarModule.SearchBar.clearEvent, function (args) {
setTimeout(function() {
searchBar.dismissSoftInput();
}, 10);
});
PS
You probably solved your issue, but this could help someone else in the future.
Okay so your problem is a Javascript problem than a NativeScript problem. For the sake of this problem, think of observable arrays as just your ordinary arrays.
In your JS you're creating a new PeopleListViewModel which you're then attaching to the bindingContext via the pageData object. So far so good. Then you're calling the load method on the PeopleListViewModel (It returns a promise which you're not really doing anything with but for this specific problem it doesn't matter).
However, when text is inputed you're not really doing anything. This is your code:
peopleList.filter(function (element, index, array) {
// DOESN"T WORK PROPERLY
console.log("element: ", JSON.stringify(element));
return element.fullName == searchText;
});
peopleList is an instance of PeopleListViewModel which returns an ObservableArray. The ObservableArray does indeed have a method called filter (which works just like filter of a regular array. Check out the NativeScript documentation and Javascript documentation of filter).
What you need to understand here is that filter returns a new array with the filtered results. Doing peopleList.filter() will send that new array into empty space. You want to var yourNewFilteredArray = peopleList.filter(). But you don't really want to redefine the array bound to the binding context, you want to modify the content of it.
Here's an example of how you could do that:
/*
* Attach a new obsersable array to the binding context.
* you can prepopulate it with the data from the
* PeopleListViewModel if you want to
*/
var resultList = new ObservableArray([]);
var pageData = new Observable({ resultList: resultList });
/*
* Then on search/filter you want to modify this new
* array. Here I first remove every item in it and then
* push matching items to it.
*/
searchBar.on("propertyChange", function (args) {
var searchText = args.object.text;
// ...
while(resultList.length > 0) {
resultList.pop();
}
peopleList.forEach(function (element) {
if (element.fullName === searchText) {
resultList.push(element);
}
});
});

How to check if text is found in column in Protractor

I'm trying to assert that a name is displayed in a column of a table. I've written an inResults function that will iterate through a column's text to see if a name exists. Here's what I'm trying:
Page object:
this.names = element.all(by.repeater('row in rows').column('{{row}}'));
this.inResults = function(nameString) {
var foundit = '';
this.names.each(function(name) {
name.getText().then(function(it) {
console.log(it); // each name IS printed...
if(it == nameString) {
console.log('it\'s TRUE!!!!'); // this gets printed...
foundit = true;
}
});
});
return foundit; // returns '' but should be true?
};
Spec expect:
expect(friendPage.inResults('Jo')).toBeTruthy();
Both console statements print as expected... but my expect fails as foundit's value is still ''. I've tried this a number of ways and none are working. What am I missing?
I've devised what I think is a better/cleaner way to solve this. It's less complex and doesn't require locator/css code in the method.
friend.page.js
// locator
this.friendName = function(text) { return element.all(by.cssContainingText('td.ng-binding', text)) };
// method
this.inResults = function(name) {
return this.friendName(name).then(function(found) {
return found.length > 0;
});
};
friend.spec.js
expect(friendPage.inResults('Jo')).toBeTruthy();
I've added this to my protractor_example project on GitHub...
I would recommend you to use filter: http://angular.github.io/protractor/#/api?view=ElementArrayFinder.prototype.filter
this.inResults = function(nameString) {
return this.names.filter(function(name) {
return name.getText().then(function(text) {
return text === nameString;
});
}).then(function(filteredElements) {
// Only the elements that passed the filter will be here. This is an array.
return filteredElements.length > 0;
});
});
// This will be a promise that resolves to a boolean.
expect(friendPage.inResults('Jo')).toBe(true);
Use map to do this.This will return a deferred that will resolve with the values in an array, so if you have this:
this.mappedVals =element.all(by.repeater('row in rows').column('{{row}}')).map(function (elm) {
return elm.getText();
});
It will resolve like this:
this.inResults = function(nameString) {
var foundit = '';
mappedVals.then(function (textArr) {
// textArr will be an actual JS array of the text from each node in your repeater
for(var i=0; i<textArr.length; i++){
if(it == textArr[i]) {
console.log('it\'s TRUE!!!!'); // this gets printed...
foundit = true;
}
}
return foundit;
});
}
And Use that in Spec file like,
friendPage.inResults('Jo').then(function(findIt){
expect(findIt).toBeTruthy();
});