Creating Forms in Nodlin
Overview
Forms in Nodlin provide an interactive interface for users to input and view data associated with nodes. This guide covers the webform module and how to create rich, functional forms.
For complete API details, see the Starlark Extensions Reference.
Basic Form Structure
Every form starts with creating a new webform object and ends by displaying it to the user.
# Create a new form
form = webform.new()
# Add form controls
form.addTextInput("name", webform.opts(label="Name", required=True))
# Form is automatically displayed to userForm Organization
Groups and Columns
Groups allow you to organize form controls. Columns within groups enable multi-column layouts (12-column grid system).
form = webform.new()
# Create a group for related fields
detailsGroup = form.addGroup("_detailsGroup")
# Add two columns (6 columns each = 50% width)
leftCol = detailsGroup.addColumn("_leftCol", 6)
rightCol = detailsGroup.addColumn("_rightCol", 6)
# Add fields to each column
leftCol.addTextInput("firstName", webform.opts(label="First Name"))
rightCol.addTextInput("lastName", webform.opts(label="Last Name"))Tabs
Tabs organize content into separate pages within a form.
form = webform.new()
# Create tabs
detailsTab = form.addTab("_detailsTab", "Details")
settingsTab = form.addTab("_settingsTab", "Settings")
# Create groups for each tab
detailsGroup = form.addGroup("_detailsGroup")
settingsGroup = form.addGroup("_settingsGroup")
# Add groups to tabs
detailsTab.addElement("_detailsGroup")
settingsTab.addElement("_settingsGroup")
# Add controls to groups
detailsGroup.addTextInput("name", webform.opts(label="Name"))
settingsGroup.addNumberInput("priority", webform.opts(label="Priority"))Example from TOGAF Architecture Project:
# Details tab
detailsTab = form.addTab("_detailsTab", "Details")
detailsGroup = form.addGroup("_detailsGroup")
detailsTab.addElement("_detailsGroup")
detailsGroup.addTextInput("name", webform.opts(label="Project Name", required=True))
detailsGroup.addEditor("description", webform.opts(label="Description"))
# Actions tab
actionsTab = form.addTab("_actionsTab", "Actions")
actionsGroup = form.addGroup("_actionsGroup")
actionsTab.addElement("_actionsGroup")
actionsGroup.addStatic("_actionsHeader", "h4", "Create Architecture Artifacts")
actionsGroup.addButton("addStakeholder", "Add Stakeholder",
webform.buttonOpts(action="addStakeholder", buttonClass="bg-blue-500"))Form Controls
Text Input
Basic text input fields.
# Simple text input
form.addTextInput("email", webform.opts(label="Email"))
# With additional options
textOpts = webform.textInputOpts(inputType="email")
form.addTextInput("email", webform.opts(label="Email", required=True), textOpts)Number Input
Numeric input fields.
form.addNumberInput("age", webform.opts(label="Age (years)", info="Enter your age"))Editor
Rich text editor for longer content.
form.addEditor("description",
webform.opts(label="Description"),
webform.editorOpts(expandable=True))Select (Dropdown)
Dropdown selection lists.
# Simple list
statusOptions = ["Open", "In Progress", "Complete"]
form.addSelect("status", statusOptions, webform.opts(label="Status"))
# From TOGAF example
statusOptions = ["Initiation", "Planning", "In Progress", "Review", "Complete", "On Hold"]
detailsGroup.addSelect("status", statusOptions, webform.opts(label="Status"))Radio Group
Radio button groups for mutually exclusive options.
# Tuple format: (value, label)
severityOptions = [
("SEV1", "Critical"),
("SEV2", "Major"),
("SEV3", "Moderate"),
("SEV4", "Minor"),
("SEV5", "Informational")
]
form.addRadioGroup("severity", severityOptions,
webform.radioGroupOpts(view=webform.TABS),
webform.opts(label="Severity"))Example from Incident Management:
statusOptions = [
("open", "Open"),
("in progress", "In progress"),
("resolved", "Resolved"),
("closed", "Closed")
]
form.addRadioGroup("status", statusOptions,
webform.radioGroupOpts(view=webform.TABS),
webform.opts(label="Status", info="Incident status", disabled=True))Slider
Slider control for numeric ranges.
scoreInputOpts = webform.opts(label="Significance",
info="Score from 0 (no impact) to 100 (high impact)")
form.addSlider("weight",
webform.sliderOpts(minValue=0, maxValue=100, step=1,
tooltips=True, tooltipPosition=webform.BOTTOM),
scoreInputOpts)Buttons
Buttons trigger actions or submit forms.
# Submit button
form.addButton("_submit", "Save",
webform.buttonOpts(submits=True),
webform.opts(align="right"))
# Action button
form.addButton("addTask", "Add Task",
webform.buttonOpts(action="addTask", buttonClass="bg-green-500"),
webform.opts(align="left"))Button Layout Example:
buttonGroup = form.addGroup("_buttonGroup")
btnLeftCol = buttonGroup.addColumn("_btnLCol", 3)
btnCenterCol = buttonGroup.addColumn("_btnCCol", 6)
btnRightCol = buttonGroup.addColumn("_btnRCol", 3)
btnLeftCol.addButton("addImpact", "+Impact",
webform.buttonOpts(action="addImpact", buttonClass="bg-green-accent-3"),
webform.opts(align="left"))
btnRightCol.addButton("_submit", "Save",
webform.buttonOpts(submits=True),
webform.opts(align="right"))Date and Time Inputs
Date and datetime inputs with flexible formatting.
# Simple date input
form.addDateInput("startDate", webform.opts(label="Start Date"))
# Date with time
dateOpts = webform.dateInputOpts(time=True)
form.addDateInput("reportedAt",
webform.opts(label="Reported at", info="Time incident was first reported"),
dateOpts)
# With format and range
dateOpts = webform.dateInputOpts(
time=True,
min='2025-07-01 00:00',
displayFormat='YYYY-MM-DD'
)
form.addDateInput("eventDate", webform.opts(label="Event Date"), dateOpts)Date Format Tokens
Formatting follows moment.js tokens:
YYYY- 4 digit yearMM- Month numberDD- Day of monthHH- Hour (24h)mm- Minutess- Second
UTC format: YYYY-MM-DDTHH:mm:ss[Z] (e.g., “2025-07-16T13:45:00Z”)
Example from Incident Management:
# Date inputs in a row
timeGroup = form.addGroup("_timeGroup")
timeLeftCol = timeGroup.addColumn("_trcol", 4)
timeMiddleCol = timeGroup.addColumn("_tmcol", 4)
timeRightCol = timeGroup.addColumn("_tlcol", 4)
myDateOpts = webform.dateInputOpts(time=True)
timeLeftCol.addDateInput("startTime",
webform.opts(label="Start time", info="Time incident was declared"),
myDateOpts)
timeMiddleCol.addDateInput("endTime",
webform.opts(label="End time", info="Time incident was resolved"),
myDateOpts)
timeRightCol.addDateInput("closedAt",
webform.opts(label="Closed at", info="Time incident was closed"),
myDateOpts)User Input Control
Special control for selecting Nodlin users and groups.
userOpts = webform.nodlinUserOpts(
help="Select responsible users or groups",
singleContactOnly=False
)
form.addUserInput('responsible', 'Responsible', userOpts)
# Access selected values
if 'responsible' in value:
for u in value.responsible['users']:
print("USER %s in domain %s" % (u['userid'], u['domain']))
for g in value.responsible['groups']:
print("GROUP %s in domain %s" % (g['group'], g['domain']))The result is a dictionary with two lists:
users- List of{userid, domain}dictionariesgroups- List of{group, domain}dictionaries
Lists and Repeating Fields
Create lists of repeating items where users can add/remove entries.
# Create a list container
myList = form.addList("userWeightings")
# Define the object structure for each list item
myObj = myList.setObject()
# Add fields to the repeating object
myObj.addTextInput("label", webform.opts(label="Label"))
myObj.addSlider("weight",
webform.sliderOpts(minValue=0, maxValue=100, step=1),
webform.opts(label="Weight"))
myObj.addHidden("nodeID") # Hidden field for data storageExample from Cause/Effect:
# Display causes with significance sliders
if hasattr(node.related, "hasCause"):
myListOf = form.addList("userWeightings")
myObj = myListOf.setObject()
# Cause name (read-only)
myObj.addTextInput("label",
webform.opts(label="Cause", info="Existing cause for this effect"),
webform.opts(disabled=True))
# Significance slider
myObj.addSlider("weight",
webform.sliderOpts(minValue=0, maxValue=100, step=1,
tooltips=True, tooltipPosition=webform.BOTTOM),
webform.opts(label="Significance"))
# Store node ID
myObj.addHidden("nodeID")Static Content
Static Text and HTML
Display read-only content in various HTML elements.
# Headers
form.addStatic("_header", "h2", "Project Details")
form.addStatic("_subheader", "h4", "Configuration Settings")
# Paragraphs
form.addStatic("_info", "p", "Please enter your project information below.")
# Custom HTML
warningHTML = """
<div style='color: orange; padding: 10px; background: #fff3cd;'>
<strong>Warning:</strong> This action cannot be undone.
</div>
"""
form.addStatic("_warning", "div", warningHTML)Example from Incident Management:
# Header with columns
titleGroup = form.addGroup("_titleGroup")
titleLeftCol = titleGroup.addColumn("_hrcol", 8, webform.opts(align="left"))
titleRightCol = titleGroup.addColumn("_hlcol", 4)
titleLeftCol.addStatic("_Header", "h2", "Incident")
titleLeftCol.addStatic("_SubHeader", "h4", value.title)
# Warning message
if showImpactWarning:
warningHTML = """
<p style="color: orange;">Higher severity impacts exist.
Review incident classification: %s</p>
""" % higherClassifiedImpacts
form.addStatic("_warning", "div", warningHTML)Images
Display images in forms using SVG or PNG.
# SVG image
svgIcon = r'<svg width="70" height="70">...</svg>'
form.addImage("_icon", webform.SVG + svgIcon,
webform.opts(align="right"))
# PNG image (base64 encoded)
pngData = "data:image/png;base64,iVBORw0KG..."
form.addImage("_logo", webform.PNG + pngData)Image URI prefixes:
webform.SVG- For SVG imageswebform.PNG- For base64-encoded PNG images
Markdown to HTML
Convert markdown to HTML for display in forms.
myMarkdown = """
# Project Overview
This is the **main project** with the following goals:
- Increase efficiency
- Reduce costs
- Improve quality
## Next Steps
Review the requirements and proceed with implementation.
"""
myHTML = form.markdownToHTML(myMarkdown)
form.addStatic("_overview", "div", myHTML)Dividers and Spacing
Add visual separation between form sections.
# Horizontal divider
form.addDivider("_div1")
# Or using static HR
form.addStatic("_hr", "hr", "")
# Vertical spacing (number = units)
form.addSpace(2) # Add 2 units of vertical spaceComplete Form Examples
Simple Project Form
form = webform.new()
# Header
form.addStatic("_header", "h2", "New Project")
# Basic fields
form.addTextInput("name", webform.opts(label="Project Name", required=True))
form.addEditor("description", webform.opts(label="Description"))
statusOptions = ["Planning", "Active", "On Hold", "Complete"]
form.addSelect("status", statusOptions, webform.opts(label="Status"))
# Dates
dateOpts = webform.dateInputOpts(time=False)
form.addDateInput("startDate", webform.opts(label="Start Date"), dateOpts)
form.addDateInput("endDate", webform.opts(label="Target End Date"), dateOpts)
# Submit
form.addButton("_submit", "Save Project",
webform.buttonOpts(submits=True),
webform.opts(align="right"))Multi-Tab Form with Dashboard
This example from TOGAF shows a sophisticated form with tabs and dynamic content.
form = webform.new()
# Details tab
detailsTab = form.addTab("_detailsTab", "Details")
detailsGroup = form.addGroup("_detailsGroup")
detailsTab.addElement("_detailsGroup")
# Project details
detailsGroup.addTextInput("name", webform.opts(label="Project Name", required=True))
detailsGroup.addSpace(1)
detailsGroup.addTextInput("organization", webform.opts(label="Organization"))
detailsGroup.addSpace(1)
statusOptions = ["Initiation", "Planning", "In Progress", "Review", "Complete"]
detailsGroup.addSelect("status", statusOptions, webform.opts(label="Status"))
detailsGroup.addSpace(1)
# Dashboard with HTML
dashboardHTML = """
<div style='padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px; margin: 16px 0;'>
<div style='display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px;'>
<div style='background: white; padding: 16px; border-radius: 6px; text-align: center;'>
<div style='font-size: 32px; font-weight: bold; color: #667eea;'>%s</div>
<div style='color: #666; font-size: 12px; margin-top: 4px;'>Stakeholders</div>
</div>
<!-- More cards... -->
</div>
</div>
""" % stakeholderCount
detailsGroup.addStatic("_dashboard", "div", dashboardHTML)
# Save button
detailsGroup.addButton("_save", "Save Project",
webform.buttonOpts(submits=True),
webform.opts(align="right"))
# Actions tab
actionsTab = form.addTab("_actionsTab", "Actions")
actionsGroup = form.addGroup("_actionsGroup")
actionsTab.addElement("_actionsGroup")
actionsGroup.addStatic("_actionsHeader", "h4", "Create Architecture Artifacts")
actionsGroup.addButton("addStakeholder", "Add Stakeholder",
webform.buttonOpts(action="addStakeholder", buttonClass="bg-blue-500"))
actionsGroup.addButton("addRequirement", "Add Requirement",
webform.buttonOpts(action="addRequirement", buttonClass="bg-indigo-500"))Form Options
Common Options (webform.opts)
These options apply to most form controls:
label- Display label for the fieldinfo- Help text/tooltiprequired- Mark field as requireddisabled- Disable the field (read-only)align- Alignment: “left”, “center”, “right”
opts = webform.opts(
label="Project Name",
info="Enter a unique project identifier",
required=True,
align="left"
)
form.addTextInput("name", opts)Button Options
submits- Make button submit the formaction- Action name to triggerbuttonClass- CSS class for styling (e.g., “bg-blue-500”)
# Submit button
submitOpts = webform.buttonOpts(submits=True)
# Action button
actionOpts = webform.buttonOpts(
action="createTask",
buttonClass="bg-green-500"
)Editor Options
expandable- Allow editor to expand vertically
editorOpts = webform.editorOpts(expandable=True)
form.addEditor("notes", webform.opts(label="Notes"), editorOpts)Handling Form Data
Accessing Form Values
Form data is available in the value object after submission:
# Check if field exists
if hasattr(value, "projectName"):
print("Project:", value.projectName)
# Dictionary-style access
if "status" in value:
currentStatus = value["status"]
# Safe access with default
projectName = getattr(value, "projectName", "Untitled")Setting Default Values
Initialize form fields with default values:
defaults = {
"status": "Planning",
"priority": "Medium",
"startDate": time.now(),
"description": ""
}
for name, defval in defaults.items():
if not hasattr(value, name):
value[name] = defvalHandling Button Actions
React to button clicks by checking the operation:
if operation.isActionName("createTask"):
# Create new task node
newTaskID = node.linkToNewNode(
scriptFQN="user.domain.project.task",
label="New Task"
)
value._save = True # Trigger save after action
if operation.isActionName("addRequirement"):
# Create requirement
node.linkToNewNode(
scriptFQN="user.domain.project.requirement",
label="requirement"
)
value._save = TrueBest Practices
1. Organize with Groups and Tabs
For complex forms, use tabs to separate concerns:
# Good: Organized into logical tabs
detailsTab = form.addTab("_details", "Details")
settingsTab = form.addTab("_settings", "Settings")
actionsTab = form.addTab("_actions", "Actions")2. Use Columns for Related Fields
Place related fields side-by-side:
dateGroup = form.addGroup("_dateGroup")
startCol = dateGroup.addColumn("_startCol", 6)
endCol = dateGroup.addColumn("_endCol", 6)
startCol.addDateInput("startDate", webform.opts(label="Start Date"))
endCol.addDateInput("endDate", webform.opts(label="End Date"))3. Provide Helpful Info Text
Guide users with clear descriptions:
form.addTextInput("email",
webform.opts(
label="Email",
info="We'll use this for notifications",
required=True
))4. Default Values Pattern
Set defaults at the start of your script:
defaults = {
"title": "",
"description": "",
"status": "open",
"priority": "medium"
}
for name, defval in defaults.items():
if not hasattr(value, name):
value[name] = defval5. Conditional Form Elements
Show fields based on state:
# Only show button if not yet started
if value.startTime == '':
form.addButton("startTask", "Start Task",
webform.buttonOpts(action="startTask"))6. Use Hidden Fields for IDs
Store identifiers without displaying them:
myObj.addHidden("nodeID") # User won't see this
myObj.addHidden("version")Troubleshooting
Form Not Displaying
Ensure the form is created and not reassigned:
# Good
form = webform.new()
form.addTextInput("name", webform.opts(label="Name"))
# Bad - overwrites form
form = webform.new()
form = anotherFunction() # Don't do this!Values Not Persisting
Check that field names match property names:
# Form field name
form.addTextInput("projectName", webform.opts(label="Project"))
# Access in code - must match!
if hasattr(value, "projectName"): # Correct
name = value.projectName
# This won't work:
if hasattr(value, "project_name"): # Wrong! Use camelCaseDate Format Issues
Use correct format strings:
# UTC format
dateOpts = webform.dateInputOpts(
time=True,
valueFormat='YYYY-MM-DDTHH:mm:ss[Z]' # Note the [Z]
)Reference Links
- Starlark Extensions API - Complete API reference
- Python Scripting Guide - Overall scripting guide
- Image Creation Guide - Creating node visuals
Next Steps
Now that you understand forms:
- Review the Python Scripting Guide for overall script structure
- Learn about Creating Node Images for visual elements
- See complete script examples showing forms in context