{"id":29684926,"url":"https://github.com/sangnandar/config-driven-apps-script-framework","last_synced_at":"2025-07-23T03:09:44.646Z","repository":{"id":305145972,"uuid":"1006524126","full_name":"sangnandar/Config-Driven-Apps-Script-Framework","owner":"sangnandar","description":"A modular, config-based framework to streamline Google Sheets development with Apps Script. Define layout, validation, formatting, and automation logic in one place using a central SHEETCONFIG and the SmartSheet utility class.","archived":false,"fork":false,"pushed_at":"2025-07-18T11:56:00.000Z","size":18,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-07-18T15:51:14.115Z","etag":null,"topics":["design-pattern","google-apps-script","google-sheets","modular-design","spreadsheet-automation"],"latest_commit_sha":null,"homepage":"https://script.google.com/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sangnandar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-06-22T13:16:49.000Z","updated_at":"2025-07-18T11:56:04.000Z","dependencies_parsed_at":"2025-07-18T15:54:18.773Z","dependency_job_id":"6b7e0cfc-6a86-4e2d-aaed-8b28982e7468","html_url":"https://github.com/sangnandar/Config-Driven-Apps-Script-Framework","commit_stats":null,"previous_names":["sangnandar/config-driven-apps-script-framework"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/sangnandar/Config-Driven-Apps-Script-Framework","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangnandar%2FConfig-Driven-Apps-Script-Framework","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangnandar%2FConfig-Driven-Apps-Script-Framework/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangnandar%2FConfig-Driven-Apps-Script-Framework/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangnandar%2FConfig-Driven-Apps-Script-Framework/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sangnandar","download_url":"https://codeload.github.com/sangnandar/Config-Driven-Apps-Script-Framework/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sangnandar%2FConfig-Driven-Apps-Script-Framework/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266609652,"owners_count":23955661,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-07-23T02:00:09.312Z","response_time":66,"last_error":null,"robots_txt_status":null,"robots_txt_updated_at":null,"robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["design-pattern","google-apps-script","google-sheets","modular-design","spreadsheet-automation"],"created_at":"2025-07-23T03:09:44.176Z","updated_at":"2025-07-23T03:09:44.636Z","avatar_url":"https://github.com/sangnandar.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Config-Based Apps Script Framework for Google Sheets\n\nA modular, config-driven framework that simplifies building scalable and maintainable Google Sheets solutions using Google Apps Script. By centralizing logic in a structured `SHEETCONFIG` object and utilizing a powerful `SmartSheet` class, this framework helps you manage layout, data rules, and automation logic consistently across multiple sheets.\n\nThis project is the final iteration of concepts developed in previous works:\n\n- [📊 Dynamic Google Sheets Layout](https://github.com/sangnandar/Dynamic-Google-Sheets-Layout)\n- [📋 Lookup Table Pattern in Apps Script](https://github.com/sangnandar/Lookup-Table-Pattern)\n- [⚡ Event Dispatcher in Apps Script](https://github.com/sangnandar/Event-Dispatcher-in-Apps-Script)\n\n\n## ✨ Features\n\n- 🧩 **Centralized Configuration (`SHEETCONFIG`)**\n  - Define columns, named ranges, validation rules, formatting rules, and edit behavior in one place.\n- 🧠 **SmartSheet Class**\n  - A utility wrapper that interprets `SHEETCONFIG` and simplifies reading/writing data.\n- 🛡️ **Validation \u0026 Conditional Formatting**\n  - Attach rules by column or named range with support for list values and styling.\n- ⚡ **Calculated Columns \u0026 Named Ranges**\n  - Use inline formulas or functions with `$var` injection to populate dynamic values.\n- 🔐 **Protected Fields**\n  - Mark columns or named ranges as locked to prevent user editing.\n- ✅ **Rule-Based onEdit Handlers**\n  - Handle edit events declaratively using rule conditions and handlers.\n- 🔧 **Debug Mode Support**\n  - Switch between production and development sheets easily.\n\n\n## 📁 Project Structure\n\n```\n\n/src\n├── config.gs             # Global SHEETCONFIG and constants\n├── SmartSheet.gs         # SmartSheet utility class\n├── utils.gs              # Utility and helper functions\n├── triggers.gs           # Entry points for onOpen, onEdit, installable, etc.\n├── custom-functions.gs   # Custom spreadsheet functions\n└── event-handlers.gs     # Named event handlers used in config\n\n````\n\n\n## 🛠️ Getting Started\n\n1. **Copy the contents of `/src` into your Apps Script project.**\n2. Define your sheet layout and logic in `config.gs` using the `SHEETCONFIG` object.\n3. Customize `triggers.gs` to apply your configuration and handle edits.\n4. Add formulas, validations, and formatters as needed.\n\n---\n\n## 📦 Example: Employees Sheet\n\n```js\nconst SHEETCONFIG = readOnlyObject({\n  Employees: {\n    layout: {\n      headerRows: 4,\n      columns: {\n        A: { name: 'name',       type: 'string' },\n        B: { name: 'age',        type: 'number' },\n        C: { name: 'joinDate',   type: 'date' },\n        D: { name: 'department', type: 'string' },\n        E: { name: 'score',      type: 'number' },\n        F: { // calculated column\n          name: 'relativeScore',\n          formula: 'IF(ISBLANK($score), \"\", $score / AVERAGE(E5:E))',\n          lock: true\n        }\n      },\n      namedRanges: {\n        'B1': { name: 'selectDepartment', type: 'string' },\n        'E1': { // calculated named-range\n          name: 'cellE1',\n          args: ['selectDepartment'],\n          formula: forE1,\n          lock: true\n        }\n      }\n    },\n    onEditRules: [\n      {\n        condition: (e, smartSheet) =\u003e {\n          return e.range.getA1Notation() === smartSheet.getNamedRange('selectDepartment').getA1Notation();\n        },\n        handler: selectDepartmentChange\n      }\n    ],\n    validationRules: {\n      column: (col) =\u003e ({\n        department: SpreadsheetApp.newDataValidation()\n          .requireValueInList(['HR', 'Engineering', 'Sales'], true)\n          .setAllowInvalid(false)\n          .build()\n      }[col] || null),\n      range: (range) =\u003e ({\n        selectDepartment: SpreadsheetApp.newDataValidation()\n          .requireValueInList(['HR', 'Engineering', 'Sales'], true)\n          .setAllowInvalid(false)\n          .build()\n      }[range] || null)\n    },\n    formattingRules: {\n      column: (col) =\u003e ({\n        department: () =\u003e formatDepartment(departmentList)\n      }[col] || null)\n    }\n  }\n});\n````\n\n\n## 🚀 Usage in Trigger Functions\n\nThe framework uses standard Apps Script triggers defined in `triggers.gs`:\n\n```js\nfunction onOpen(e)\n{\n  const ss = e.source;\n\n  if (DEBUG) clearAll(ss); // optional: reset the spreadsheet in dev\n\n  const currentEditors = new Set(ss.getEditors().map(e =\u003e e.getEmail()));\n  const newEditors = editors\n    .map(e =\u003e e.email)\n    .filter(email =\u003e !currentEditors.has(email));\n\n  if (newEditors.length \u003e 0) {\n    try {\n      ss.addEditors(newEditors);\n    } catch (e) {\n      showAlert(`Failed to add editors: ${e.message}`);\n    }\n  }\n\n  if (!validateSHEETCONFIG()) {\n    showAlert('Not valid. Check the log.');\n    return;\n  }\n\n  for (const sheetName in SHEETCONFIG) {\n    const sheet = ss.getSheetByName(sheetName);\n    if (!sheet) continue;\n\n    const smartSheet = new SmartSheet(sheet);\n    setNamedRanges(smartSheet);\n    applyCalculatedColumns(smartSheet);\n    applyCalculatedNamedRanges(smartSheet);\n    applyValidationRules(smartSheet);\n    applyFormattingRules(smartSheet);\n  }\n}\n\nfunction onEdit(e)\n{\n  const sheet = e.range.getSheet();\n  const sheetName = sheet.getSheetName();\n\n  const rules = SHEETCONFIG[sheetName]?.onEditRules;\n  if (rules) {\n    const smartSheet = new SmartSheet(sheet);\n    for (const rule of rules) {\n      if (rule.condition(e, smartSheet)) {\n        rule.handler(e, smartSheet);\n        return;\n      }\n    }\n  }\n}\n```\n\nThese triggers:\n\n* Initialize the sheet when opened\n* Handle dynamic logic on user edits based on your config\n\n\n## 📚 Documentation\n\n* **`SHEETCONFIG`**\n\n  * Keys:\n\n    * `layout` → define structure: `columns`, `namedRanges`, `headerRows`\n    * `onEditRules` → define edit logic conditionally\n    * `validationRules` → dynamic input validation\n    * `formattingRules` → conditional formatting to be applied\n    * `namedFunctions` → for future support\n\n* **SmartSheet Methods**\n\n  * `getRowData(rowNumber)`\n  * `getColumnValues(colName)`\n  * `getNamedRange(name)`\n  * `getCalculatedColumns()`\n  * `getColumnValidationRule(name)`\n  * `getColumnFormattingRule(name)`\n    *(and many more)*\n\n\n## 🧠 Design Philosophy\n\n* **Declarative \u003e Imperative**: Let config describe what your sheet is and how it behaves.\n* **Reusable**: Avoid boilerplate logic across sheets.\n* **Maintainable**: Add/edit/remove sheet logic in config, not in multiple functions.\n* **Transparent**: Anyone reading the config knows the sheet structure and logic.\n\n\n## 🙌 Credits\n\nFramework by [Sunandar Gusti](https://github.com/sangnandar), based on experience building complex automation for Google Sheets using clean, modular patterns.\n\n\n## 🔭 Planned Improvements\n\nThis project is actively maintained. Here are a few next development goals:\n\n- ✅ **Improve `validateSHEETCONFIG`**\n  - Add deeper integrity checks (e.g. missing column names, missing formula for calculated columns, missing handler functions).\n  \n- ✅ **Implement Config Caching**\n  - Use `CacheService` to store a serialized version of the `SHEETCONFIG` object to improve performance, especially in large spreadsheet.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsangnandar%2Fconfig-driven-apps-script-framework","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsangnandar%2Fconfig-driven-apps-script-framework","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsangnandar%2Fconfig-driven-apps-script-framework/lists"}