Custom Field List with frozen columns and measures
Custom UI controls and views
by Flexmonster Pivot Table
January 15, 2026
HTML
<script src="https://cdn.flexmonster.com/flexmonster.js"></script>
<div>
<h2>Creating a custom Field List</h2>
<p>
This example shows how to create a custom Field List where the user can edit
only the <b>Rows</b> and <b>Report Filters</b> boxes, while
<b>Columns</b> and <b>Measures</b> remain the same.
</p>
<p>
For more details, refer to our
<a
href="https://www.flexmonster.com/doc/creating-a-custom-ui-control-or-view/#creating-custom-field-list"
target="_blank"
>step-by-step tutorial</a>.
</p>
</div>
<div id="wrapper">
<div id="backdrop"></div>
<div id="field-list" class="hideList">
<h3>Custom Field List</h3>
<button
id="field-list-cancel-button"
class="button"
onclick="closeFieldList()"
>
Cancel
</button>
<button id="field-list-apply-button" class="button" onclick="applySlice()">
Apply
</button>
<div class="field-list-layout">
<div id="available-fields" class="field-box">
<h3>Available fields</h3>
</div>
<div class="right-column">
<div id="rows" class="field-box">
<h3>Rows</h3>
</div>
<div id="report-filters" class="field-box">
<h3>Report filters</h3>
</div>
</div>
</div>
</div>
<div id="pivot-container"></div>
</div>
CSS
@import url(https://cdn.flexmonster.com/assets/jsfiddle-styles.css);
.field-list-layout {
display: flex;
gap: 30px;
height: 90%;
margin-top: 20px;
}
#fm-pivot-view .fm-fields-view-wrap.fm-fields-opened,
#fm-pivot-view div.fm-ui-modal-overlay {
display: none !important;
}
#wrapper {
font-family: system-ui;
height: 600px;
position: relative;
}
#available-fields {
flex: 1;
min-width: 150px;
display: flex;
flex-direction: column;
overflow-y: auto;
font-family: system-ui;
font-size: 13px;
}
.right-column {
flex: 2;
display: flex;
flex-direction: column;
justify-content: space-between;
gap: 20px;
}
#rows,
#report-filters {
flex: 1;
overflow-y: auto;
}
.field-box {
font-family: sans-serif;
border: 1px solid #ccc;
padding: 16px;
background-color: #fafafa;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
}
.field-box h3 {
margin: 0 0 10px 0;
font-size: 15px;
font-weight: 600;
}
.draggable-field {
padding: 8px 12px;
background-color: #eaeaea;
border: 1px solid #bbb;
border-radius: 4px;
margin-bottom: 8px;
margin-top: 5px;
cursor: grab;
transition: background-color 0.2s;
}
.draggable-field:hover {
background-color: #dcdcdc;
}
.button {
border-collapse: collapse;
box-sizing: border-box;
text-transform: uppercase;
position: relative;
border-radius: 4px;
cursor: pointer;
text-align: center;
display: inline;
font-size: 14px;
font-weight: bold;
padding: 7px 10px;
letter-spacing: 0.5px;
min-width: 50px;
border: 1px solid #555;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
height: 30px;
}
#field-list-apply-button {
float: right;
color: #fff;
background: #555;
}
#field-list-cancel-button {
float: right;
color: #555;
background:...
JavaScript
const pivot = new Flexmonster({
container: "pivot-container",
componentFolder: "https://cdn.flexmonster.com/",
report: {
dataSource: {
filename: "data/data.csv",
},
slice: {
rows: [],
columns: [
{
uniqueName: "Category",
},
],
measures: [
{
uniqueName: "Price",
aggregation: "sum",
},
],
reportFilters: [],
},
},
})
const backdrop = document.getElementById("backdrop")
let fieldListWasOpenedOnce = false
pivot.on("fieldslistopen", () => {
pivot.closeFieldsList()
openFieldList()
})
pivot.on("reportcomplete", function () {
if (!fieldListWasOpenedOnce) {
openFieldList()
fieldListWasOpenedOnce = true
}
})
function getAvailableFields() {
const allFields = pivot.getAllHierarchies()
const columns = pivot.getColumns().map((col) => col.uniqueName)
const measures = pivot.getMeasures().map((measure) => measure.uniqueName)
const rows = pivot.getRows().map((row) => row.uniqueName)
const reportFilters = pivot.getReportFilters().map((f) => f.uniqueName)
return allFields.filter(
(field) =>
!columns.includes(field.uniqueName) &&
!measures.includes(field.uniqueName) &&
!rows.includes(field.uniqueName) &&
!reportFilters.includes(field.uniqueName),
)
}
function openFieldList() {
const availableFields = getAvailableFields()
const currentRows = pivot.getRows()
const currentFilters = pivot.getReportFilters()
const renderFields = (containerId, fields) => {
const container = document.getElementById(containerId)
const header = container.querySelector("h3")?.outerHTML || ""
container.innerHTML = header
fields.forEach((field) => {
const item = document.createElement("div")
item.className = "draggable-field"
item.draggable = true
item.innerText = field.caption
...