Development Guide
Overview
This guide covers conventions, best practices, and resources for contributing to or extending Merjent Fulcrum Tools.
Naming Conventions
Variable Name Abbreviations
Merjent Fulcrum Tools uses consistent abbreviations to make code more readable without making names too long. The first portion of a variable/function name indicates the type of data it applies to.
Data Structure Prefixes
| Prefix | Meaning | Example | Description |
|---|---|---|---|
arr |
Array | arrSort() |
Functions that process arrays |
str |
String | strAfter() |
String manipulation functions |
dt |
Date | dtAddDays() |
Date object operations |
iso |
ISO Date String | iso2dt() |
ISO format date strings |
rows |
Array of Objects | rows2Sorted() |
Array of JSON objects/dictionaries |
dn |
data_name | dnSubmit |
Fulcrum field data name (single) |
dns |
data_names | dns |
Array of Fulcrum field data names |
Action Prefixes
| Prefix | Meaning | Example | Description |
|---|---|---|---|
mk/make |
Make/Create | mkMflds() |
Returns a new JS object/type |
pop |
Populate | popFields() |
Calls SETVALUE() to populate Fulcrum field |
build |
Build HTML | buildDefaultSection() |
Returns HTML string (ReportHelpers) |
rep |
Repeatable | repRequestRecords() |
Works with Fulcrum repeatables |
Other Common Abbreviations
| Abbreviation | Meaning | Context |
|---|---|---|
exc |
Exclude | Field data names to exclude from processing |
secDn |
Section Data Name | Section-level field data name |
secOpt/secOpts |
Section Options | SecOpt object for section configuration |
fld/flds |
Field(s) | Field or collection of fields |
mFlds |
Merjent Fields | MfldColl object |
Examples
// Array operations
Utils.arrSort([3, 1, 2]); // Sort array
Utils.arr2Rem([1,2,3], 2); // Remove from array (non-destructive)
Utils.arrUnique([1,2,2,3]); // Get unique values
// String operations
Utils.strAfter('prefix_value', '_'); // Get text after delimiter
Utils.strBefore('prefix_value', '_'); // Get text before delimiter
Utils.strProper('john doe'); // Proper case
// Date operations
Utils.dtAddDays(new Date(), 7); // Add days to date
Utils.dtDaysDiff(date1, date2); // Days between dates
Utils.iso2dt('2025-01-15'); // ISO string to Date object
// Rows (array of objects)
Utils.rows2Sorted(rows, 'name'); // Sort by property
Utils.rowsFind(rows, 'id', 123); // Find by property value
// Fulcrum field operations
const dns = FIELDNAMES('section'); // Get field data names
const mFlds = M.mkMflds(dns); // Make field collection
M.buildDefaultSection(secDn, mFlds); // Build HTML section
Code Style Guidelines
Module Pattern for Fulcrum Code Examples
IMPORTANT: When writing code examples for Fulcrum (in tutorials, documentation, or JSDoc comments), always use this module pattern:
//#region !START MERJENT FULCRUM TOOLS!
// ... paste dist file content ...
//#endregion !END MERJENT FULCRUM TOOLS!
// For data events (Mtools only):
const M = module.exports.Mtools
// For data events (with MerjentApp):
const M = module.exports.Mtools
const mApp = module.exports.MerjentApp
// For PDF reports:
const M = module.exports.ReportHelpers
Key Rules:
- ✅ Use
Mfor Mtools/ReportHelpers (capital M) - ✅ Use
mAppfor MerjentApp (capital A) - ❌ Never use
mtools,mapp(all lowercase) - ❌ Never use
new Mtools($form)pattern - Property access must match:
M.getVal(),mApp.dnSubmit
See CLAUDE.md for complete details and examples.
JavaScript
Use ES6+ Syntax
// GOOD: const/let
const M = module.exports.Mtools;
let count = 0;
// AVOID: var
var M = module.exports.Mtools;
Use Arrow Functions for Callbacks
// GOOD
const filtered = dns.filter(dn => dn !== 'exclude_this');
// ACCEPTABLE for multi-line
const filtered = dns.filter(dn => {
return dn !== 'exclude_this' && dn !== 'exclude_that';
});
Use Template Literals
// GOOD
const message = `Processing ${formName} (${index + 1}/${total})`;
// AVOID
const message = 'Processing ' + formName + ' (' + (index + 1) + '/' + total + ')';
Prefer Static Methods for Utils
// GOOD: Static method
class Utils {
static isBlank(value) {
return value == null || value === '';
}
}
// Access directly (works everywhere)
Utils.isBlank(someValue);
// Note: Utils is a hybrid class - methods are available both statically
// (Utils.methodName) and through Mtools instance (M.methodName)
Python
Follow PEP 8
# Good variable names
form_name = form['form']['name']
record_index = context.get('record_index', 0)
# Class names: PascalCase
class UpdateMTools(BaseMToolTool):
pass
# Function names: snake_case
def apply_search_replace(text, patterns):
pass
Use Type Hints When Appropriate
def apply_search_replace(text: str, search_replace_pairs: list, use_regex: bool = False) -> str:
"""Apply search and replace operations"""
pass
Document with Docstrings
def handle_app(self, form, template_names, context):
"""
Process one app - update data events and report templates
Args:
form: Fulcrum form object
template_names: List of report template names
context: Dictionary with search/replace configuration
Returns:
List of result dictionaries
"""
pass
Documentation Standards
JSDoc Comments
All public methods should have JSDoc comments:
/**
* Calculate days between two dates
*
* @param {Date|string} dt1 - Start date
* @param {Date|string} dt2 - End date
* @returns {number} Number of days between dates
* @throws {Error} If dates are invalid
*
* @example
* Utils.dtDaysDiff('2025-01-01', new Date()) // 14
*/
static dtDaysDiff(dt1 = new Date(), dt2 = new Date()) {
// Implementation
}
Comment Structure
/**
* Brief one-line description
*
* Detailed description if needed. Can span multiple
* paragraphs and include usage notes.
*
* @param {type} paramName - Parameter description
* @param {type} [optionalParam] - Optional parameter description
* @returns {type} Return value description
* @throws {ErrorType} When error occurs
*
* @example
* // Usage example
* methodName(arg1, arg2)
*/
Module Documentation
Each file should start with a module description:
/**
* @module Mtools/DataEventHelpers
* @description Helper methods for Fulcrum data events including
* weather API integration and fire danger calculations
*/
Build Process
Sibling Dependency: merjent-fulcrum-core
This project depends on merjent-fulcrum-core as a sibling directory:
Fulcrum/
├── merjent-fulcrum-core/ # REQUIRED - shared templates (must exist)
└── merjent-fulcrum-tools/ # This repo
The build script automatically builds merjent-fulcrum-core first. If it's missing, the build will fail.
Building JavaScript Modules
The build process (buildPkg.ps1) performs these steps:
- Build merjent-fulcrum-core - Compiles shared templates (runs
../merjent-fulcrum-core/build/build.ps1) - Generate ReportTemplateBlocks.js - Creates template class from
html-blocks.json - Bundling - Uses microbundle to create 4 CommonJS modules
- Post-processing - Injects version/timestamp, fixes exports
- Minification - Creates
.mmin.jsversions with terser (preserves names for debugging) - Documentation - Generates JSDoc HTML with foodoc template
Build Commands
# Full build (Windows)
build.bat
# Direct PowerShell
powershell -ExecutionPolicy Unrestricted -Command .\build\buildPkg.ps1
# Development mode (watch)
npm run dev
# Documentation only
npm run generate-docs
Build Configuration
microbundle (package.json)
{
"source": "./src/index_de.js",
"main": "./dist/merjent-tools-de.cjs",
"scripts": {
"build": "build.bat",
"dev": "microbundle watch"
}
}
terser (package.json)
{
"terser": {
"mangle": {
"keep_classnames": true,
"keep_fnames": true
},
"compress": false
}
}
This preserves class and function names for easier debugging in Fulcrum.
Version Management
Version Numbering
Uses semantic versioning: MAJOR.MINOR.PATCH
- MAJOR - Breaking changes (e.g., renamed exports, removed methods)
- MINOR - New features, non-breaking changes
- PATCH - Bug fixes
Updating Version
-
Edit
package.json:{ "version": "0.5.0", "since": "2025-11-04T14:55:18Z" } -
Update
CHANGELOG.mdintutorials/ -
Run build - version is automatically injected into dist files
Version Markers in Dist Files
All dist files include version footer:
//#region -- MTools Footer: v0.5.0 2025-11-04T14:55:18Z --
// Module code and exports
//#endregion
These markers are used by updateMtools.py for targeted updates.
Testing
Testing JavaScript Modules
No automated tests currently exist. Manual testing process:
- Build the module
- Copy dist file to a test Fulcrum app
- Test in Fulcrum environment with:
- Different field types
- Edge cases (null values, empty arrays, etc.)
- Different user roles (for MApp)
- Mobile and desktop views
Testing Python Tools
Test tools on a single app first:
def handle_app(self, form, template_names, context):
form_name = form['form']['name']
# TEST MODE: Only process one app
if form_name != 'Test App Name':
return []
# Your logic here
Then test on all apps after validation.
Contributing
Workflow
-
Create feature branch
git checkout -b feature/my-new-feature -
Make changes in
src/for JavaScript,tools/for Python -
Build and test
build.bat # Test in Fulcrum -
Document with JSDoc/docstrings
-
Update changelog in
tutorials/CHANGELOG.md -
Commit
git add . git commit -m "Add feature: description" -
Push and create pull request
git push origin feature/my-new-feature
Commit Message Format
<type>: <subject>
<body>
<footer>
Types:
feat:New featurefix:Bug fixdocs:Documentation changesrefactor:Code refactoringbuild:Build system changeschore:Maintenance tasks
Examples:
feat: Add search/replace to updateMtools.py
- Added command-line arguments for search/replace
- Integrated into update workflow
- Updated documentation
Closes #42
File Organization
JavaScript Source Files
src/
├── Mtools/ # Helper classes
│ ├── Utils.js # Base utilities
│ ├── FulcrumHelpers.js # Fulcrum utilities
│ ├── RequestHelpers.js # API requests
│ ├── DataEventHelpers.js # Data event specific
│ ├── FieldHelpers.js # Field manipulation
│ ├── ReportHelpers.js # PDF reports
│ └── ReportTemplateBlocks.js # Auto-generated from merjent-fulcrum-core
│
├── mApp/ # MerjentApp module
│ ├── merjentApp.js # Main class
│ ├── mixins/ # Composable behaviors
│ │ ├── PermissionMixins.js
│ │ ├── StatusMixins.js
│ │ ├── PhotoMixins.js
│ │ └── LockingMixins.js
│ └── templates/ # Pre-configured templates
│ └── DefaultTemplate.js
│
├── MtoolsDataEvents.js # Entry: data events
├── MtoolsMerjentApp.js # Entry: data events + Mapp
├── MtoolsReports.js # Entry: reports
└── MtoolsUtils.js # Entry: standalone utils
Python Tool Files
tools/
├── core/ # Shared infrastructure
│ ├── fulcrum_client.py # API client
│ ├── app_looper.py # Looping logic
│ └── base_tool.py # Base class
│
├── helpers.py # General helpers
├── fulcrum_helpers.py # Fulcrum-specific helpers
│
├── updateMtools.py # Update deployment
├── findMtoolFunctions.py # Search/replace
└── EXAMPLE_*.py # Example tools
Common Development Tasks
Adding a New Helper Method
-
Add to appropriate class in
src/Mtools/ -
Document with JSDoc:
/** * Description of what this does * * @param {type} param - Parameter description * @returns {type} Return value */ static myNewMethod(param) { // Implementation } -
Add instance method wrapper (for non-static access):
myNewMethod(param) { return Utils.myNewMethod(param); } -
Build and test
-
Update changelog
Adding a New Mixin to MApp
-
Create mixin file in
src/mApp/mixins/ -
Define mixin object:
export const MyMixins = { myMethod() { // Use this.getVal(), this.setVal(), etc. } }; -
Import in
merjentApp.js:import { MyMixins } from './mixins/MyMixins.js'; -
Assign to class:
Object.assign(MApp.prototype, MyMixins); -
Build and test
Creating a New Python Tool
-
Create file in
tools/ -
Extend BaseMToolTool:
from core.base_tool import BaseMToolTool class MyTool(BaseMToolTool): # Implement required methods -
Implement methods:
setup_args()- CLI argumentshandle_app()- Process one appprint_results()- Display summary
-
Add main block:
if __name__ == '__main__': MyTool().run() -
Test on single app first
Troubleshooting Development Issues
Build fails with module errors
Cause: Missing dependencies
Solution:
npm install
Documentation not generating
Cause: JSDoc syntax errors
Solution:
npm run generate-docs
# Check console for errors
Python tool can't find modules
Cause: Not running from tools/ directory
Solution:
cd tools
python updateMtools.py
Changes not appearing in Fulcrum
Cause: Forgot to rebuild
Solution:
build.bat
# Then copy NEW dist file to Fulcrum
Next Steps
- Review specific tutorials for detailed usage:
Note: Python tools have been moved to merjent-fulcrum-pyTools
-
Check the API documentation for complete method reference
-
Browse the source code to see implementation details
-
See General Development Resources for Fulcrum documentation and standalone Utils usage
Getting Help
GitHub Issues
Resources
- Search existing issues
- Check the documentation
- Review tutorials