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 M for Mtools/ReportHelpers (capital M)
  • ✅ Use mApp for 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:

  1. Build merjent-fulcrum-core - Compiles shared templates (runs ../merjent-fulcrum-core/build/build.ps1)
  2. Generate ReportTemplateBlocks.js - Creates template class from html-blocks.json
  3. Bundling - Uses microbundle to create 4 CommonJS modules
  4. Post-processing - Injects version/timestamp, fixes exports
  5. Minification - Creates .mmin.js versions with terser (preserves names for debugging)
  6. 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

  1. Edit package.json:

    {
      "version": "0.5.0",
      "since": "2025-11-04T14:55:18Z"
    }
    
  2. Update CHANGELOG.md in tutorials/

  3. 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:

  1. Build the module
  2. Copy dist file to a test Fulcrum app
  3. 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

  1. Create feature branch

    git checkout -b feature/my-new-feature
    
  2. Make changes in src/ for JavaScript, tools/ for Python

  3. Build and test

    build.bat
    # Test in Fulcrum
    
  4. Document with JSDoc/docstrings

  5. Update changelog in tutorials/CHANGELOG.md

  6. Commit

    git add .
    git commit -m "Add feature: description"
    
  7. Push and create pull request

    git push origin feature/my-new-feature
    

Commit Message Format

<type>: <subject>

<body>

<footer>

Types:

  • feat: New feature
  • fix: Bug fix
  • docs: Documentation changes
  • refactor: Code refactoring
  • build: Build system changes
  • chore: 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

  1. Add to appropriate class in src/Mtools/

  2. Document with JSDoc:

    /**
     * Description of what this does
     *
     * @param {type} param - Parameter description
     * @returns {type} Return value
     */
    static myNewMethod(param) {
      // Implementation
    }
    
  3. Add instance method wrapper (for non-static access):

    myNewMethod(param) {
      return Utils.myNewMethod(param);
    }
    
  4. Build and test

  5. Update changelog

Adding a New Mixin to MApp

  1. Create mixin file in src/mApp/mixins/

  2. Define mixin object:

    export const MyMixins = {
      myMethod() {
        // Use this.getVal(), this.setVal(), etc.
      }
    };
    
  3. Import in merjentApp.js:

    import { MyMixins } from './mixins/MyMixins.js';
    
  4. Assign to class:

    Object.assign(MApp.prototype, MyMixins);
    
  5. Build and test

Creating a New Python Tool

  1. Create file in tools/

  2. Extend BaseMToolTool:

    from core.base_tool import BaseMToolTool
    
    class MyTool(BaseMToolTool):
        # Implement required methods
    
  3. Implement methods:

    • setup_args() - CLI arguments
    • handle_app() - Process one app
    • print_results() - Display summary
  4. Add main block:

    if __name__ == '__main__':
        MyTool().run()
    
  5. 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

Note: Python tools have been moved to merjent-fulcrum-pyTools


Getting Help

GitHub Issues

Resources