Google Apps Script

https://developers.google.com/apps-script

Automations

Auto-sort by column

function onEdit(e) {
  const sheet = e.source.getActiveSheet();
  const lastRow = sheet.getLastRow();
  const range = sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn()); // Get data starting from row 2 (Assumes row 1 is a header row )
  
      if (sheet.getName() === "Sheet1") {
    // Sort the range by column 6 (F), ascending
    range.sort({ column: 6, ascending: true });
  }
}

Handle Manual Execution

  • If you want to run the script manually for testing, you can modify the code to handle the absence of the event object (e).
 if (!e) {
    // Simulate the event object for manual testing
    e = { source: SpreadsheetApp.getActiveSpreadsheet() };
  }
 
  const sheet = e.source.getActiveSheet();

Create dynamic header dictionary

// Function to create the header dictionary
function getHeaderDict(sheet) {
  const lastRow = sheet.getLastRow();
  const headerRow = 1; // The row containing the headers
 
  // Get the headers from the first row
  const headers = sheet.getRange(headerRow, 1, 1, sheet.getLastColumn()).getValues()[0];
 
  // Create a dictionary (object) of headers with their column indexes
  const headerDict = headers.reduce((acc, header, index) => {
    acc[header] = index + 1; // Add 1 because column indexes are 1-based in Apps Script
    return acc;
  }, {});
 
  Logger.log('Header Dictionary: %s', JSON.stringify(headerDict));
  return headerDict;
}

Auto-sort with dynamic header lookup

function sortData(sheet, headerDict){
  const lastRow = sheet.getLastRow();
  const dateColumnIndex = headerDict["c"];
 
  if (!dateColumnIndex) {
    console.error("The 'Date' column was not found.");
    return;
  }
 
  // Define the data range starting from row 2
  const dataRange = sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn());
 
  // Sort the range by the "Date" column, ascending
  dataRange.sort({ column: dateColumnIndex, ascending: true });
}

Add timestamp

// Auto-add timestamp (or clear if rest of row was cleared)
function addTimestamp(sheet, row, headerDict) {
  const dateAddedIndex = headerDict["Date Added"];
  const lastCol = sheet.getLastColumn();
 
  const rowValues = sheet.getRange(row, 1, 1, lastCol).getValues()[0];
  const dateValue = rowValues[dateAddedIndex - 1];
 
  // Check whether rest of row was cleared
  const hasOtherData = rowValues
    .filter((_, index) => index !== (dateAddedIndex - 1))
    .some(cell => cell !== "" && cell !== null);
 
  if (hasOtherData && !dateValue) {
    // Add timestamp if there's other data but no timestamp yet
    const timestamp = new Date();
    sheet.getRange(row, dateAddedIndex).setValue(timestamp);
  } else if (!hasOtherData && dateValue) {
    // Clear timestamp if it's the only value in the row
    sheet.getRange(row, dateAddedIndex).clearContent();
  }
}
 

Run script when sheet is edited

// Function triggered on edit event
function onEdit(e) {
  if (!e) {
    // Simulate the event object for manual testing
    e = { source: SpreadsheetApp.getActiveSpreadsheet() };
  }
 
  const sheet = e.source.getActiveSheet();
  if (sheet.getName() !== "Sheet1") return; // Only run on "Sheet1"
 
  // Get the header dictionary from the sheet
  const headerDict = getHeaderDict(sheet);
 
  const editedRange = e.range;
  const editedRow = editedRange.getRow();
 
  // Add timestamp if needed
  addTimestamp(sheet, editedRow, headerDict);
 
  // Auto-sort 
  sortData(sheet,headerDict);
}

Starter Template

// Function triggered on edit event
function onEdit(e) {
  if (!e) {
    // Simulate the event object for manual testing
    e = { source: SpreadsheetApp.getActiveSpreadsheet() };
  }
 
  const sheet = e.source.getActiveSheet();
  // if (sheet.getName() !== "Sheet1") return; // Only run on "Sheet1"
  const sheetName = sheet.getName(); // Get the name of the active sheet
  const validSheets = ["Sheet1", "Sheet2"]; // Array of valid sheet names
  if (!validSheets.includes(sheetName)) return; // If not in the list, exit function
  Logger.log(sheet);
 
  // Get the header dictionary from the sheet
  const headerDict = getHeaderDict(sheet);
 
  const editedRange = e.range;
  const editedRow = editedRange.getRow();
 
  // Uncomment to insert timestamp 
  // addTimestamp(sheet, editedRow, headerDict);
 
  // Auto-sort 
  sortData(sheet,headerDict);
}
 
// Function to create the header dictionary
function getHeaderDict(sheet) {
  const lastRow = sheet.getLastRow();
  const headerRow = 1; // The row containing the headers
 
  // Get the headers from the first row
  const headers = sheet.getRange(headerRow, 1, 1, sheet.getLastColumn()).getValues()[0];
 
  // Create a dictionary (object) of headers with their column indexes
  const headerDict = headers.reduce((acc, header, index) => {
    acc[header] = index + 1; // Add 1 because column indexes are 1-based in Apps Script
    return acc;
  }, {});
 
 
  // Convert dictionary to a formatted JSON string for better readability
  const headerDictString = JSON.stringify(headerDict, null, 2);
  Logger.log('Header Dictionary: %s', headerDictString);
  // SpreadsheetApp.getUi().alert("Header Dictionary:\n" + headerDictString);
 
  return headerDict;
}
 
function sortData(sheet, headerDict) {
  const lastRow = sheet.getLastRow();
  const dateColumn = "Date";
  const timeColumn = "Time";
 
  const dateColumnIndex = headerDict[dateColumn];
  const timeColumnIndex = headerDict[timeColumn];
 
  if (!dateColumnIndex || !timeColumnIndex) {
    console.error("The 'Date' or 'Time' column was not found.");
    SpreadsheetApp.getUi().alert("The 'Date' or 'Time' column was not found.");
    return;
  }
 
  // Define the data range starting from row 2
  const dataRange = sheet.getRange(2, 1, lastRow - 1, sheet.getLastColumn());
 
  // Sort the range first by Date, then by Time (both ascending)
  dataRange.sort([
    { column: dateColumnIndex, ascending: true },
    { column: timeColumnIndex, ascending: true }
  ]);
}
 
 
 
// Function to add timestamp when a new row is added
// Auto-add timestamp (or clear if rest of row was cleared)
function addTimestamp(sheet, row, headerDict) {
  const dateAddedIndex = headerDict["Date Added"];
  const lastCol = sheet.getLastColumn();
 
  const rowValues = sheet.getRange(row, 1, 1, lastCol).getValues()[0];
  const dateValue = rowValues[dateAddedIndex - 1];
 
  // Check whether rest of row was cleared
  const hasOtherData = rowValues
    .filter((_, index) => index !== (dateAddedIndex - 1))
    .some(cell => cell !== "" && cell !== null);
 
  if (hasOtherData && !dateValue) {
    // Add timestamp if there's other data but no timestamp yet
    const timestamp = new Date();
    sheet.getRange(row, dateAddedIndex).setValue(timestamp);
  } else if (!hasOtherData && dateValue) {
    // Clear timestamp if it's the only value in the row
    sheet.getRange(row, dateAddedIndex).clearContent();
  }
}
 

Todo: add option for Time column

// Function to add timestamp and time when a new row is added
function addTimestamp(sheet, row, headerDict) {
  const dateAddedIndex = headerDict["Date Added"];
  const timeAddedIndex = headerDict["Time"];
 
  const timestamp = new Date();
  const timeString = timestamp.toLocaleTimeString(); // Format the time
 
  // If the "Date Added" column is empty, add date and time
  if (!sheet.getRange(row, dateAddedIndex).getValue()) {
    sheet.getRange(row, dateAddedIndex).setValue(timestamp);
  }
 
  // If the "Time" column is empty, add only the time
  if (!sheet.getRange(row, timeAddedIndex).getValue()) {
    sheet.getRange(row, timeAddedIndex).setValue(timeString);
  }
}