Related
I am setting up a sheet where a person will be able to check a checkbox, in different times, depending on the progress of a task. So, there are 5 checkboxes per row, for a number of different tasks.
Now, the idea is that, when you check one of those checkboxes, a message builds up in the few next cells coming after. So, the message is built in 3 cells. The first cell is just text, the second one is the date, and the third one is time. Also, those cells have 5 paragraphs each (one per checkbox).
The problem comes when I try to make that timestamp stay as it was when it was entered. As it is right now, the time changes every time I update any part of the Google Sheet.
I set u my formulas as follows:
For the text message:
=IF($C4=TRUE,"Insert text 1 here","")&CHAR(10)&IF($E4=TRUE, "Insert text here","")&CHAR(10)&IF($G4=TRUE, "Insert text 3 here","")&CHAR(10)&IF($I4=TRUE, "Insert text 4 here,"")&CHAR(10)&IF($K4=TRUE, "Insert text 5 here","")
For the date:
=IF($C4=TRUE,(TEXT(NOW(),"mmm dd yyyy")),"")&CHAR(10)&IF($E4=TRUE,(TEXT(NOW(),"mmm dd yyyy")),"")&CHAR(10)&IF($G4=TRUE,(TEXT(NOW(),"mmm dd yyyy")),"")&CHAR(10)&IF($I4=TRUE,(TEXT(NOW(),"mmm dd yyyy")),"")&CHAR(10)&IF($K4=TRUE,(TEXT(NOW(),"mmm dd yyyy")),"")
And for the time:
=IF($C4=TRUE,(TEXT(NOW(),"HH:mm")),"")&CHAR(10)&IF($E4=TRUE,(TEXT(NOW(),"HH:mm")),"")&CHAR(10)&IF($G4=TRUE,(TEXT(NOW(),"HH:mm")),"")&CHAR(10)&IF($I4=TRUE,(TEXT(NOW(),"HH:mm")),"")&CHAR(10)&IF($K4=TRUE,(TEXT(NOW(),"HH:mm")),"")
And it all looks like this:
I would appreciate it greatly if anyone could help me get this to work so that date and time are inserted after checking those boxes and they donĀ“t change again
Notice that your struggle with the continuous changing date time. I had the same struggle as yours over the year, and I found a solution that works for my case nicely. But it needs to be a little more "dirty work" with Apps Script
Some background for my case:
I have multiple sheets in the spreadsheet to run and generate the
timestamp
I want to skip my first sheet without running to generate timestamp
in it
I want every edit, even if each value that I paste from Excel to
generate timestamp
I want the timestamp to be individual, each row have their own
timestamp precise to every second
I don't want a total refresh of the entire sheet timestamp when I am
editing any other row
I have a column that is a MUST FILL value to justify whether the
timestamp needs to be generated for that particular row
I want to specify my timestamp on a dedicated column only
function timestamp() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const totalSheet = ss.getSheets();
for (let a=1; a<totalSheet.length; a++) {
let sheet = ss.getSheets()[a];
let range = sheet.getDataRange();
let values = range.getValues();
function autoCount() {
let rowCount;
for (let i = 0; i < values.length; i++) {
rowCount = i
if (values[i][0] === '') {
break;
}
}
return rowCount
}
rowNum = autoCount()
for(let j=1; j<rowNum+1; j++){
if (sheet.getRange(j+1,7).getValue() === '') {
sheet.getRange(j+1,7).setValue(new Date()).setNumberFormat("yyyy-MM-dd hh:mm:ss");
}
}
}
}
Explanation
First, I made a const totalSheet with getSheets() and run it
with a for loop. That is to identify the total number of sheets
inside that spreadsheet. Take note, in here, I made let a=1;
supposed all JavaScript the same, starts with 0, value 1 is to
skip the first sheet and run on the second sheet onwards
then, you will notice a function let sheet = ss.getSheets()[a]
inside the loop. Take note, it is not supposed to use const if
your value inside the variable is constantly changing, so use
let instead will work fine.
then, you will see a function autoCount(). That is to make a for
loop to count the number of rows that have values edited in it. The
if (values[i][0] === '') is to navigate the script to search
through the entire sheet that has value, looking at the row i and
the column 0. Here, the 0 is indicating the first column of the
sheet, and the i is the row of the sheet. Yes, it works like a
json object with panda feeling.
then, you found the number of rows that are edited by running the
autoCount(). Give it a rowNum variable to contain the result.
then, pass that rowNum into a new for loop, and use if (sheeet.getRange(j+1,7).getValue() === '') to determine which row
has not been edited with timestamp. Take note, where the 7 here
indicating the 7th column of the sheet is the place that I want a
timestamp.
inside the for loop, is to setValue with date in a specified
format of ("yyyy-MM-dd hh:mm:ss"). You are free to edit into any
style you like
ohya, do remember to deploy to activate the trigger with event type
as On Change. That is not limiting to edit, but for all kinds of
changes including paste.
Here's a screenshot on how it would look like:
Lastly, please take note on some of my backgrounds before deciding to or not to have the solution to work for your case. Cheers, and happy coding~!
I have created a SQFlite database for my app which is working very well. Basically, the app is to create a list of my client's company details. The company name appears in an Expansion Tile, and once you open the tile, you see the rest of the company's details. I know how to arrange the tiles in either descending or ascending order, but is there a way to arrange according to alphabetic order based on the company name? Thus, can one arrange the tables in SQFlite in alphabetic order instead of ASC or DESC? When my client's details are added dynamically, I want them to be arranged in alphabetic order so that it makes more sense. Thank you so much for any help.. I have tried to find a comment on this on Stackoverflow, but don't seem to be able to find one.
Ok, I managed to figure this out. My question really shows my inexperience. Anyway, Query has a orderBy property. In my database I created the following code:
Future<void> _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE todo(
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT,
name TEXT,
phone TEXT,
fax TEXT,
email TEXT,
street TEXT,
city TEXT,
town TEXT,
code TEXT,
isExpanded INTEGER
orderBy used to be 'id DESC'. It should simply be changed to 'title ASC'. Thus, it will order the sequence by the title in alphabetic order from top to bottom. Here is the code:
Future<List<Todo>> getTodo() async {
final db = await database;
// query the database and save as list of maps
List<Map<String, dynamic>> items = await db.query(
'todo',
orderBy: 'title ASC',
);
Thus, the correct question is not how to order in alphabetic order, but how to order based on title (which is the company name in my example) in ASCending order.
In Excel I have a data table of Paired Items that are tagged with an identifier. Essentially, named linkages.
Worksheet: Links
Tag
Point-A
Point-B
Route 1
Home
Office
Route 2
Home
Grocery 1
Happy Hour
Office
Bar
Sad Hour
Office
Dump
Headaches
Bar
Pharmacy
Sick
Bar
Dump
Route 3
Office
Moms
Route 4
Office
Park
Victory
Park
Bar
Discard
Park
Dump
I want to transform this data into a grid of all points in rows and columns with the tag placed at the intersection (Much like old paper road maps with grids for city distances)
Worksheet: Grid
A \ B
Bar
Dump
Grocery 1
Home
Home
Moms
Office
Office
Park
Pharmacy
Bar
Sick
Happy Hour
Victory
Headaches
Dump
Sick
Sad Hour
Discard
Grocery 1
Route 2
Home
Route 1
Home
Route 2
Moms
Route 3
Office
Happy Hour
Sad Hour
Route 1
Route 3
Office
Route 4
Park
Victory
Discard
Route 4
Pharmacy
Headaches
I have written the following M code for transforming, but it seems a bit wayward and overwrought. I am using bit coding of points to construct a join key, so the bitting process will probably break around 32 points.
Is there a shorter set of LETs that do the same transform to grid ?
Is there a way to create a key that is Min(Point-A,Point-B) delimited concatenation with Max(Point-A,Point-B), and thus not rely of bitting?
M code (copied from Advanced Editor)
let
LinksTable = Table.SelectRows(Excel.CurrentWorkbook(), each [Name] = "Links"),
Links = Table.RemoveColumns(Table.ExpandTableColumn(LinksTable, "Content", {"Tag", "Point-A", "Point-B"}), "Name"),
AllPoints = Table.Combine(
{ Table.SelectColumns(Table.RenameColumns(Links,{"Point-A", "Point"}), "Point"),
Table.SelectColumns(Table.RenameColumns(Links,{"Point-B", "Point"}), "Point")
}),
ThePoints = Table.Sort(Table.Distinct(AllPoints),{"Point"}),
PointsIndexed = Table.AddIndexColumn(ThePoints, "Index", 0, 1, Int64.Type),
PointsBitted = Table.RemoveColumns(Table.AddColumn(PointsIndexed, "Bit", each Number.Power(2, [Index]), Int64.Type),"Index"),
AllPairsBitted = Table.Join(
Table.RenameColumns(PointsBitted, {{"Point", "Point-A"}, {"Bit", "Bit-A"}}), {},
Table.RenameColumns(PointsBitted, {{"Point", "Point-B"}, {"Bit", "Bit-B"}}), {},
JoinKind.FullOuter
),
AllPairsKeyed = Table.RemoveColumns(
Table.AddColumn(AllPairsBitted, "BitKeyPair", each Number.BitwiseOr([#"Bit-A"],[#"Bit-B"])),
{ "Bit-A", "Bit-B"}
),
#"Links-A-Bitted" = Table.Join(
Links, "Point-A",
Table.RenameColumns(PointsBitted,{{"Point", "Point-A"}, {"Bit", "Bit-A"}}), "Point-A"
),
#"Links-AB-Bitted" = Table.Join(
#"Links-A-Bitted", "Point-B",
Table.RenameColumns(PointsBitted,{{"Point", "Point-B"}, {"Bit", "Bit-B"}}), "Point-B"
),
LinksKeyed = Table.RemoveColumns(
Table.AddColumn(#"Links-AB-Bitted", "BitKeyLink", each Number.BitwiseOr([#"Bit-A"],[#"Bit-B"])),
{ "Bit-A", "Bit-B"}
),
AllPairsTagged = Table.Sort( Table.RemoveColumns(
Table.Join(
AllPairsKeyed, "BitKeyPair",
Table.SelectColumns(LinksKeyed, {"BitKeyLink", "Tag"}), "BitKeyLink",
JoinKind.LeftOuter
),
{"BitKeyPair", "BitKeyLink"}
),
{"Point-A", "Point-B"}
),
Grid = Table.Pivot(AllPairsTagged, List.Distinct(AllPairsTagged[#"Point-B"]), "Point-B", "Tag", List.First)
in
Grid
I think you can use PIVOT to achieve this. Using directly this functionality would not work because you are looking for symmetry of columns and rows.
The trick is to force that symmetry, appending values from Point-B into values of Point-A.
Steps
Create a secondary table and reorder the columns in the opposite way that the original table, so Tag, Point-B and Point-A.
On the secondary table, rename the columns to Tag, Point-A and Point-B in that order. Append usually take column names literally, so without renaming it would append the names of the same columns.
Pivot on column Point-B without aggregating data.
Reorder the columns using Point-A as a reference, so you have symmetry of columns and rows.
It's worth mentioning that's good practice to Buffer the source table because is used multiple times across the calculations.
Calculation
let
Source = Table.FromRows(Json.Document(Binary.Decompress(Binary.FromText("Zc69CsMgEMDxVwnOLv14ghKoS2looEvIcJwXIkEMZxx8+6axiUInwd/99bpOvFxYqDoJKZSztB7PYTBIope7nbPd2SFxXMe/rGCeY6Vc4JxJcQPetAX9Z3Wwc0oJNOBI/hdI0YzAFjCm1uB0yBGldS7lgw9nfWHX0hrgabO3wcVx3K/yirXxCKwzpK/6Dw==", BinaryEncoding.Base64), Compression.Deflate)), let _t = ((type nullable text) meta [Serialized.Text = true]) in type table [Tag = _t, #"Point-A" = _t, #"Point-B" = _t]),
BufferedSource = Table.Buffer(Source),
SecondTable = Table.ReorderColumns(BufferedSource,{"Tag","Point-B","Point-A"}),
SecondTableRenameCols = Table.RenameColumns(SecondTable,{{"Point-A","Point-B"},{"Point-B","Point-A"}}),
AppendTables = Table.Combine({BufferedSource,SecondTableRenameCols}),
PivotTables = Table.Pivot(AppendTables, List.Distinct(AppendTables[#"Point-B"]), "Point-B", "Tag"),
ReorderCols = Table.ReorderColumns( PivotTables, PivotTables[#"Point-A"])
in ReorderCols
Output
Point-A
Bar
Dump
Grocery 1
Home
Moms
Office
Park
Pharmacy
Bar
Sick
Happy Hour
Victory
Headaches
Dump
Sick
Sad Hour
Discard
Grocery 1
Route 2
Home
Route 2
Route 1
Moms
Route 3
Office
Happy Hour
Sad Hour
Route 1
Route 3
Route 4
Park
Victory
Discard
Route 4
Pharmacy
Headaches
I am a complete beginner who knows a little bit of HTML and Java so my question might sound very dumb.
I'm basically trying to use Google Spreadsheets in order to track the availability of an item/its price on this website. I'm using the "IMPORTXML" function and have no trouble getting the title of the product or its description. However I cannot get the price as it needs me to select a size first, which I don't know how to do through the "IMPORTXML" function.
Right now, this returns "Imported content is empty.":
=IMPORTXML("https://www.artisan-jp.com/fx-hien-eng.html","//p[#id='price']")
Would creating a function through Google Script work? If so, how do I do it?
Thank you!
You won't be able to do fetch any data with IMPORTXML since Javascript is used to display the price. With IMPORTFROMWEB addon, you can activate JS rendering but you'll only get the price of the default product.
It's probably better to use Selenium + Python (or any other language) to achieve your goal. That way you'll be able to click and select a specific product.(size, color, hardness)
If you really want to do this with a Google solution, you'll have to write your own custom function in Google Apps Script (send a POST request over a specific url : https://www.artisan-jp.com/get_syouhin.php). Something like :
function myFunction() {
var formData = {
'kuni': 'on',
'sir': '140',
'size': '1',
'color': '1',
};
var options = {
'method' : 'post',
'payload' : formData
};
Logger.log(UrlFetchApp.fetch('https://www.artisan-jp.com/get_syouhin.php', options).getContentText());
}
In the first part (formData), your declare the parameters of the POST. These parameters correspond to the properties of the product.
Sir :
XSoft = 140
Soft = 141
Mid = 142
Size :
S = 1
M = 2
L = 3
XL = 4
Color :
Red = 1
Black = 5
Output :
You'll get the reference number, the description of the product and its price.
When the product is not in stock, there's a preceding NON in the output.
It's up to you now to extract the data of interest from the output and to populate the cells of your workbook.
Assuming your function is named "mouse". Just use SPLIT to display the data properly.
=SPLIT(mouse();"/")
To extract the price only, you can use SPLIT then QUERY. SUBSTITUTE is used to coerce the result to a number.
=SUBSTITUTE(QUERY(SPLIT(mouse();"/");"select Col4");".";",")*1
I am recreating and expanding on a doc I had previously made. I have already brought in the script I had used originally, and tweaked it where I believed appropriate to get it working in this sheet, but I must have missed something. Editable samples of the 3 spreadsheet files involved can be found here. These files are a sample "Price List", "Catalog"(which aggregates manufacturer names from all price lists, and also has a "Catalog" tab for misc items not sold by one of my primary vendors), and "Addendum B" which is the file I require assistance with.
This document is an addendum to my contracts which lists all equipment being sold as part of that contract. It has 2 sheets in it ("Addendum B" and "XREF"), and "Addendum B" has several dependent columns: Vendor, Manufacturer, Model, Description, and Price. Their dependencies are as follows:
Currently Working
Vendor: Basic data validation pulling from XREF!A2:A.
Not working, script in file
Manufacturer: Based on the Vendor selected, should be a drop-down
list generated from the column headed with that vendor's name on
"XREF".
Now here's were it gets tricky beyond what I had previously done.
Model: I want this column to be a drop-down listing all model numbers
associated with that manufacturer, from a completely separate price
list provided to me by my vendor. (I have shared a sample price list which reflects column positions as they appear in all such files.
Description: Displays the corresponding description for the Model selected, from the price list selected in the Vendor column.
Price: Displays the corresponding markup price for the Model selected, from the price list selected in the Vendor column.
And that about summarizes my goals and what I'm struggling with.
So I looked into your script file in sheet Addendum B.
I have made few edits and it should be working now, the modified code:
function onEdit()
{
var ss = SpreadsheetApp.getActiveSpreadsheet(),
sheet = ss.getActiveSheet(),
name = sheet.getName();
if (name != 'Addendum B') return;
var range = sheet.getActiveRange(),
col = range.getColumn();
if (col != 6) return; //You col was set to 5 changed it to 6!
var val = range.getValue(),
dv = ss.getSheetByName('XREF'),
data = dv.getDataRange().getValues(),
catCol = data[0].indexOf(val),
list = [];
Logger.log(catCol)
for (var i = 1, len = 100; i < len; i++) // Problem is here, you have too many items in list! Cannot have more 500 items for validation
list.push(data[i][catCol]);
var listRange = dv.getRange(2,catCol +1,dv.getLastRow() - 1, 1)
Logger.log(list)
var cell = sheet.getRange(range.getRow(), col-1)
var rule = SpreadsheetApp.newDataValidation()
.requireValueInRange(listRange) // Use requireValueIn Range instead to fix the problem
.build();
cell.setDataValidation(rule);
Logger.log(cell.getRow())
}
The reason your validation was not working was you had more than 500 items in your data validation list. I just modified it to take the same values from range instead. Hope you find that helpful!
Now for the remaining 3 questions, here are my comments and thoughts on it:
1) I didn't find any code related to the problem you mentioned in your question. So, I am gonna assume you are asking for general ideas on how to achieve this?
2) You basically approach the problem the same as you did with the above code! Once a manufacturer is selected, the script looks for that manufacturer in the sheet and update the Data validation in the corresponding model column.
You will modify the code like so
var ss = SpreadsheetApp.openById("1nbCJOkpIQxnn71sJPj6X4KaahROP5cMg1SI9xIeJdvY")
//The above code with select the catalog sheet.
dv = ss.getSheetByName('Misc_Catalog')
//The above code will open the Misc_Catalog tab.
3) A better approach would be to use Sidebar/Dialog Box to validate your input then add it to the sheet at the end. (Looks Cleaner and also prevents unnecessary on edit trigger in the sheet, which can take a while to update.)
You find more details here: https://developers.google.com/apps-script/guides/dialogs