diff --git a/README.md b/README.md
index 62c8335..0e623c8 100644
--- a/README.md
+++ b/README.md
@@ -25,7 +25,7 @@ Full docs available at [https://theoephraim.github.io/node-google-spreadsheet](h
_The following examples are meant to give you an idea of just some of the things you can do_
-> **IMPORTANT NOTE** - To keep the examples concise, I'm calling await [at the top level](https://v8.dev/features/top-level-await) which is not allowed by default in most versions of node. If you need to call await in a script at the root level, you must instead wrap it in an async function like so:
+> **IMPORTANT NOTE** - To keep the examples concise, I'm calling await [at the top level](https://v8.dev/features/top-level-await) which is not allowed in some older versions of node. If you need to call await in a script at the root level and your environment does not support it, you must instead wrap it in an async function like so:
```javascript
(async function () {
diff --git a/docs/classes/google-spreadsheet-worksheet.md b/docs/classes/google-spreadsheet-worksheet.md
index 0c510e6..d041c6d 100644
--- a/docs/classes/google-spreadsheet-worksheet.md
+++ b/docs/classes/google-spreadsheet-worksheet.md
@@ -333,6 +333,24 @@ Param|Type|Required|Description
?> The authentication method being used must have write access to the destination document as well
+
+#### `setDataValidation(range, rule)` (async) :id=fn-setDataValidation
+> Sets a data validation rule to every cell in the range
+
+Param|Type|Required|Description
+---|---|---|---
+`range`|Object
[GridRange](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#GridRange)|✅|Range of cells to apply the rule to, sheetId not required!
+`rule`|Object
[DataValidationRule](https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#DataValidationRule)
or `false`|✅|Object describing the validation rule
Or `false` to unset the rule
+
+
+- ✨ **Side Effects -** sheet is copied to the other doc
+
+?> The authentication method being used must have write access to the destination document as well
+
+
+
+
+
### Exports
See [Exports guide](guides/exports) for more info.
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4f94997..202f1dd 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1,5 +1,9 @@
lockfileVersion: '6.0'
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
dependencies:
axios:
specifier: ^1.4.0
@@ -436,6 +440,7 @@ packages:
/@commitlint/config-validator@17.4.4:
resolution: {integrity: sha512-bi0+TstqMiqoBAQDvdEP4AFh0GaKyLFlPPEObgI29utoKEYoPQTvF0EYqIwYYLEoJYhj5GfMIhPHJkTJhagfeg==}
engines: {node: '>=v14'}
+ requiresBuild: true
dependencies:
'@commitlint/types': 17.4.4
ajv: 8.12.0
@@ -445,6 +450,7 @@ packages:
/@commitlint/execute-rule@17.4.0:
resolution: {integrity: sha512-LIgYXuCSO5Gvtc0t9bebAMSwd68ewzmqLypqI2Kke1rqOqqDbMpYcYfoPfFlv9eyLIh4jocHWwCK5FS7z9icUA==}
engines: {node: '>=v14'}
+ requiresBuild: true
dev: true
optional: true
@@ -476,6 +482,7 @@ packages:
/@commitlint/resolve-extends@17.4.4:
resolution: {integrity: sha512-znXr1S0Rr8adInptHw0JeLgumS11lWbk5xAWFVno+HUFVN45875kUtqjrI6AppmD3JI+4s0uZlqqlkepjJd99A==}
engines: {node: '>=v14'}
+ requiresBuild: true
dependencies:
'@commitlint/config-validator': 17.4.4
'@commitlint/types': 17.4.4
@@ -489,6 +496,7 @@ packages:
/@commitlint/types@17.4.4:
resolution: {integrity: sha512-amRN8tRLYOsxRr6mTnGGGvB5EmW/4DDjLMgiwK3CCVEmN6Sr/6xePGEpWaspKkckILuUORCwe6VfDBw6uj4axQ==}
engines: {node: '>=v14'}
+ requiresBuild: true
dependencies:
chalk: 4.1.2
dev: true
@@ -1784,6 +1792,7 @@ packages:
/ajv@8.12.0:
resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==}
+ requiresBuild: true
dependencies:
fast-deep-equal: 3.1.3
json-schema-traverse: 1.0.0
@@ -2566,6 +2575,7 @@ packages:
/cosmiconfig-typescript-loader@4.3.0(@types/node@20.2.5)(cosmiconfig@8.1.3)(ts-node@10.9.1)(typescript@4.9.5):
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
engines: {node: '>=12', npm: '>=6'}
+ requiresBuild: true
peerDependencies:
'@types/node': '*'
cosmiconfig: '>=7'
@@ -3858,6 +3868,7 @@ packages:
/global-dirs@0.1.1:
resolution: {integrity: sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==}
engines: {node: '>=4'}
+ requiresBuild: true
dependencies:
ini: 1.3.8
dev: true
@@ -5169,6 +5180,7 @@ packages:
/json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+ requiresBuild: true
dev: true
optional: true
@@ -5344,11 +5356,13 @@ packages:
/lodash.mergewith@4.6.2:
resolution: {integrity: sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==}
+ requiresBuild: true
dev: true
optional: true
/lodash.uniq@4.5.0:
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
+ requiresBuild: true
dev: true
optional: true
@@ -6340,6 +6354,7 @@ packages:
/require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
+ requiresBuild: true
dev: true
optional: true
@@ -6384,6 +6399,7 @@ packages:
/resolve-global@1.0.0:
resolution: {integrity: sha512-zFa12V4OLtT5XUX/Q4VLvTfBf+Ok0SPc1FNGM/z9ctUdiU618qwKpWnd0CHs3+RqROfyEg/DhuHbMWYqcgljEw==}
engines: {node: '>=8'}
+ requiresBuild: true
dependencies:
global-dirs: 0.1.1
dev: true
@@ -7549,7 +7565,3 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: true
-
-settings:
- autoInstallPeers: false
- excludeLinksFromLockfile: false
diff --git a/src/lib/GoogleSpreadsheetWorksheet.ts b/src/lib/GoogleSpreadsheetWorksheet.ts
index d536dc9..7437032 100644
--- a/src/lib/GoogleSpreadsheetWorksheet.ts
+++ b/src/lib/GoogleSpreadsheetWorksheet.ts
@@ -12,6 +12,7 @@ import {
A1Range, SpreadsheetId, DimensionRangeIndexes, WorksheetDimension, WorksheetId, WorksheetProperties, A1Address,
RowIndex, ColumnIndex, DataFilterWithoutWorksheetId, DataFilter, GetValuesRequestOptions, WorksheetGridProperties,
WorksheetDimensionProperties, CellDataRange, AddRowOptions, GridRangeWithOptionalWorksheetId,
+ DataValidationRule,
} from './types/sheets-types';
@@ -819,9 +820,22 @@ export class GoogleSpreadsheetWorksheet {
// https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#SortRangeRequest
}
- async setDataValidation() {
- // Request type = `setDataValidation`
- // https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#SetDataValidationRequest
+ /**
+ * Sets (or unsets) a data validation rule to every cell in the range
+ * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/request#SetDataValidationRequest
+ */
+ async setDataValidation(
+ range: GridRangeWithOptionalWorksheetId,
+ /** data validation rule object, or set to false to clear an existing rule */
+ rule: DataValidationRule | false
+ ) {
+ return this._makeSingleUpdateRequest('setDataValidation', {
+ range: {
+ sheetId: this.sheetId,
+ ...range,
+ },
+ ...rule && { rule },
+ });
}
async setBasicFilter() {
diff --git a/src/lib/types/sheets-types.ts b/src/lib/types/sheets-types.ts
index a9e85dd..3777d59 100644
--- a/src/lib/types/sheets-types.ts
+++ b/src/lib/types/sheets-types.ts
@@ -526,3 +526,88 @@ export type AddRowOptions = {
/** set to true to insert new rows in the sheet while adding this data */
insert?: boolean,
};
+
+/**
+ * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ConditionType
+ */
+export type ConditionType =
+ | 'NUMBER_GREATER'
+ | 'NUMBER_GREATER_THAN_EQ'
+ | 'NUMBER_LESS'
+ | 'NUMBER_LESS_THAN_EQ'
+ | 'NUMBER_EQ'
+ | 'NUMBER_NOT_EQ'
+ | 'NUMBER_BETWEEN'
+ | 'NUMBER_NOT_BETWEEN'
+ | 'TEXT_CONTAINS'
+ | 'TEXT_NOT_CONTAINS'
+ | 'TEXT_STARTS_WITH'
+ | 'TEXT_ENDS_WITH'
+ | 'TEXT_EQ'
+ | 'TEXT_IS_EMAIL'
+ | 'TEXT_IS_URL'
+ | 'DATE_EQ'
+ | 'DATE_BEFORE'
+ | 'DATE_AFTER'
+ | 'DATE_ON_OR_BEFORE'
+ | 'DATE_ON_OR_AFTER'
+ | 'DATE_BETWEEN'
+ | 'DATE_NOT_BETWEEN'
+ | 'DATE_IS_VALID'
+ | 'ONE_OF_RANGE'
+ | 'ONE_OF_LIST'
+ | 'BLANK'
+ | 'NOT_BLANK'
+ | 'CUSTOM_FORMULA'
+ | 'BOOLEAN'
+ | 'TEXT_NOT_EQ'
+ | 'DATE_NOT_EQ'
+ | 'FILTER_EXPRESSION';
+
+/**
+ * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#relativedate
+ */
+export type RelativeDate =
+ | 'PAST_YEAR'
+ | 'PAST_MONTH'
+ | 'PAST_WEEK'
+ | 'YESTERDAY'
+ | 'TODAY'
+ | 'TOMORROW';
+
+/**
+ * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#ConditionValue
+ */
+export type ConditionValue =
+ | { relativeDate: RelativeDate, userEnteredValue?: undefined }
+ | { relativeDate?: undefined, userEnteredValue: string };
+
+/**
+ * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/other#BooleanCondition
+ */
+export type BooleanCondition = {
+ /** The type of condition. */
+ type: ConditionType;
+ /**
+ * The values of the condition.
+ * The number of supported values depends on the condition type. Some support zero values, others one or two values, and ConditionType.ONE_OF_LIST supports an arbitrary number of values.
+ */
+ values: ConditionValue[];
+};
+
+/**
+ * @see https://developers.google.com/sheets/api/reference/rest/v4/spreadsheets/cells#DataValidationRule
+ *
+ * example:
+ * - https://stackoverflow.com/a/43442775/3068233
+ */
+export type DataValidationRule = {
+ /** The condition that data in the cell must match. */
+ condition: BooleanCondition;
+ /** A message to show the user when adding data to the cell. */
+ inputMessage?: string;
+ /** True if invalid data should be rejected. */
+ strict: boolean;
+ /** True if the UI should be customized based on the kind of condition. If true, "List" conditions will show a dropdown. */
+ showCustomUi: boolean;
+};
diff --git a/src/test/manage.test.ts b/src/test/manage.test.ts
index 10868c6..c65a7b0 100644
--- a/src/test/manage.test.ts
+++ b/src/test/manage.test.ts
@@ -190,6 +190,60 @@ describe('Managing doc info and sheets', () => {
});
});
+ describe.only('data validation rules', () => {
+ let sheet: GoogleSpreadsheetWorksheet;
+
+ beforeAll(async () => {
+ sheet = await doc.addSheet({ title: 'validation rules test' });
+ });
+ afterAll(async () => {
+ await sheet.delete();
+ });
+
+
+ it('can set data validation', async () => {
+ // add a dropdown; ref: https://stackoverflow.com/a/43442775/3068233
+ await sheet.setDataValidation(
+ {
+ startRowIndex: 2,
+ endRowIndex: 100,
+ startColumnIndex: 3,
+ endColumnIndex: 4,
+ },
+ {
+ condition: {
+ type: 'ONE_OF_LIST',
+ values: [
+ {
+ userEnteredValue: 'YES',
+ },
+ {
+ userEnteredValue: 'NO',
+ },
+ {
+ userEnteredValue: 'MAYBE',
+ },
+ ],
+ },
+ showCustomUi: true,
+ strict: true,
+ }
+ );
+ });
+
+ it('can clear a data validation', async () => {
+ await sheet.setDataValidation(
+ {
+ startRowIndex: 2,
+ endRowIndex: 100,
+ startColumnIndex: 3,
+ endColumnIndex: 4,
+ },
+ false
+ );
+ });
+ });
+
describe('deleting a sheet', () => {
let sheet: GoogleSpreadsheetWorksheet;
let numSheets: number;