Disable a PivotPanel Checkbox Field in Vue

Background:
Sometimes, you may want to disable the checkbox of a specific field in your PivotPanel to prevent users from interacting with it.
Steps to Complete:
- Create a PivotEngine (data + fields)
- Track which fields to disable
- Render PivotPanel + PivotGrid and keep a
refto the panel - Disable checkboxes via the fields grid’s
formatItem
Getting Started:
Create a PivotEngine (data + fields)
Engine shared by the Panel and Grid.
/* src/olap/engine.ts */
import * as wjOlap from '@mescius/wijmo.olap';
export type OrderRow = {
Country: string; Category: string; Product: string;
Amount: number; Quantity: number; Date: Date;
};
export function makeData(rows = 200): OrderRow[] {
const countries = ['US', 'UK', 'Germany', 'Japan'];
const cats = ['Electronics', 'Clothing'];
const prods: Record<string, string[]> = {
Electronics: ['Phone', 'Laptop', 'Tablet'],
Clothing: ['Shirt', 'Jacket', 'Jeans'],
};
const out: OrderRow[] = [];
for (let i = 0; i < rows; i++) {
const c = countries[(Math.random() * countries.length) | 0];
const cat = cats[(Math.random() * cats.length) | 0];
const p = prods[cat][(Math.random() * prods[cat].length) | 0];
out.push({
Country: c, Category: cat, Product: p,
Amount: +(Math.random() * 1000).toFixed(2),
Quantity: 1 + ((Math.random() * 10) | 0),
Date: new Date(2024, (Math.random() * 12) | 0, 1 + ((Math.random() * 28) | 0)),
});
}
return out;
}
export function createEngine(): wjOlap.PivotEngine {
return new wjOlap.PivotEngine({
itemsSource: makeData(),
fields: [
{ binding: 'Country', header: 'Country' },
{ binding: 'Category', header: 'Category' },
{ binding: 'Product', header: 'Product' },
{ binding: 'Amount', header: 'Amount', format: 'n2' },
{ binding: 'Quantity', header: 'Quantity' },
{ binding: 'Date', header: 'Date' },
],
rowFields: ['Country'],
valueFields: ['Amount', 'Quantity'],
});
}
Track which fields to disable
Reactive list of protected field headers. Checks against these names in the formatter.
<!-- src/App.vue -->
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import * as wjOlap from '@mescius/wijmo.olap';
import { createEngine } from './olap/engine';
import * as WjOlapVue from '@mescius/wijmo.vue2.olap'; // <wj-pivot-panel>, <wj-pivot-grid>
const engine = createEngine();
const disabledFields = ref<string[]>(['Amount']); // edit as needed
</script>
Render PivotPanel + PivotGrid and keep a ref to the panel
Bind both to the same engine; ref gives access to the underlying control. Wijmo Vue components expose the control via ref, letting you call ref.value.control.
<!-- src/App.vue -->
<template>
<div class="wrap">
<h2>Wijmo PivotPanel (Vue) — Disable specific field checkboxes</h2>
<!-- panel + grid bound to the same engine -->
<wj-pivot-panel ref="panelRef" :items-source="engine"></wj-pivot-panel>
<wj-pivot-grid :items-source="engine"></wj-pivot-grid>
<p>Disabled fields: <code>{{ disabledFields.join(', ') }}</code></p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue';
import * as wjOlap from '@mescius/wijmo.olap';
import { createEngine } from './olap/engine';
import * as WjOlapVue from '@mescius/wijmo.vue2.olap'; // registers components
const engine = createEngine();
const disabledFields = ref<string[]>(['Amount']);
const panelRef = ref<any>(null);
</script>
<style scoped>
.wrap { font-family: system-ui, Segoe UI, Arial; padding: 16px; }
wj-pivot-panel, wj-pivot-grid { display: block; margin: 12px 0; }
</style>
Disable checkboxes via the fields grid’s formatItem
Hook the internal fields list grid (_lbFields) formatItem and disable the <input type="checkbox"> when the field header matches.
<!-- still in src/App.vue (add to <script setup>) -->
<script setup lang="ts">
/* ...existing imports/refs... */
onMounted(() => {
const panel = panelRef.value?.control as wjOlap.PivotPanel | undefined;
const fieldsGrid = (panel as any)?._lbFields; // private; version-sensitive
if (!fieldsGrid?.formatItem) return;
const handler = (s: any, e: any) => {
if (e.panel === s.cells) {
const row = e.getRow();
const col = e.getColumn();
const header = row?.dataItem?.[col?.binding];
if (disabledFields.value.includes(header)) {
const chk: HTMLInputElement | null = e.cell.querySelector('input[type="checkbox"]');
if (chk) chk.disabled = true; // keep protected fields non-toggleable
}
}
};
fieldsGrid.formatItem.addHandler(handler);
// optional: cleanup on unmount
addEventListener('beforeunload', () => fieldsGrid.formatItem.removeHandler(handler));
});
</script>
If implemented correctly, your checkboxes should now be disabled/protected. I hope you find this article helpful. Happy coding!
Full code file to reference:
<!-- src/App.vue -->
<template>
<div class="wrap">
<h2>Wijmo PivotPanel (Vue) — Disable specific field checkboxes</h2>
<!-- Bind Panel + Grid to the same engine -->
<wj-pivot-panel ref="panelRef" :items-source="engine"></wj-pivot-panel>
<wj-pivot-grid :items-source="engine"></wj-pivot-grid>
<p>Disabled fields: <code>{{ disabledFields.join(', ') }}</code></p>
</div>
</template>
<script setup lang="ts">
/**
* import CSS here: ensures Wijmo styles load without requiring edits to main.ts.
*/
import '@mescius/wijmo.styles/wijmo.css';
import { ref, onMounted, onBeforeUnmount } from 'vue';
import * as wjOlap from '@mescius/wijmo.olap';
import '@mescius/wijmo.vue2.olap'; // registers <wj-pivot-panel> / <wj-pivot-grid>
/** ---------- Demo data + engine helpers ---------- */
type OrderRow = {
Country: string; Category: string; Product: string;
Amount: number; Quantity: number; Date: Date;
};
function makeData(rows = 200): OrderRow[] {
const countries = ['US', 'UK', 'Germany', 'Japan'];
const cats = ['Electronics', 'Clothing'];
const prods: Record<string, string[]> = {
Electronics: ['Phone', 'Laptop', 'Tablet'],
Clothing: ['Shirt', 'Jacket', 'Jeans'],
};
const out: OrderRow[] = [];
for (let i = 0; i < rows; i++) {
const c = countries[(Math.random() * countries.length) | 0];
const cat = cats[(Math.random() * cats.length) | 0];
const p = prods[cat][(Math.random() * prods[cat].length) | 0];
out.push({
Country: c,
Category: cat,
Product: p,
Amount: +(Math.random() * 1000).toFixed(2),
Quantity: 1 + ((Math.random() * 10) | 0),
Date: new Date(2024, (Math.random() * 12) | 0, 1 + ((Math.random() * 28) | 0)),
});
}
return out;
}
function createEngine(): wjOlap.PivotEngine {
return new wjOlap.PivotEngine({
itemsSource: makeData(),
fields: [
{ binding: 'Country', header: 'Country' },
{ binding: 'Category', header: 'Category' },
{ binding: 'Product', header: 'Product' },
{ binding: 'Amount', header: 'Amount', format: 'n2' },
{ binding: 'Quantity', header: 'Quantity' },
{ binding: 'Date', header: 'Date' },
],
rowFields: ['Country'],
valueFields: ['Amount', 'Quantity'],
});
}
/** ---------- State ---------- */
const engine = createEngine();
const disabledFields = ref<string[]>(['Amount']); // single source of truth for protected fields
const panelRef = ref<any>(null);
/** ---------- Disable checkboxes in the PivotPanel fields list ---------- */
let detachFormatItem: null | (() => void) = null;
onMounted(() => {
const panel = panelRef.value?.control as wjOlap.PivotPanel | undefined;
const fieldsGrid = (panel as any)?._lbFields; // internal fields list grid hosts the checkboxes
if (!fieldsGrid?.formatItem) return;
const handler = (s: any, e: any) => {
if (e.panel === s.cells) {
const row = e.getRow();
const col = e.getColumn();
const header = row?.dataItem?.[col?.binding];
if (disabledFields.value.includes(header)) {
const chk: HTMLInputElement | null = e.cell.querySelector('input[type="checkbox"]');
if (chk) chk.disabled = true; // prevent toggling protected fields
}
}
};
fieldsGrid.formatItem.addHandler(handler);
detachFormatItem = () => fieldsGrid.formatItem.removeHandler(handler);
});
/** ---------- Optional: block “Remove Field” via context menu ---------- */
let unpatchMenu: null | (() => void) = null;
onMounted(() => {
const panel = panelRef.value?.control as wjOlap.PivotPanel | undefined;
if (!panel) return;
try {
const MenuProto = (wjOlap as any)._ListContextMenu?.prototype;
if (MenuProto && !MenuProto.__patched && MenuProto._canExecute) {
const original = MenuProto._canExecute;
MenuProto._canExecute = function (this: any, cmd: string) {
const isRemove = /remove(field)?/i.test(cmd ?? '');
const header = this?._targetField?.header;
const owner = (this?.ownerPivotPanel ?? this?.hostPivotPanel)?.owner;
if (isRemove && header && owner?.disabledFields?.includes(header)) return false;
return original.apply(this, arguments as any);
};
MenuProto.__patched = true;
// expose disabledFields to the menu instance (best-effort; private API)
(panel as any).owner = { disabledFields: disabledFields.value };
unpatchMenu = () => {
// guard against double-unpatch or version differences
try { if (MenuProto._canExecute && original) MenuProto._canExecute = original; } catch {}
};
}
} catch {
// ignore if internals differ
}
});
/** ---------- Cleanup ---------- */
onBeforeUnmount(() => {
try { detachFormatItem?.(); } catch {}
try { unpatchMenu?.(); } catch {}
});
</script>
<style scoped>
.wrap { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; padding: 16px; }
wj-pivot-panel, wj-pivot-grid { display: block; margin: 12px 0; }
</style>