> ## Documentation Index
> Fetch the complete documentation index at: https://learn.nextedy.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Top Panel Template

> The top panel template (`risksheetTopPanel.vm`) renders a customizable HTML area above the Nextedy RISKSHEET grid (referred to as the attributes panel in the KB).

export const LastReviewed = ({date}) => {
  if (!date) return null;
  const formatted = new Date(`${date}T00:00:00Z`).toLocaleDateString("en-US", {
    year: "numeric",
    month: "long",
    day: "numeric",
    timeZone: "UTC"
  });
  return <p className="mt-10 text-sm text-gray-400 dark:text-zinc-500 not-prose">
      Last reviewed on {formatted}
    </p>;
};

## Template File and Loading

The template file is named `risksheetTopPanel.vm` and is stored as a document attachment in Polarion. The `/api/panel` endpoint loads the file, renders it through the Velocity engine with full access to Polarion services, and returns the resulting HTML. If an error occurs during rendering, a red error message box displays instead of the panel content.

<Frame>
  <img src="https://mintcdn.com/none-17b4493f/yWl5nA2D0IzEnZC1/risksheet/diagrams/reference/templates/top-panel-template/diagram-1.svg?fit=max&auto=format&n=yWl5nA2D0IzEnZC1&q=85&s=e288db0377add6566bbe154fbaabfabb" alt="diagram" style={{ maxWidth: "420px", width: "100%" }} width="420" height="320" data-path="risksheet/diagrams/reference/templates/top-panel-template/diagram-1.svg" />
</Frame>

<Tip title="Template Inheritance">
  If no `risksheetTopPanel.vm` file is attached to the current document, Risksheet searches the document's template hierarchy. The system uses the same configuration inheritance path as the sheet configuration. See [Template Path Configuration](/risksheet/reference/configuration/template-path-configuration) for the lookup order.
</Tip>

## Three-File Architecture: Declarative Config vs Custom Logic

Risksheet uses three distinct configuration files, each with a different role and change-management profile. Understanding this separation is critical for regulated industries (medical devices, automotive, aerospace):

| Layer                  | File                                               | Content Type                                                           | Change Impact                                       |
| ---------------------- | -------------------------------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------- |
| **Declarative config** | sheet configuration (`risksheet.json`)             | WHAT to show: columns, levels, dataTypes, styles                       | Auditable structure, auto-validatable, easy to diff |
| **Custom logic**       | top panel configuration (`risksheetTopPanel.vm`)   | HOW to compute: risk matrices, conditional formatting, formula helpers | Custom code, requires code review                   |
| **Export formatting**  | PDF export configuration (`risksheetPdfExport.vm`) | HOW to print: PDF layout, cover pages                                  | Presentation layer                                  |

### Why Externalize JavaScript to the Top Panel?

This separation simplifies change management in regulated industries:

1. **Validation scope** -- When the risk matrix changes (for example, severity thresholds updated), only the top panel changes. The sheet configuration (which defines grid structure) is untouched, narrowing the validation scope for change control.
2. **Auditability** -- The sheet configuration is a declarative artifact. Tools can automatically verify its structure (correct property names, valid enum references, level consistency) without parsing JavaScript.
3. **Template reuse** -- The same sheet configuration structure can work with different calculation strategies by swapping the top panel. For example, one top panel implements a 2D risk matrix (severity x occurrence to classification); another implements a 3D RPN calculation (S x O x D to numeric score).
4. **Separation of concerns** -- Configuration authors (risk managers) define WHAT columns exist and how data flows. Script authors (developers, validation engineers) define HOW values are calculated. Different competencies, different review gates.

**Recommended pattern**: in the sheet configuration, keep `formulas` entries as thin wrappers that delegate to functions defined in the top panel. Example:

```yaml theme={null}
formulas:
  initialRE: "(info) => { return getInitialRE(info); }"
```

The full `getInitialRE` implementation lives in the top panel `<script>` block, alongside risk matrix logic and any jQuery-based cell decorators. In-line formulas in the sheet configuration are acceptable for simple calculations (for example, `RPN = S * O * D`); complex logic (risk matrices, multi-field conditional formatting) should be externalized to the top panel.

## Template Structure

A typical `risksheetTopPanel.vm` file contains three sections:

| Section          | Purpose                                                                      | Example                                |
| ---------------- | ---------------------------------------------------------------------------- | -------------------------------------- |
| `<style>` block  | CSS rules for panel layout, image sizing, and custom classes                 | `.rs-top-panel-info { padding: 8px; }` |
| HTML markup      | Document metadata display, action buttons, filter controls                   | `<div>Product: $!productFamily</div>`  |
| `<script>` block | JavaScript functions for formulas, query factories, and context menu actions | `function calcRisk(info) { ... }`      |

```velocity theme={null}
## Velocity comments start with ##

## 1. CSS styling
<style>
  .panel-info { display: flex; gap: 16px; padding: 8px; }
</style>

## 2. HTML content
<div class="panel-info">
  <span>Document: $doc.moduleNameWithSpace</span>
</div>

## 3. JavaScript functions
<script>
  function myFormulaHelper(info) {
    return info.item['sev'] * info.item['occ'];
  }
</script>
```

## Velocity Context Variables

The top panel template receives the full Velocity context with access to Polarion services and document data. See [Velocity Template Context](/risksheet/reference/api/velocity-context) for the complete reference.

| Variable           | Type               | Description                                                                                               |
| ------------------ | ------------------ | --------------------------------------------------------------------------------------------------------- |
| `$doc`             | `IModule`          | Current Polarion document object. Provides access to document metadata, custom fields, and the module API |
| `$document`        | `Document`         | Risksheet document wrapper with `customFields` access for direct field reading                            |
| `$transaction`     | `ITransaction`     | Current Polarion transaction for data operations                                                          |
| `$trackerService`  | `ITrackerService`  | Polarion tracker service for work item queries and data retrieval                                         |
| `$projectService`  | `IProjectService`  | Polarion project service for project-level metadata                                                       |
| `$securityService` | `ISecurityService` | Polarion security service for current user and role lookup                                                |

### \$securityService -- Role-Based UI

The `$securityService` is available in top panel Velocity and exposes the current user and that user's roles. Use it to drive role-based default views, conditional rendering, and permission-aware UI:

| Method                  | Returns            | Description                               |
| ----------------------- | ------------------ | ----------------------------------------- |
| `getCurrentUser()`      | user object        | The currently authenticated Polarion user |
| `getRolesForUser(user)` | list of role names | The roles assigned to the given user      |

```velocity theme={null}
#set($currentUser = $securityService.getCurrentUser())
#set($userRoles = $securityService.getRolesForUser($currentUser))

<script>
  var currentUserRoles = [
  #foreach($role in $userRoles)
    "$role"#if($foreach.hasNext),#end
  #end
  ];

  // Example: select a default view based on user role
  window.risksheet = window.risksheet || {};
  if (currentUserRoles.indexOf("safety_reviewer") >= 0
      && risksheet.appConfig && risksheet.appConfig.views) {
    // Reviewers see the "Final Risk Assessment" view by default
    risksheet.appConfig.views.forEach(function(v) {
      v.defaultView = (v.name === "Final Risk Assessment");
    });
  }
</script>
```

Typical use cases: showing reviewer-only filters, hiding administrative buttons from end users, and selecting an appropriate saved view per role.

## Accessing Document Custom Fields

There are two approaches to reading document-level custom fields in the top panel.

### Method 1: \$doc.getOldApi().getValue()

Use `$doc.getOldApi().getValue('customFieldID')` for programmatic access. This is the preferred method when you need to pass values into JavaScript variables:

```velocity theme={null}
#set($productFamily = $doc.getOldApi().getValue('productFamily'))
#set($riskMatrixVersion = $doc.getOldApi().getValue('riskMatrixVersion'))
<script>
  var documentProductFamily = "$!productFamily";
  var matrixVersion = "$!riskMatrixVersion";
</script>
```

### Method 2: \$document.customFields

Use `$document.customFields.fieldName` for direct display in HTML markup:

```velocity theme={null}
<div class="rs-top-panel-info">
  <span>Product Family: $!document.customFields.productFamily</span>
  <span>Risk Matrix: $!document.customFields.riskMatrixVersion</span>
</div>
```

<Note title="customFields works in the top panel">
  `$!document.customFields.<field>` renders the document custom-field value correctly in the top panel template — the shipped Risksheet top panels rely on it (for example `$!document.customFields.item`, `owner`, `version`, `model`, `team`). This differs from a standard Polarion page Velocity context, where `$document.customFields` may print empty and `$document.getValue('id')` is the usual form. In the top panel, both Method 1 and Method 2 above are valid; use `$!` (with the exclamation mark) to suppress null-reference output for optional fields.
</Note>

<Warning title="Read-Only Access">
  The top panel can **display** document custom fields but **cannot modify** them. To change document field values, use the standard Polarion document editor. Custom context menu actions for document workflow transitions are not natively supported.
</Warning>

<Note title="Table-Type Custom Fields">
  Table-type custom fields can also be accessed via `$doc.getOldApi().getValue('customFieldID')`. The returned object structure depends on the Polarion table field implementation.
</Note>

## Bridging Server Data to Client-Side Formulas

The primary use case for the top panel template is defining JavaScript functions that sheet configuration formulas can call. This bridges server-side Polarion data (accessible via Velocity) into client-side formula execution.

### Pattern

<Frame>
  <img src="https://mintcdn.com/none-17b4493f/yWl5nA2D0IzEnZC1/risksheet/diagrams/reference/templates/top-panel-template/diagram-2.svg?fit=max&auto=format&n=yWl5nA2D0IzEnZC1&q=85&s=99ffc433ebf0154105f2b85dfa91c07d" alt="diagram" style={{ maxWidth: "600px", width: "100%" }} width="600" height="280" data-path="risksheet/diagrams/reference/templates/top-panel-template/diagram-2.svg" />
</Frame>

**Step 1.** Define a JavaScript function in `risksheetTopPanel.vm` using Velocity to inject server-side data:

```velocity theme={null}
#set($matrixData = $doc.getOldApi().getValue('riskMatrix'))
<script>
  var riskMatrix = $matrixData;

  function getRiskAcceptance(severity, occurrence) {
    return riskMatrix[severity][occurrence];
  }
</script>
```

**Step 2.** Reference the top panel function from a formula in the sheet configuration:

```yaml theme={null}
formulas:
  riskAcceptance: "function(info) { return getRiskAcceptance(info.item['severity'], info.item['occurrence']); }"
```

<Tip title="Accessing Work Item Data in Top Panel Functions">
  Top panel functions called from formulas receive the same `info` object. Access work item field values via `info.item['fieldId']` and return the result. Prepare all needed values within functions defined in the top panel file.
</Tip>

### Reading Downstream Task Data with risksheet.ds.getDownstreamRows

For formulas that need access to downstream task work items linked from the current risk row, the client-side data source exposes `risksheet.ds.getDownstreamRows(riskId)`. This is useful for aggregating values across mitigation tasks, computing initial-vs-residual risk indices, or iterating over multi-round assessments.

```javascript theme={null}
function getInitialRiskIndex(info) {
  var riskId = info.item["systemItemId"];
  var tasks = risksheet.ds.getDownstreamRows(riskId);

  var det = 0;
  tasks.forEach(function(t) {
    // Direct field access on task objects
    var round = t.round;
    var rcm = t.RCMdet;

    // Filter by enum string value
    if (round === "initial") {
      // Enum underscore-to-decimal pattern: "0_1" -> 0.1
      det = parseFloat((rcm || "0_0").replace("_", "."));
    }
  });

  // Read another formula's stored result via info.item
  return det * info.item['ri'];
}
```

| Pattern                                  | Details                                                                                                             |
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `risksheet.ds.getDownstreamRows(riskId)` | Returns an array of task objects linked downstream from the risk identified by `riskId`                             |
| `t.fieldId`                              | Direct field access on each task object (for example, `t.RCMdet`, `t.round`)                                        |
| Enum underscore-to-decimal               | Enum values are stored with underscore for decimals: `"0_1"` becomes `0.1` via `replace("_", "."); parseFloat(...)` |
| `info.item['columnId']`                  | A formula can read another formula's stored result on the same row through `info.item`                              |

This API enables risk roll-ups and cross-row aggregations that would otherwise require server-side rendering or custom Velocity macros.

## Dynamic Risk Matrices from External Sources

The top panel can reference external data sources via Velocity context and Polarion APIs, enabling dynamic risk matrix definitions shared across projects without duplicating formula logic in each sheet configuration:

```velocity theme={null}
#set($xmlConfig = $trackerService.getDataService().getAttachment($doc, 'risk-matrix.xml'))
<script>
  var riskMatrix = parseRiskMatrix('$!xmlConfig');

  function evaluateRiskCondition(severity, occurrence) {
    return riskMatrix.lookup(severity, occurrence);
  }
</script>
```

This pattern replaces hardcoded `riskCondition` formulas in each project's sheet configuration with a centralized, dynamic matrix definition.

<Info title="Verify in application">
  The exact API for loading attachments and external XML files depends on your Polarion version and installed plugins. The pattern above demonstrates the general approach -- verify the specific Polarion API calls in your environment.
</Info>

## Filtering Linked Items with Query Factory

Combine the top panel template with a query factory function to filter item suggestions based on document-level fields. This is useful when different documents in the same project should only link to items matching their product family or variant.

**Top panel template (`risksheetTopPanel.vm`):**

```velocity theme={null}
#set($productFamily = $doc.getOldApi().getValue('productFamily'))
<script>
  var documentProductFamily = "$!productFamily";

  window.risksheet = window.risksheet || {};
  window.risksheet.queryFactories = window.risksheet.queryFactories || {};
  window.risksheet.queryFactories["filterByProductFamily"] = function(info) {
    return 'type:requirement AND productFamily:' + documentProductFamily;
  };
</script>
```

**Column configuration in the sheet configuration:**

```yaml theme={null}
columns:
  - bindings: linkedRequirement
    header: Linked Requirement
    type: itemLink
    typeProperties:
      queryFactory: "filterByProductFamily"
```

The query factory function constructs a Lucene query string that filters the item suggester results. The function receives a single `info` object describing the current row and context.

## Enum Type Identifiers

When configuring enum columns, the `type` property in the sheet configuration must match the enum definition name in Polarion's custom fields XML. The top panel can help verify or map enum identifiers:

```velocity theme={null}
<script>
  function getEnumDisplayValue(info) {
    var fieldValue = info.item['riskCategory'];
    // Map enum ID to display label if needed
    return fieldValue;
  }
</script>
```

<Note title="Finding Enum Identifiers">
  Check the `.polarion/documents/fields/custom-fields.xml` file in your project's SVN repository for the exact enum definition name to use in the `type` property. The enum type identifier in the sheet configuration must match exactly.
</Note>

## CSS Styling in the Top Panel

The top panel template can include `<style>` blocks to control the appearance of the panel content, rich text images, and custom cell rendering:

```velocity theme={null}
<style>
  .rs-rich-text-image img {
    max-height: 40px;
    object-fit: contain;
  }
  .rs-top-panel-info {
    display: flex;
    gap: 16px;
    padding: 8px;
    font-size: 13px;
    color: #333;
  }
  .rs-top-panel-info .label {
    font-weight: 600;
    color: #555;
  }
</style>
```

<Tip title="Rich Text Image Display">
  When rendering rich text fields with images via server render columns, set the `bindings` to `task.$item` rather than the specific field binding. Control image sizing through CSS in the top panel. See [Render Custom Data](/risksheet/guides/columns/custom-data-rendering) for details.
</Tip>

## Context Menu Integration

Custom context menu actions can be registered via `window.risksheet.customContextMenuActions` in the top panel `<script>` block. Functions must exist on the global `window` object.

```velocity theme={null}
<script>
  window.myCustomAction = function(item) {
    var projectId = "$!{project.id}";
    // Custom action implementation using server-side data
    alert('Running check for item ' + item.ID + ' in project ' + projectId);
  };

  window.risksheet = window.risksheet || {};
  window.risksheet.customContextMenuActions = [
    {
      label: "Run Custom Check",
      enabled: function(item) { return !item.isNew; },
      execute: function(item) { window.myCustomAction(item); }
    }
  ];
</script>
```

Each custom context menu action requires three properties:

| Property  | Type             | Description                                                                                                       |
| --------- | ---------------- | ----------------------------------------------------------------------------------------------------------------- |
| `label`   | `string`         | Text displayed in the context menu                                                                                |
| `enabled` | `function(item)` | Returns `true` if the action is available for the given item. The function receives the current row's data object |
| `execute` | `function(item)` | The action to perform when clicked. The function receives the current row's data object                           |

## Maximize/Restore Toggle

The top panel visibility can be toggled by the user to maximize the grid viewing area. When toggled:

* The top panel hides entirely
* The grid expands to use the full available space
* The toggle button is always available in the toolbar

This is useful when the top panel displays informational content that is not needed during active editing.

## Error Handling

If the `risksheetTopPanel.vm` template contains errors (Velocity syntax errors, missing variable references, or JavaScript errors), the panel displays a red error message box instead of the expected content. The grid remains functional even when the top panel fails to render.

<Warning title="Template Debugging">
  If the top panel displays an error, check the Polarion server logs for Velocity rendering errors. Common issues include missing `$doc` references, undefined custom field IDs, and malformed Velocity syntax. Use `$!variable` (with exclamation mark) to suppress null reference errors for optional fields.
</Warning>

## Complete Example

A full `risksheetTopPanel.vm` file for an FMEA document with metadata display, role-aware default view, risk evaluation function, downstream task aggregation, query factory, and a custom context menu action:

```velocity theme={null}
## risksheetTopPanel.vm - FMEA document top panel
## Displays product family, matrix version, and document name
## Provides risk evaluation function and filtered item linking

#set($productFamily = $doc.getOldApi().getValue('productFamily'))
#set($riskMatrixVersion = $doc.getOldApi().getValue('riskMatrixVersion'))
#set($currentUser = $securityService.getCurrentUser())
#set($userRoles = $securityService.getRolesForUser($currentUser))

<style>
  .fmea-panel {
    display: flex;
    gap: 16px;
    padding: 8px 12px;
    font-size: 13px;
    background: #f5f5f5;
    border-bottom: 1px solid #ddd;
  }
  .fmea-panel .label {
    font-weight: 600;
    color: #555;
  }
  .fmea-panel .value {
    color: #333;
  }
</style>

<div class="fmea-panel">
  <span><span class="label">Product Family:</span>
    <span class="value">$!productFamily</span></span>
  <span><span class="label">Matrix Version:</span>
    <span class="value">$!riskMatrixVersion</span></span>
  <span><span class="label">Document:</span>
    <span class="value">$doc.moduleNameWithSpace</span></span>
</div>

<script>
  // Server-side data injected via Velocity
  var documentProductFamily = "$!productFamily";
  var currentUserRoles = [
  #foreach($role in $userRoles)
    "$role"#if($foreach.hasNext),#end
  #end
  ];

  // Risk evaluation function called from sheet configuration formulas
  function getRiskLevel(severity, occurrence) {
    var matrix = {
      "high":   { "high": "unacceptable", "medium": "undesirable", "low": "acceptable" },
      "medium": { "high": "undesirable",  "medium": "acceptable",  "low": "acceptable" },
      "low":    { "high": "acceptable",   "medium": "acceptable",  "low": "negligible" }
    };
    return matrix[severity]
      ? (matrix[severity][occurrence] || "unknown")
      : "unknown";
  }

  // Aggregate downstream task data for residual risk computation
  function getResidualRisk(info) {
    var tasks = risksheet.ds.getDownstreamRows(info.item["systemItemId"]);
    var reduction = 0;
    tasks.forEach(function(t) {
      // Enum "0_1" -> 0.1
      if (t.round === "final" && t.RCMdet) {
        reduction += parseFloat(t.RCMdet.replace("_", "."));
      }
    });
    return reduction;
  }

  // Role-based default view selection
  window.risksheet = window.risksheet || {};
  if (currentUserRoles.indexOf("safety_reviewer") >= 0
      && risksheet.appConfig && risksheet.appConfig.views) {
    risksheet.appConfig.views.forEach(function(v) {
      v.defaultView = (v.name === "Final Risk Assessment");
    });
  }

  // Query factory for filtering linked item suggestions by product family
  window.risksheet.queryFactories = window.risksheet.queryFactories || {};
  window.risksheet.queryFactories["filterByProductFamily"] = function(info) {
    return 'type:requirement AND productFamily:' + documentProductFamily;
  };

  // Custom context menu action
  window.risksheet.customContextMenuActions = [
    {
      label: "Evaluate Risk Level",
      enabled: function(item) { return item['severity'] && item['occurrence']; },
      execute: function(item) {
        var level = getRiskLevel(item['severity'], item['occurrence']);
        alert('Risk Level: ' + level);
      }
    }
  ];
</script>
```

The corresponding sheet configuration formulas reference the top panel functions:

```yaml theme={null}
formulas:
  riskLevel: "function(info){ return getRiskLevel(info.item['severity'], info.item['occurrence']); }"
  residualRisk: "function(info){ return getResidualRisk(info); }"
columns:
  - id: riskLevel
    header: Risk Level
    bindings: riskLevel
    formula: riskLevel
  - id: residualRisk
    header: Residual Risk
    bindings: residualRisk
    formula: residualRisk
  - bindings: linkedRequirement
    header: Linked Requirement
    type: itemLink
    typeProperties:
      queryFactory: "filterByProductFamily"
```

## Related Pages

* [Velocity Template Context](/risksheet/reference/api/velocity-context) -- full list of available Velocity context variables
* [Velocity Templates](/risksheet/reference/templates/velocity-templates) -- Velocity template engine reference and syntax
* [Custom Renderer Templates](/risksheet/reference/templates/custom-renderers) -- cell display customization templates
* [PDF Export Template](/risksheet/reference/templates/pdf-export-template) -- PDF export Velocity template
* [Formula Functions](/risksheet/reference/formulas/formula-functions) -- formula patterns that call top panel functions
* [Customize the Top Panel](/risksheet/guides/customization/top-panel) -- step-by-step how-to guide
* [Configuration Properties Index](/risksheet/reference/configuration/properties-index) -- all configuration properties

<LastReviewed date="2026-06-24" />
