I'm trying to concat multiple fields in AG Grid. This is working but when the field is blank the grid shows undefined.
See my code below. I have a grid that has a students First, Middle and Last Name. However, when the Middle name is blank, the 'Student' field, where the values are concatenated it shows the middle name as undefined.
this.state = {
modules: AllCommunityModules,
columnDefs: [
{
field:"FirstName",
headerName: "FirstName",
},
{
field: "MiddleName",
header: "MiddleName",
},
{
field: "LastName",
header: "LastName",
},
{
field: "Student",
header: "Student",
valueGetter: studentValueGetter,
},
function studentValueGetter(params) {
return params.data.FirstName + params.data.MiddleName + params.data.LastName;
}
A header
Another header
A header
Another header
John
Pete
Smith
JohnPeteSmith
Sarah
Jane
SarahunderfinedJane
Just add a condition on the middle name:
return params.data.FirstName + ' ' +
(params.data.MiddleName ? params.data.MiddleName + ' ' : '') +
params.data.LastName;
Or the same thing with more advanced syntax:
const { FirstName, MiddleName, LastName } = params.data;
return [FirstName, MiddleName, LastName].filter(n => !!n).join(' ');
Not an ag-grid specific thing though; this is JavaScript generally.
Related
I'm trying to insert a complex list structure into a contentControl in MS Word using the javascript API. The structure is build according to an object that contains nested arrays: Different items containing sub items that contain different properties. These items arrays can change in size so it needs to be generic. Maybe the Office.js API isn't really build for what I am trying to achieve and I should be using either insertHTML (with the structure build in HTML) or OOXML.
This is the structure I already build
The function that produced this:
import ContentControl = Word.ContentControl;
import {formatDate} from '../formatters';
let firstItem = true;
let listId;
export async function resolveItems(contentControl: ContentControl, data: Array<any>, t) {
Word.run( contentControl, async (context) => {
contentControl.load('paragraphs');
await context.sync();
for (const item of data) {
if (firstItem) {
const firstPara = contentControl.paragraphs.getFirst();
firstPara.insertText('ITEM (' + formatDate(item.date) + ')', 'Replace');
firstItem = false;
const contactList = firstPara.startNewList();
contactList.load('id');
await context.sync().catch((error) => {
console.log('Error: ' + error);
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
listId = contactList.id;
} else {
const otherItem = contentControl.insertParagraph('ITEM (' + formatDate(item.date) + ')', 'End');
otherItem.load(['isListItem']);
await context.sync();
otherItem.attachToList(listId, 0);
}
for (const subItem of item.subItems) {
let descriptionParaList = new Array();
let subItemPara = contentControl.insertParagraph(subItem.title + ' (' + formatDate(subItem.date) + ')', 'End');
subItemPara.load(['isListItem']);
await context.sync();
if (subItemPara.isListItem) {
subItemPara.listItem.level = 1;
} else {
subItemPara.attachToList(listId, 1);
}
let descriptions = subItem.descriptions;
for (const description of descriptions) {
let descriptionPara = contentControl.insertParagraph('', 'End');
descriptionPara.insertText(t(description.descriptionType) + ': ', 'Start').font.set({
italic: true
});
descriptionPara.insertText(description.description, 'End').font.set({
italic: false
});
descriptionPara.load(['isListItem', 'leftIndent']);
descriptionParaList.push(descriptionPara);
}
await context.sync();
for (const subItemPara of descriptionParaList) {
if (subItemPara.isListItem) {
subItemPara.detachFromList();
}
subItemPara.leftIndent = 72;
}
}
}
return context.sync().catch((error) => {
console.log('Error: ' + error);
if (error instanceof OfficeExtension.Error) {
console.log('Debug info: ' + JSON.stringify(error.debugInfo));
}
});
});}
The data structure looks like this:
'LAST_5_Items': [
{
'date': '2019-03-14T14:51:29.506+01:00',
'type': 'ITEM',
'subItems': [
{
'date': '2019-03-14T14:51:29.506+01:00',
'title': 'SUBITEM 1',
'descriptions': [
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
}
]
},
{
'date': '2019-03-14T14:51:29.506+01:00',
'title': 'SUBITEM 2',
'descriptions': [
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
}
]
}
]
},
{
'date': '2019-03-14T14:16:26.220+01:00',
'type': 'ITEM',
'subItems': [
{
'date': '2019-03-14T14:16:26.220+01:00',
'title': 'SUBITEM 1',
'descriptions': [
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
}
]
},
{
'date': '2019-03-14T14:16:26.220+01:00',
'title': 'SUBITEM 2',
'descriptions': [
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
},
{
'descriptionType': 'SUBJECTIVE',
'description': 'Subjective 1',
'additionalInformation': ''
},
{
'descriptionType': 'SUBJECTIVE',
'description': 'Subjective 2',
'additionalInformation': ''
},
{
'descriptionType': 'OBJECTIVE',
'description': 'Objective 1',
'additionalInformation': ''
},
{
'descriptionType': 'OBJECTIVE',
'description': 'Objective 2',
'additionalInformation': ''
},
{
'descriptionType': 'EVALUATION',
'description': 'Evaluation',
'additionalInformation': ''
},
{
'descriptionType': 'REASON',
'description': 'Reason 1',
'additionalInformation': ''
}
]
}
]
}
]
What I am trying to achieve is a template resolver. The addin will allow the user to put placeholder (ContentControls), tags like First name, Last name,... and the 5 last contacts (the one I am now describing) in the document and when he resolves the file it will fetch all the data needed and start replacing the ContentControls with this structured layout.
Yes the code works, but I highly doubt that the code is well structured with so many context.sync() calls. It is too slow and it is not usable.
I need so many context.sync() because I need the properties of the List ID and if a Paragraph belongs to a list.
Is there a better way to achieve what I am trying to achieve using the office.js API?
Ideally the queue should be synced once so the user would not see the content being added and changed in a very strange way like it is now behaving.
Thanks
There is a technique for avoiding having context.sync inside a loop. The basic idea is that you have two loops, with a single context.sync in between them. In the first loop, you create an array of objects. Each object contains a reference to one of the Office objects that you want to process (e.g., change, delete, etc.) It looks like paragraphs in your case. But the object has other properties. Each of them holds some item of data that you need to process the object. These other items may be properties of other Office objects or they may be other data. Also, either in your loop or just after it, you load all the properties you're going to need of all the Office objects in the objects in your array. (This is usually easier to do inside the loop.)
Then you have context.sync.
After the sync, you loop through the array of objects that you created before the context.sync. The code inside this second loop processes the first item in each object (which is the Office object that you want to process) using the other properties of the object you created.
Here are a couple of examples of this technique:
My answer to this StackOverflow question: Document not in sync after replace text.
This code sample:
https://github.com/OfficeDev/Word-Add-in-Angular2-StyleChecker/blob/master/app/services/word-document/word.document.service.js.
I want's to exclude some data in some controller method and in other method i want's that data. I do it with forEach function right into method after finding that :
nine: function (req, res) {
Dore.find()
.limit(9)
.sort('createdAt DESC')
.populate('file')
.exec(function (err, sh) {
if (err) {
return res.negotiate(err);
} else {
console.log('before : ', sh);
sh.forEach(function (item, i) {
delete item.zaman;
});
console.log('after : ', sh);
return res.send(sh);
}
});
},
I want to know how possible to do that with finding and do not included ever in finding so we don't need to remove that again with forEach.
tanks
As #zabware say we have select method in Query option I try this format but do not work and return all data :
I try to use that with following format but don't working :
Model.find( {
where: {},
limit: 9,
sort: 'createdAt DESC'
},
{
select: [ 'id', 'createdAt' ]
} )
and
Model.find( {
where: {},
limit: 9,
sort: 'createdAt DESC',
select: [ 'id', 'createdAt' ]
} )
and
Model.find( {}, select: [ 'id', 'createdAt' ] )
Although toJson is really designed to solve the kind of problem you are facing, it will not help you, since in some actions you want to exclude certain fields and in others not.
So we need to select fields per query. Luckily there is a solution for this in waterline:
Dore.find({}, {select: ['foo', 'bar']})
.limit(9)
.sort('createdAt DESC')
.populate('file')
.exec(function (err, sh) {
console.log(err);
console.log(sh);
});
This is supported since sails 0.11 but not really documented. You can find it under query options here https://github.com/balderdashy/waterline-docs/blob/e7b9ecf2510646b7de69663f709175a186da10d5/queries/query-language.md#query-options
I accidentally stumbled upon it while playing with the debugger - Sails version 1.2.3
There is something like omit and You can put it into your find method call:
const user = await User.findOne({
where: {email : inputs.email},
omit: ['password']
}); //There won't be password field in the user object
It is a pity that there is no word about it in the documentation.
How do I specify in DBIx::Class the column of the third table I'm joining on? Here is my code:
my $join = $schema->resultset('Track')->search({'name' => 'Eminem'},{'join' => {'cd' => 'artist'}});
It just displays the tracks of Eminem, but I also want to display the artist name, Eminem? I cannot access name in that query because this is a ResultSet for Track, name is a column in the Artist table, the third table in the join.
I guess you want to filter your resultset based on the artist name, not track name:
my $rs = $schema->resultset('Track')->search({
'artist.name' => 'Eminem',
},
{
join => { cd => 'artist' },
});
When looping through the resultset you can access it using the relationship and column accessors:
for my $track ($rs->all) {
say $track->cd->artist->name . ' - ' . $track->name;
}
Use the relationship accessors.
for my $track (
$schema->resultset('Track')->search(
{
name => 'Eminem',
},
{
join => {cd => 'artist'},
}
)->all
) {
use Data::Printer;
p { $track->get_inflated_columns };
p $track->cd->artist->name;
}
__END__
{
cd MyDatabase::Main::Result::Cd {
internals: {
_column_data {
artist 2,
cdid 3,
title "The Marshall Mathers LP"
},
}
},
title "The Way I Am",
trackid 1
}
"Eminem"
{
cd MyDatabase::Main::Result::Cd {
internals: {
_column_data {
artist 2,
cdid 3,
title "The Marshall Mathers LP"
},
}
},
title "Stan",
trackid 2
}
"Eminem"
By following this example http://dev.sencha.com/deploy/ext-4.1.0-gpl/examples/form/forum-search.html
I could apply the auto-complete feature on my combo box, using php/postgresql query retrieves the names then:
{
xtype: 'combo',
store: ds,
hideTrigger:true,
typeAhead: false,
id: 'search_input_text',
width: 187,
queryMode: 'remote',
hideLabel: true,
queryParam: 'query',
displayField: 'first_name',
valueField: 'first_name',
listConfig: {
loadingText: 'Loading...',
getInnerTpl: function() {
return '{first_name}';
}}
listeners: {
buffer: 50,
change: function() {
var store = this.store;
store.clearFilter();
store.filter({
property: 'first_name',
anyMatch: true,
value : this.getValue()
});
}
}
}
Now, I need to edit this to let user enter either the first or last name of the student, and then each name entered (first or last) will be shown in the combo box.
So, I edited the query to make it retrieve the names :
SELECT first_name, last_name FROM students WHERE first_name ILIKE '%$query%' OR last_name ILIKE '%$query%' ";
and:
displayField: 'first_name'+'last_name',
valueField: 'first_name'+'last_name',
return '{first_name}'+'{last_name}';
store.filter({
property: 'first_name',
anyMatch: true,
value : this.getValue()
},
{
property: 'last_name',
anyMatch: true,
value : this.getValue()
}
but still, it retrieves only first names according to what user types,
Note that if I use this alone:
store.filter({
property: 'last_name',
anyMatch: true,
value : this.getValue()
});
it works fine with last names only.
You can apply multiple filters to data store like this:
var filters = [
new Ext.util.Filter({
filterFn: function(item){return item.get('first_name') == this.getValue() || item.get('last_name') == this.getValue();
}
})
];
store.filter(filters);
The above code block is just an idea on how to apply multiple filters in the same field. Notice the double-pipe "||". You can use "&&" as per your needs. It is like SQL query.
When submitting a form with Extjs I can see the form items have values with this code just before the submit:
var itemsForm = '';
function mostraItems(item, index, length) { itemsForm += item.id + ':' + item.name + ':' + item.value + ':' + index + '\n'; }
myForm.form.items.each(mostraItems);
alert (itemsForm);
myForm.form.submit({...
But when the request arrives at the handling page the form items are not there. I can see the request details with Firebug.
One of the form items is a ComboBox:
var myCombo = new Ext.form.ComboBox({
//autoWidth: true,
width: 250,
displayField: 'theFieldText',
editable: false,
emptyText: 'Select something ...',
fieldLabel: 'Some text',
listeners: { 'select': { fn: theOnSelect, scope: this} },
mode: 'local',
selectOnFocus: true,
store: theStore,
triggerAction: 'all',
typeAhead: true,
valueField: 'theFieldValue'
});
This is Extjs 2.1
You need to specify the "name" property in order to map the control to a form name-value pair.