目前,电子表格/表格/范围类中的Google Apps脚本没有undo()
功能。在问题跟踪器上打开了一些问题,我现在只能找到一个(我不知道Triaged的含义):here。
有人提出使用DriveApp和修订历史记录的解决方法,但我环顾四周并没有找到任何东西(也许它被埋没了?)。无论如何,对于许多不同的操作来说,undo()
函数是非常必要的。我只能想到一种解决方法,但我无法让它工作(数据的存储方式,我不知道它是否可能)。这是一些伪 -
function onOpen () {
// Get all values in the sheet(s)
// Stringify this/each (matrix) using JSON.stringify
// Store this/each stringified value as a Script or User property (character limits, ignore for now)
}
function onEdit () {
// Get value of edited cell
// Compare to some value (restriction, desired value, etc.)
// If value is not what you want/expected, then:
// -----> get the stringified value and parse it back into an object (matrix)
// -----> get the old data of the current cell location (column, row)
// -----> replace current cell value with the old data
// -----> notifications, coloring cell, etc, whatever else you want
// If the value IS what you expected, then:
// -----> update the 'undoData' by getting all values and re-stringifying them
// and storing them as a new Script/User property
}
基本上,当打开电子表格时,将所有值存储为脚本/用户属性,并仅在满足某些单元格条件(on)时引用它们。如果要撤消,请获取存储在当前单元位置的旧数据,并将当前单元格的值替换为旧数据。如果不需要撤消该值,则更新存储的数据以反映对电子表格所做的更改。
到目前为止,我的代码已经破了,我认为这是因为当对象被字符串化并存储时(例如,它没有正确解析),嵌套数组结构会丢失。如果有人写过这种功能,请分享。否则,有关如何编写此内容的建议将会有所帮助。
编辑:这些文件非常不稳定。行/列的数量不会改变,数据的位置也不会改变。如果可能的话,为临时修订历史实现 get-all-data / store-all-data 类型函数实际上将满足我的需求。
答案 0 :(得分:8)
当我需要保护工作表但允许通过侧边栏进行编辑时,我遇到了类似的问题。我的解决方案是有两张纸(一张隐藏)。如果编辑第一个工作表,则会触发onEdit过程并从第二个工作表重新加载值。如果您取消隐藏并编辑第二张纸,则会从第一张纸重新加载。工作完美,非常有趣,删除质量数据,并观看自我修复!
答案 1 :(得分:4)
只要您不添加或删除行和列,就可以依赖行号和列号作为存储在ScriptDb中的历史值的索引。
function onEdit(e) {
// Exit if outside validation range
// Column 3 (C) for this example
var row = e.range.getRow();
var col = e.range.getColumn();
if (col !== 3) return;
if (row <= 1) return; // skip headers
var db = ScriptDb.getMyDb();
// Query database for history on this cell
var dbResult = db.query({type:"undoHistory",
row:row,
col:col});
if (dbResult.getSize() > 0) {
// Found historic value
var historicObject = dbResult.next();
}
else {
// First change for this cell; seed historic value
historicObject = db.save({type:"undoHistory",
row:row,
col:col,
value:''});
}
// Validate the change.
if (valueValid(e.value,row,col)) {
// update script db with this value
historicObject.value = e.value;
db.save(historicObject);
}
else {
// undo the change.
e.range.getSheet()
.getRange(row,col)
.setValue(historicObject.value);
}
}
您需要提供验证数据值的函数。同样,在此示例中,我们只关心一列中的数据,因此验证非常简单。例如,如果您需要执行不同类型的验证,那么您可以在switch
参数上col
。
/**
* Test validity of edited value. Return true if it
* checks out, false if it doesn't.
*/
function valueValid( value, row, col ) {
var valid = false;
// Simple validation rule: must be a number between 1 and 5.
if (value >= 1 && value <= 5)
valid = true;
return valid;
}
此撤消功能适用于协同编辑的电子表格,尽管在脚本数据库中存储历史值存在竞争条件。如果多个用户同时对单元格进行首次编辑,则数据库最终可能会有多个表示该单元格的对象。在后续更改中,query()
的使用和仅选择第一个结果的选项可确保只选择其中一个倍数。
如果这成为问题,可以通过将函数包含在Lock中来解决。
答案 2 :(得分:1)
修改了小组的答案,以便在用户选择多个小区时允许范围:
我已经使用了我所称的&#34; Dual Sheets&#34;。
/**
* Test function for onEdit. Passes an event object to simulate an edit to
* a cell in a spreadsheet.
* Check for updates: https://stackoverflow.com/a/16089067/1677912
*/
function test_onEdit() {
onEdit({
user : Session.getActiveUser().getEmail(),
source : SpreadsheetApp.getActiveSpreadsheet(),
range : SpreadsheetApp.getActiveSpreadsheet().getActiveCell(),
value : SpreadsheetApp.getActiveSpreadsheet().getActiveCell().getValue(),
authMode : "LIMITED"
});
}
function onEdit() {
// This script prevents cells from being updated. When a user edits a cell on the master sheet,
// it is checked against the same cell on a helper sheet. If the value on the helper sheet is
// empty, the new value is stored on both sheets.
// If the value on the helper sheet is not empty, it is copied to the cell on the master sheet,
// effectively undoing the change.
// The exception is that the first few rows and the first few columns can be left free to edit by
// changing the firstDataRow and firstDataColumn variables below to greater than 1.
// To create the helper sheet, go to the master sheet and click the arrow in the sheet's tab at
// the tab bar at the bottom of the browser window and choose Duplicate, then rename the new sheet
// to Helper.
// To change a value that was entered previously, empty the corresponding cell on the helper sheet,
// then edit the cell on the master sheet.
// You can hide the helper sheet by clicking the arrow in the sheet's tab at the tab bar at the
// bottom of the browser window and choosing Hide Sheet from the pop-up menu, and when necessary,
// unhide it by choosing View > Hidden sheets > Helper.
// See https://productforums.google.com/d/topic/docs/gnrD6_XtZT0/discussion
// modify these variables per your requirements
var masterSheetName = "Master" // sheet where the cells are protected from updates
var helperSheetName = "Helper" // sheet where the values are copied for later checking
var ss = SpreadsheetApp.getActiveSpreadsheet();
var masterSheet = ss.getActiveSheet();
if (masterSheet.getName() != masterSheetName) return;
var masterRange = masterSheet.getActiveRange();
var helperSheet = ss.getSheetByName(helperSheetName);
var helperRange = helperSheet.getRange(masterRange.getA1Notation());
var newValue = masterRange.getValues();
var oldValue = helperRange.getValues();
Logger.log("newValue " + newValue);
Logger.log("oldValue " + oldValue);
Logger.log(typeof(oldValue));
if (oldValue == "" || isEmptyArrays(oldValue)) {
helperRange.setValues(newValue);
} else {
Logger.log(oldValue);
masterRange.setValues(oldValue);
}
}
// In case the user pasted multiple cells this will be checked
function isEmptyArrays(oldValues) {
if(oldValues.constructor === Array && oldValues.length > 0) {
for(var i=0;i<oldValues.length;i++) {
if(oldValues[i].length > 0 && (oldValues[i][0] != "")) {
return false;
}
}
}
return true;
}