Slickgrid Display Object in Single Column not Row - row

I am trying to figure out how to use slickgrid to display my object in a single column and not in a single row. Below is an example. Ideas?
Grid data looks like this
[{ fname: 'john', lname:'doe'}, {fname:'kyle', lname:'noobie'}]
This is what I get with slickgrid (row object view)
desc | fname | lanme
option1 | john | doe
option2 | kyle | noobie
This is what I want (column object view)
desc | option1 | option2
fname | john | kyle
lname | doe | noobie

So I figured this out and it's not pretty. Basically I have to pivot the data into a new row objects and then unpivot it back out to save it. It's not really that pretty but it gets the job done. The crazy thing is now you have to create a single editor and formatter for and apply that to each column. The editor/formatter look at the field names and do switch statement to determine what it should look like. Have fun. I did. :)
function getPivotedRows(origData) {
//this is a mapping between the column headings and the properties
var properties = [{ 'First Name': 'fname' }, { 'Last Name': 'lname' }];
//this holds my pivoted rows
var rows = [];
for (var i = 0; i < properties.length; i++) {
var row = {};
for (var j = 0; j < origData.length; j++) {
var key = "";
for (var p in properties[i]) {
row['Description'] = p; //set the column
key = properties[i][p];
}
row['options' + (j + 1)] = origData[j][key];
}
row[gridOptions.uniqueKey] = i;
rows.push(row);
}
return rows;
}

Related

How to emulate the formula Proper on AppScript for a specific column?

I found the following code to emulate the proper formula, but it has a wrong ( maybe outdated) syntax, and as far as i understood, it should applies to all columns of a given sheet.
function PROPER_CASE(str) {
if (typeof str != "string")
throw `Expected string but got a ${typeof str} value.`;
str = str.toLowerCase();
var arr = str.split(/.-:?—/ );
return arr.reduce(function(val, current) {
return val += (current.charAt(0).toUpperCase() + current.slice(1));
}, "");
}
Here's an example of the input :
A
B
C
D
ColumnA
ColumnB
ColumnC
ColumnD
EXCEL ACTION LIMIMTED (毅添有限公司)
207/2018
n/a
without-proper
Hang Wo Holdings
205/2015
35/2020
without-proper
central southwood limited
308/2019
n/a
without-proper
This would be the desired output:
ColumnA ColumnB ColumnC COlumnD
Excel Action Limited (毅添有限公司) 207/2018 n/a without-proper
Hang Wo Holdings 205/2015 35/2020 without-proper
Central Southwood Limited 308/2019 n/a without-proper
And this is the error output of that function :
Erro
Expected string but got a undefined value.
PROPER_CASE # macros.gs:115
This is the only way I can see of reproducing you results. I don't see how to avoid captalizing the first letter of the last two columns with avoiding them:
function lfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
if (sh.getLastRow() > 4) {
sh.getRange(6, 1, sh.getLastRow() - 5, sh.getLastColumn()).clearContent();
SpreadsheetApp.flush();
}
const vs = sh.getDataRange().getDisplayValues().map((r, i) => {
return r.map((c, j) => {
if (i > 0 && j < 1) {
let arr = c.toString().toLowerCase().split(/.-:?-/g);
return arr.reduce((val, current) => {
//Logger.log(current)
return val += current.charAt(0).toUpperCase() + current.slice(1);
}, '');
} else {
return c;
}
});
});
Logger.log(JSON.stringify(vs))
sh.getRange(sh.getLastRow() + 2, 1, vs.length, vs[0].length).setValues(vs);
}
A
B
C
D
Data
ColumnA
ColumnB
ColumnC
ColumnD
EXCEL ACTION LIMIMTED (毅添有限公司)
207/2018
n/a
without-proper
Hang Wo Holdings
205/2015
35/2020
without-proper
central southwood limited
308/2019
n/a
without-proper
Outpput
ColumnA
ColumnB
ColumnC
ColumnD
Excel action limimted (毅添有限公司)
207/2018
n/a
without-proper
Hang wo holdings
205/2015
35/2020
without-proper
Central southwood limited
308/2019
n/a
without-proper
I have tested your code and it works fine. It does convert the input string into a proper case.
However, take note that in Google Sheets, when you get values, your data is in 2D Array or Nested Array.
So to apply this to your Spreadsheet after getting the values you will have to target the column you want to replace and loop through each string in the array. You will then have to setValues() back to the specified range to replace it in the spreadsheet.
Solution 1:
Try:
With your function, try adding this script to apply to your spreadsheet.
function setToColumn(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getRange(1,1,sheet.getLastRow()); //2ND Parameter is the column, replace if you want to edit different column
var allData = dataRange.getValues().flat();
var properData = []
allData.forEach(function(data){
properData.push([PROPER_CASE(data)])
});
dataRange.setValues(properData);
}
From:
Result:
Solution 2:
If you don't mind using different script which only needs one function you may use the script below:
function properCase() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getRange(1,1,sheet.getLastRow()); //2ND Parameter is the column, replace if you want to edit different column (1 = Column A, 2 = Column B)
var allData = dataRange.getValues().flat();
var properData = []
allData.forEach(function(data){
properData.push([data.toLowerCase().replace(/\b[a-z]/ig, function(match) {return match.toUpperCase()})]);
});
dataRange.setValues(properData);
}
Reference for Solution 2:
Apps script how to format a cell to Proper Text (Case)

Get last row ignoring formula in google script

I used a google script that move row from one sheet to another from column A to column D. On column E i have a formula. How can i put data on last row from Column A to D from example, ignoring formula from column E. Can someone help me with this?
function onEdit(e){
let r = e.range;
if (r.columnStart != 4 || r.rowStart == 1 || e.value == null) return;
const sh = SpreadsheetApp.getActive();
const valArray = ["Waiting","In progress","Complete"];
const destArray = ["Order","Open order","Complete order"];
let dest = sh.getSheetByName(destArray[valArray.indexOf(e.value)]);
let src = sh.getActiveSheet();
if (dest.getName() == src.getName()) return;
src.getRange(r.rowStart,1,1,4).moveTo(dest.getRange(dest.getLastRow()+1,1,1,4));
src.deleteRow(r.rowStart);
}
Here an example of my problem, maybe this will explain better:https://docs.google.com/spreadsheets/d/1qowADCPYiyej25ezXtjVLO5fvg9Gr9rolX3bh2-ZAG4/edit#gid=0
Replace dest.getLastRow() by dest.getLastDataRow('A') and add this function:
Object.prototype.getLastDataRow = function(col){
var lastRow = this.getLastRow();
var range = this.getRange(col + lastRow);
if (range.getValue() !== "") {
return lastRow;
} else {
return range.getNextDataCell(SpreadsheetApp.Direction.UP).getRow();
}
};
I still sugest you to erase all formulas in column E and put in E1
={"price * 2";ARRAYFORMULA(if(C2:C>0;C2:C*2;""))}
so that the formula will populate / will be active the new row

Replacing a row in QTableWidget based on cell name

Lets say I have QTableWidget that looks like this:
__| columnname1 | columnname2
1 | name1 | value1
2 | name2 | value2
3 | name3 | value3
4 | name4 | value4
that I created with this code:
ui->tableWidget->setColumnCount(2);
foreach(const QString& key, keys)
{
value = jsonObj.value(key);
ui->tableWidget->sortItems(int (1),Qt::DescendingOrder);
int currRow = ui->tableWidget->rowCount();
ui->tableWidget->setSortingEnabled(false);
ui->tableWidget->insertRow(ui->tableWidget->rowCount());
ui->tableWidget->setItem(currRow, 0, new QTableWidgetItem(key));
ui->tableWidget->setItem(currRow, 1, new QTableWidgetItem(value.toString()));
ui->tableWidget->setHorizontalHeaderLabels({"columnname1", "columnname2"});
ui->tableWidget->setSortingEnabled(true);
ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
}
Now, for example, I have a new value for name2, how can I replace that entire row where name2 is?
First of all, you're calling some functions for every row that only need to be called once for the full table. Instead, your code sample should look more like this:
ui->tableWidget->setColumnCount(2);
ui->tableWidget->setSortingEnabled(false);
ui->tableWidget->setHorizontalHeaderLabels({"columnname1", "columnname2"});
ui->tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
foreach(const QString& key, keys)
{
value = jsonObj.value(key);
int currRow = ui->tableWidget->rowCount();
ui->tableWidget->insertRow(ui->tableWidget->rowCount());
ui->tableWidget->setItem(currRow, 0, new QTableWidgetItem(key));
ui->tableWidget->setItem(currRow, 1, new QTableWidgetItem(value.toString()));
}
ui->tableWidget->sortItems(int (1),Qt::DescendingOrder);
ui->tableWidget->setSortingEnabled(true);
Then, if you have a new value for the row 'name2', you only need to replace the value. You already know how to do that:
// first find the row index of row 'name2':
int rowIdx = -1;
for(int i = 0; i < ui->tableWidget->rowCount(); i++) {
if(ui->tableWidget->item(i, 0)->text() == "name2") {
rowIdx = i;
break;
}
}
if(rowIdx >= 0) {
// then EITHER replace the item:
ui->tableWidget->setItem(rowIdx, 1, new QTableWidgetItem(value.toString()));
// OR replace the data:
ui->tableWidget->item(rowIdx, 1)->setText(value.toString());
}
I didn't compile this sample code, it could have typos or wrong function names, but you'll get the idea.

Copy cell if certain condition met

I know there are already many topics like mine, but I am completely new to programming scripts and therefore I ask you to help me with following problem:
In cell B2 is the user name and in cell C2 the submitter type - if the submitter type is "Requester" then the user name of cell B2 should be copied to cell AE2 - the problem is, that I also want to keep the previous user names if the submitter type change and gets updated...
That means, first the submitter type is "Requester" then the system should copy the user name to cell AE2 - if the submitter type of my line change, then the already copied user name should not change - understood what I want? This should work for all lines of the spreadsheet...
I tried the code but got following error:
Maybe you can improve the code for me?
I changed those 2:
Before:
for (var i = 1; i <= numRows; i++) {
var userNameCell = rows.getCell(i, userNameColumn);
var subTypeCell = rows.getCell(i, subTypeColumn);
var sUserNameCell = rows.getCell(i, sUserNameColumn);
After:
for (var i = 1; i <= numRows; i++) {
var userNameCell = rows.getCell(i, userNameColumn);
var sUserNameCell = rows.getCell(i, sUserNameColumn);
var subTypeCell = rows.getCell(i, subTypeColumn);
The error now is:
new error
Or was the change wrong? Maybe you mean that:
var userNameColumn = 2; //Column where the user name is written
var subTypeColumn = 5; //Column where the submitter type is written ex. "Requester"
var sUserNameColumn = 3; //Column where the user name is saved
But then only one object (username) is known instead of 2 before - subTypeCell and sUserNameCell again undefined - that says the debugger - the code looks now like that (again with my changes, because then 2 objects instead only 1 is known):
You can do something like this:
var userNameColumn = 2; //Column where the user name is written
var subTypeColumn = 3; //Column where the submitter type is written ex. "Requester"
var sUserNameColumn = 5; //Column where the user name is saved
function saveUserName() {
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
var rows = sheet.getDataRange();
var numRows = rows.getNumRows();
for (var i = 1; i <= numRows; i++) {
var userNameCell = rows.getCell(i, userNameColumn);
var subTypeCell = rows.getCell(i, subTypeColumn);
var sUserNameCell = rows.getCell(i, sUserNameColumn);
if(sUserNameCell.isBlank() && subTypeCell.getValue() === 'Requester') {
sUserNameCell.setValue(userNameCell)
};
}
}
Firstly this defines the columns that you are working with, which are the "Username"(in the scriptuserNameColumn), the column with the "Submitter type"(in the scriptsubTypeColumn) and the column where the username is saved (in the scriptsUserNameColumn). Each have been assigned a number corresponding to a columns in spreadsheet, wher 2 is B, 3 is C, and so on. You can change them to your needs.
Then we create a function called saveUserName() inside this function we get the current spreadsheet, we define what range in the spreadsheet we will work with, here it is everything, then we get the number of rows that has data in them.
After that we go through the data we defined, and setup some variables we can use for each cell. Then we create our if statement, which states, if the "Saved-User-Name-Cell" is empty and the "Submitter-Type-Cell" = the word 'Requester' then we grab the "Saved-User-Name-Cell" and set it's value equal to what is within the "User-Name-Cell".
That's it!

How to do a cross join / cartesian product in RavenDB?

I have a web application that uses RavenDB on the backend and allows the user to keep track of inventory. The three entities in my domain are:
public class Location
{
string Id
string Name
}
public class ItemType
{
string Id
string Name
}
public class Item
{
string Id
DenormalizedRef<Location> Location
DenormalizedRef<ItemType> ItemType
}
On my web app, there is a page for the user to see a summary breakdown of the inventory they have at the various locations. Specifically, it shows the location name, item type name, and then a count of items.
The first approach I took was a map/reduce index on InventoryItems:
this.Map = inventoryItems =>
from inventoryItem in inventoryItems
select new
{
LocationName = inventoryItem.Location.Name,
ItemTypeName = inventoryItem.ItemType.Name,
Count = 1
});
this.Reduce = indexEntries =>
from indexEntry in indexEntries
group indexEntry by new
{
indexEntry.LocationName,
indexEntry.ItemTypeName,
} into g
select new
{
g.Key.LocationName,
g.Key.ItemTypeName,
Count = g.Sum(entry => entry.Count),
};
That is working fine but it only displays rows for Location/ItemType pairs that have a non-zero count of items. I need to have it show all Locations and for each location, all item types even those that don't have any items associated with them.
I've tried a few different approaches but no success so far. My thought was to turn the above into a Multi-Map/Reduce index and just add another map that would give me the cartesian product of Locations and ItemTypes but with a Count of 0. Then I could feed that into the reduce and would always have a record for every location/itemtype pair.
this.AddMap<object>(docs =>
from itemType in docs.WhereEntityIs<ItemType>("ItemTypes")
from location in docs.WhereEntityIs<Location>("Locations")
select new
{
LocationName = location.Name,
ItemTypeName = itemType.Name,
Count = 0
});
This isn't working though so I'm thinking RavenDB doesn't like this kind of mapping. Is there a way to get a cross join / cartesian product from RavenDB? Alternatively, any other way to accomplish what I'm trying to do?
EDIT: To clarify, Locations, ItemTypes, and Items are documents in the system that the user of the app creates. Without any Items in the system, if the user enters three Locations "London", "Paris", and "Berlin" along with two ItemTypes "Desktop" and "Laptop", the expected result is that when they look at the inventory summary, they see a table like so:
| Location | Item Type | Count |
|----------|-----------|-------|
| London | Desktop | 0 |
| London | Laptop | 0 |
| Paris | Desktop | 0 |
| Paris | Laptop | 0 |
| Berlin | Desktop | 0 |
| Berlin | Laptop | 0 |
Here is how you can do this with all the empty locations as well:
AddMap<InventoryItem>(inventoryItems =>
from inventoryItem in inventoryItems
select new
{
LocationName = inventoryItem.Location.Name,
Items = new[]{
{
ItemTypeName = inventoryItem.ItemType.Name,
Count = 1}
}
});
)
this.AddMap<Location>(locations=>
from location in locations
select new
{
LocationName = location.Name,
Items = new object[0]
});
this.Reduce = results =>
from result in results
group result by result.LocationName into g
select new
{
LocationName = g.Key,
Items = from item in g.SelectMany(x=>x.Items)
group item by item.ItemTypeName into gi
select new
{
ItemTypeName = gi.Key,
Count = gi.Sum(x=>x.Count)
}
};