|
| 1 | +# CLAUDE.md - File-Based Module Development Guide |
| 2 | + |
| 3 | +This file provides guidance for creating and developing LabKey file-based modules. |
| 4 | + |
| 5 | +## What is a File-Based Module? |
| 6 | + |
| 7 | +A file-based module is a LabKey module that doesn't contain any Java code. It enables custom development without compiling, letting you directly deploy and test module resources, often without restarting the server. File-based modules support: |
| 8 | + |
| 9 | +- SQL queries and views |
| 10 | +- Reports (R, JavaScript, HTML) |
| 11 | +- Custom data views |
| 12 | +- Web parts and HTML/JavaScript client-side applications |
| 13 | +- Assay definitions |
| 14 | +- ETL configurations |
| 15 | +- Pipeline definitions |
| 16 | + |
| 17 | +## Module Directory Structure |
| 18 | + |
| 19 | +### Development/Source Layout |
| 20 | +``` |
| 21 | +myModule/ |
| 22 | +├── module.properties # Module configuration (REQUIRED) |
| 23 | +├── README.md # Module documentation |
| 24 | +└── resources/ # All module resources go here |
| 25 | + ├── queries/ # SQL queries and query metadata |
| 26 | + │ └── [schema_name]/ # Organize by schema |
| 27 | + │ ├── [query_name].sql |
| 28 | + │ ├── [query_name].query.xml |
| 29 | + │ └── [query_name]/ # Query-specific views |
| 30 | + │ └── [view_name].html |
| 31 | + ├── reports/ # Report definitions |
| 32 | + │ └── schemas/ |
| 33 | + │ └── [schema_name]/ |
| 34 | + │ └── [query_name]/ |
| 35 | + │ ├── [report_name].r |
| 36 | + │ ├── [report_name].rhtml |
| 37 | + │ └── [report_name].report.xml |
| 38 | + ├── views/ # Custom views and web parts |
| 39 | + │ ├── [view_name].html |
| 40 | + │ └── [view_name].webpart.xml |
| 41 | + ├── schemas/ # Database schema definitions |
| 42 | + │ └── dbscripts/ |
| 43 | + │ ├── postgresql/ |
| 44 | + │ └── sqlserver/ |
| 45 | + ├── web/ # JavaScript, CSS, images |
| 46 | + │ └── [moduleName]/ |
| 47 | + │ ├── [moduleName].js |
| 48 | + │ └── [moduleName].css |
| 49 | + ├── assay/ # Assay type definitions |
| 50 | + ├── etls/ # ETL configurations |
| 51 | + ├── folderTypes/ # Custom folder type definitions |
| 52 | + └── pipeline/ # Pipeline task definitions |
| 53 | +``` |
| 54 | + |
| 55 | +### Deployed Layout |
| 56 | +When deployed, the structure changes slightly: |
| 57 | +- `resources/` directory contents move to root level |
| 58 | +- `module.properties` becomes `config/module.xml` |
| 59 | +- Compiled code (if any) goes to `lib/` |
| 60 | + |
| 61 | +## module.properties File |
| 62 | + |
| 63 | +This is the **required** configuration file for your module. Place it in the module root. |
| 64 | + |
| 65 | +### Required Properties |
| 66 | +```properties |
| 67 | +ModuleClass: org.labkey.api.module.SimpleModule |
| 68 | +Name: myModule |
| 69 | +``` |
| 70 | + |
| 71 | +### Recommended Properties |
| 72 | +```properties |
| 73 | +ModuleClass: org.labkey.api.module.SimpleModule |
| 74 | +Name: myModule |
| 75 | +Label: My Custom Module |
| 76 | +Description: A file-based module for custom queries, reports, and views.\ |
| 77 | + Multi-line descriptions can span multiple lines using backslash continuation. |
| 78 | +Version: 1.0.0 |
| 79 | +Author: Your Name <your.email@example.com> |
| 80 | +Organization: Your Organization |
| 81 | +OrganizationURL: https://example.com |
| 82 | +License: Apache 2.0 |
| 83 | +LicenseURL: https://www.apache.org/licenses/LICENSE-2.0 |
| 84 | +Maintainer: Your Name <your.email@example.com> |
| 85 | +RequiredServerVersion: 23.11 |
| 86 | +``` |
| 87 | +`Name` should usually be the same as the directory name, especially for file-based modules. |
| 88 | + |
| 89 | +### Additional Properties |
| 90 | +- **SchemaVersion**: Version number for SQL schema upgrade scripts (e.g., `1.00`) |
| 91 | +- **ManageVersion**: Boolean (true/false) for schema version management |
| 92 | +- **BuildType**: "Development" or "Production" |
| 93 | +- **SupportedDatabases**: "pgsql" or "mssql" (comma-separated) |
| 94 | +- **URL**: Homepage URL for the module |
| 95 | + |
| 96 | +### Auto-Generated Properties (Don't Set) |
| 97 | +These are set during build: BuildNumber, BuildOS, BuildPath, BuildTime, BuildUser, EnlistmentId, ResourcePath, SourcePath, VcsRevision, VcsURL |
| 98 | + |
| 99 | +## Creating Web Parts |
| 100 | + |
| 101 | +Web parts are HTML views that can be added to LabKey pages. |
| 102 | + |
| 103 | +### Basic Web Part Structure |
| 104 | + |
| 105 | +**File**: `resources/views/myWebPart.html` |
| 106 | +```html |
| 107 | +<div class="labkey-module-content"> |
| 108 | + <h2>My Web Part</h2> |
| 109 | + <p>Content goes here</p> |
| 110 | +</div> |
| 111 | + |
| 112 | +<script type="text/javascript" nonce="<%=scriptNonce%>"> |
| 113 | +// JavaScript code here |
| 114 | +// IMPORTANT: Always include nonce="<%=scriptNonce%>" for CSP compliance |
| 115 | +</script> |
| 116 | +``` |
| 117 | + |
| 118 | +**Configuration**: `resources/views/myWebPart.webpart.xml` |
| 119 | +```xml |
| 120 | +<?xml version="1.0" encoding="UTF-8"?> |
| 121 | +<webpart xmlns="http://labkey.org/data/xml/webpart"> |
| 122 | + <name>My Web Part</name> |
| 123 | + <description>Description of what this web part does</description> |
| 124 | + <location>body</location> |
| 125 | +</webpart> |
| 126 | +``` |
| 127 | + |
| 128 | +### Important: Content Security Policy (CSP) |
| 129 | + |
| 130 | +LabKey enforces CSP, so **all inline scripts must include the nonce attribute**: |
| 131 | +```html |
| 132 | +<script type="text/javascript" nonce="<%=scriptNonce%>"> |
| 133 | + // Your code here |
| 134 | +</script> |
| 135 | +``` |
| 136 | + |
| 137 | +Without the nonce, your inline scripts will be blocked by the browser. |
| 138 | + |
| 139 | +### Template Variables |
| 140 | + |
| 141 | +LabKey automatically substitutes the following variables in HTML view files (use `<%=variableName%>` syntax): |
| 142 | + |
| 143 | +- **scriptNonce**: CSP nonce for inline scripts. **Required for all `<script>` tags** to comply with Content Security Policy. |
| 144 | + ```html |
| 145 | + <script type="text/javascript" nonce="<%=scriptNonce%>"> |
| 146 | + ``` |
| 147 | +
|
| 148 | +- **contextPath**: The web application's context path (e.g., `/labkey`). Use for building URLs to server resources. |
| 149 | + ```javascript |
| 150 | + var url = '<%=contextPath%>' + '/someResource.js'; |
| 151 | + ``` |
| 152 | +
|
| 153 | +- **containerPath**: The current container's path (e.g., `/MyProject/MyFolder`). Use for building container-specific URLs. |
| 154 | + ```javascript |
| 155 | + var containerUrl = '<%=contextPath%>' + '<%=containerPath%>'; |
| 156 | + ``` |
| 157 | +
|
| 158 | +- **wrapperDivId**: A unique ID for the wrapper div containing the view (format: `ModuleHtmlView_<uniqueID>`). Useful for scoping JavaScript or CSS to a specific view instance. |
| 159 | + ```javascript |
| 160 | + var wrapper = document.getElementById('<%=wrapperDivId%>'); |
| 161 | + ``` |
| 162 | +
|
| 163 | +- **id**: The web part's row ID (or `-1` if not rendered as a web part). Use to identify specific web part instances. |
| 164 | + ```javascript |
| 165 | + var webPartId = <%=id%>; // Note: no quotes, this is a number |
| 166 | + ``` |
| 167 | +
|
| 168 | +- **webpartContext**: A JSON string containing configuration for the web part, including: |
| 169 | + - `wrapperDivId`: The wrapper div ID |
| 170 | + - `id`: The web part row ID |
| 171 | + - `properties`: An object containing the web part's custom properties (from webpart.xml configuration) |
| 172 | + - Any additional properties set on the web part |
| 173 | +
|
| 174 | + ```javascript |
| 175 | + var config = JSON.parse('<%=webpartContext%>'); |
| 176 | + console.log('Web part ID:', config.id); |
| 177 | + console.log('Properties:', config.properties); |
| 178 | + ``` |
| 179 | +
|
| 180 | +**Note**: The entire view is automatically wrapped in a div with the `wrapperDivId`, so you don't need to create it yourself. |
| 181 | +
|
| 182 | +## Using LabKey JavaScript APIs |
| 183 | +
|
| 184 | +LabKey provides a comprehensive JavaScript API for interacting with the server. |
| 185 | +
|
| 186 | +**Complete API Reference**: For the full JavaDoc-style API documentation, see https://labkey.github.io/labkey-api-js/ |
| 187 | +
|
| 188 | +### Accessing User and Container Information |
| 189 | +
|
| 190 | +User and container information is automatically rendered into the page: |
| 191 | +
|
| 192 | +```javascript |
| 193 | +// Access current user information |
| 194 | +console.log('User ID:', LABKEY.user.id); |
| 195 | +console.log('Display Name:', LABKEY.user.displayName); |
| 196 | +console.log('Email:', LABKEY.user.email); |
| 197 | +console.log('Is Admin:', LABKEY.user.isAdmin); |
| 198 | +console.log('Is Guest:', LABKEY.user.isGuest); |
| 199 | +
|
| 200 | +// Access current container/folder information |
| 201 | +console.log('Container ID:', LABKEY.container.id); |
| 202 | +console.log('Container Path:', LABKEY.container.path); |
| 203 | +console.log('Container Name:', LABKEY.container.name); |
| 204 | +``` |
| 205 | +
|
| 206 | +### Common LABKEY JavaScript Objects |
| 207 | +
|
| 208 | +- **LABKEY.ActionURL**: Build URLs to LabKey controllers and actions |
| 209 | +- **LABKEY.Ajax**: Make AJAX requests to LabKey APIs |
| 210 | +- **LABKEY.Query**: Execute SQL queries and retrieve data |
| 211 | +- **LABKEY.Security**: Access user permissions and security info |
| 212 | +- **LABKEY.Utils**: Utility functions for common tasks |
| 213 | +
|
| 214 | +### Example: Querying Data |
| 215 | +
|
| 216 | +```javascript |
| 217 | +LABKEY.Query.selectRows({ |
| 218 | + schemaName: 'lists', |
| 219 | + queryName: 'MyList', |
| 220 | + success: function(data) { |
| 221 | + console.log('Rows:', data.rows); |
| 222 | + }, |
| 223 | + failure: function(error) { |
| 224 | + console.error('Query failed:', error); |
| 225 | + } |
| 226 | +}); |
| 227 | +``` |
| 228 | +
|
| 229 | +## Creating SQL Queries |
| 230 | +
|
| 231 | +Place SQL query files in `resources/queries/[schema_name]/`. |
| 232 | +
|
| 233 | +### Basic Query |
| 234 | +
|
| 235 | +**File**: `resources/queries/core/Users.sql` |
| 236 | +```sql |
| 237 | +SELECT |
| 238 | + UserId, |
| 239 | + DisplayName, |
| 240 | + Email, |
| 241 | + Active |
| 242 | +FROM core.Users |
| 243 | +WHERE Active = TRUE |
| 244 | +ORDER BY DisplayName |
| 245 | +``` |
| 246 | +
|
| 247 | +### Query Metadata |
| 248 | +
|
| 249 | +**File**: `resources/queries/core/Users.query.xml` |
| 250 | +```xml |
| 251 | +<?xml version="1.0" encoding="UTF-8"?> |
| 252 | +<query xmlns="http://labkey.org/data/xml/query"> |
| 253 | + <metadata> |
| 254 | + <columns> |
| 255 | + <column columnName="UserId"> |
| 256 | + <description>Unique user identifier</description> |
| 257 | + </column> |
| 258 | + <column columnName="DisplayName"> |
| 259 | + <description>User's display name</description> |
| 260 | + </column> |
| 261 | + </columns> |
| 262 | + </metadata> |
| 263 | +</query> |
| 264 | +``` |
| 265 | +
|
| 266 | +## Deployment and Testing |
| 267 | +
|
| 268 | +### Location |
| 269 | +
|
| 270 | +File-based modules are deployed to: |
| 271 | +``` |
| 272 | +<LABKEY_ROOT>/build/deploy/externalModules/[moduleName]/ |
| 273 | +``` |
| 274 | +
|
| 275 | +**IMPORTANT**: Back up your module before running `gradlew cleanBuild` as the build directory may be deleted. |
| 276 | +
|
| 277 | +### Enabling the Module |
| 278 | +
|
| 279 | +1. Navigate to your target folder in LabKey |
| 280 | +2. Go to **Folder > Management** |
| 281 | +3. Click the **Folder Type** tab |
| 282 | +4. Check your module's checkbox under "Modules" |
| 283 | +5. Click **Update Folder** |
| 284 | +
|
| 285 | +### Hot Deployment |
| 286 | +
|
| 287 | +Most changes to file-based module resources don't require a server restart: |
| 288 | +- ✅ HTML/JavaScript changes in views: No restart needed |
| 289 | +- ✅ Query definition changes: No restart needed |
| 290 | +- ✅ Report updates: No restart needed |
| 291 | +- ⚠️ module.properties changes: Restart required |
| 292 | +- ⚠️ Adding new resource types: May require restart |
| 293 | +
|
| 294 | +Simply refresh your browser to see changes. |
| 295 | +
|
| 296 | +## Best Practices |
| 297 | +
|
| 298 | +### Security |
| 299 | +- Always use `nonce="<%=scriptNonce%>"` for inline scripts |
| 300 | +- Escape user-supplied data before rendering in HTML |
| 301 | +- Use LABKEY.Utils.encodeHtml() to prevent XSS |
| 302 | +- Never expose sensitive data in client-side code |
| 303 | +
|
| 304 | +### Code Organization |
| 305 | +- Keep JavaScript in separate files under `resources/web/[moduleName]/` |
| 306 | +- Use meaningful names for queries, views, and reports |
| 307 | +- Group related queries by schema |
| 308 | +- Document your queries with .query.xml metadata files |
| 309 | +
|
| 310 | +### Performance |
| 311 | +- Use query metadata to hide unnecessary columns |
| 312 | +- Limit result sets with WHERE clauses |
| 313 | +- Create database indexes for frequently queried columns |
| 314 | +- Cache expensive queries when possible |
| 315 | +
|
| 316 | +### Maintenance |
| 317 | +- Version your module.properties appropriately |
| 318 | +- Document breaking changes in your README |
| 319 | +- Test on both PostgreSQL and SQL Server if supporting both |
| 320 | +- Keep module.properties up to date with RequiredServerVersion |
| 321 | +
|
| 322 | +## Common Patterns |
| 323 | +
|
| 324 | +### Creating a Dashboard Web Part |
| 325 | +
|
| 326 | +```html |
| 327 | +<div class="labkey-module-content"> |
| 328 | + <h2>My Dashboard</h2> |
| 329 | + <div id="dashboard-content">Loading...</div> |
| 330 | +</div> |
| 331 | +
|
| 332 | +<script type="text/javascript" nonce="<%=scriptNonce%>"> |
| 333 | +(function() { |
| 334 | + // Parse web part configuration |
| 335 | + var config = JSON.parse('<%=webpartContext%>'); |
| 336 | + console.log('Web part ID:', config.id); |
| 337 | + console.log('Custom properties:', config.properties); |
| 338 | +
|
| 339 | + // Query data in the current container |
| 340 | + LABKEY.Query.selectRows({ |
| 341 | + containerPath: '<%=containerPath%>', |
| 342 | + schemaName: 'core', |
| 343 | + queryName: 'Users', |
| 344 | + success: function(data) { |
| 345 | + var html = '<ul>'; |
| 346 | + data.rows.forEach(function(row) { |
| 347 | + html += '<li>' + LABKEY.Utils.encodeHtml(row.DisplayName) + '</li>'; |
| 348 | + }); |
| 349 | + html += '</ul>'; |
| 350 | +
|
| 351 | + // Use wrapperDivId to scope to this specific view instance |
| 352 | + var wrapper = document.getElementById('<%=wrapperDivId%>'); |
| 353 | + var contentDiv = wrapper.querySelector('#dashboard-content'); |
| 354 | + contentDiv.innerHTML = html; |
| 355 | + } |
| 356 | + }); |
| 357 | +})(); |
| 358 | +</script> |
| 359 | +``` |
| 360 | +
|
| 361 | +### Adding Custom Button Actions |
| 362 | +
|
| 363 | +```html |
| 364 | +<div class="labkey-module-content"> |
| 365 | + <button id="myButton" class="labkey-button">Click Me</button> |
| 366 | +</div> |
| 367 | +
|
| 368 | +<script type="text/javascript" nonce="<%=scriptNonce%>"> |
| 369 | +(function() { |
| 370 | + document.getElementById('myButton').addEventListener('click', function() { |
| 371 | + LABKEY.Utils.alert('Button Clicked', 'You clicked the button!'); |
| 372 | + }); |
| 373 | +})(); |
| 374 | +</script> |
| 375 | +``` |
| 376 | +
|
| 377 | +## Documentation Resources |
| 378 | +
|
| 379 | +For more information, see: |
| 380 | +- Simple Modules Overview: https://www.labkey.org/Documentation/wiki-page.view?name=simpleModules |
| 381 | +- File-Based Module Tutorial: https://www.labkey.org/Documentation/wiki-page.view?name=moduleqvr |
| 382 | +- JavaScript API Documentation: https://labkey.github.io/labkey-api-js/ |
| 383 | +- Module Directory Structures: https://www.labkey.org/Documentation/wiki-page.view?name=moduleDirectoryStructures |
| 384 | +- Query Development: https://www.labkey.org/Documentation/wiki-page.view?name=addSQLQuery |
| 385 | +
|
| 386 | +## Quick Start Checklist |
| 387 | +
|
| 388 | +- [ ] Create module directory in `build/deploy/externalModules/` |
| 389 | +- [ ] Add `module.properties` with required fields |
| 390 | +- [ ] Create `resources/` directory structure |
| 391 | +- [ ] Add at least one view or query |
| 392 | +- [ ] Enable module in a test folder |
| 393 | +- [ ] Test functionality in browser |
| 394 | +- [ ] Document usage in README.md |
| 395 | +- [ ] Back up module outside build directory |
0 commit comments