Sometimes you need to make it easier to switch between tabs in a dynamics crm form. Here's how you can use office fabric ui to make a "pivot". Just add this to your webpack build.
/** Navigation via Pivot from Office UI Fabric. */
import React from "react"
import { render } from "react-dom"
import {Fabric} from "office-ui-fabric-react/lib"
import { Pivot, PivotItem }
from 'office-ui-fabric-react/lib/Pivot';
/**
* Manage state as best you can given that all changes are side-effects to dynamics.
* There is not an easy way to detect when the tabs are changed external to
* react. Tabs hidden at the start are always hidden. Tabs are set once in props once
* at creation time and cannot be changed.
*/
export class DynamicsPivot extends React.Component {
static ALL = -1
constructor(props) {
super(props)
let alwaysHidden = props.alwaysHidden || []
if(props.tabs && !props.alwaysHidden) {
// tabs is a fake array
for(let i=0; i< props.tabs.getLength(); i++) {
const t = props.tabs.get(i)
if(!t.getVisible()) alwaysHidden.push(i)
}
}
this.state = {
alwaysHidden, // indexes to hide, 0-based
selectedIndex: props.selectedIndex ||
((props.tabs && props.tabs.getLength() > 0) ? 0 : DynamicsPivot.ALL),
tabs: props.tabs,
// true=>collapse un-selected tabs but keep visible
// otherwise un-selected tabs are not visible
collapse: props.collapse,
labelMap: props.labelMap, // use these labels instead of actual tab labels
}
}
static defaultProps = {
alwaysHidden: null,
collapse: true,
tabs: null,
labelMap: {}
}
/** Get a label for a tab. Uses labelMap. */
getLabel = (t) => {
const lab = t.getLabel()
const calc = this.state.labelMap[lab]
if(calc) return calc
return lab
}
/** Hide all but selected. This has side effects not visible to react. */
processTabs = () => {
const selected = this.state.selectedIndex
const haveTabs = this.state.tabs
if(haveTabs) {
for(let i=0; i < this.state.tabs.getLength(); i++) {
const shouldHide = this.state.alwaysHidden.includes(i)
const t = this.state.tabs.get(i)
if((i !== selected || shouldHide) && selected != DynamicsPivot.ALL) {
// hide
if(this.state.collapse) t.setDisplayState("collapsed")
else t.setVisible(false)
}
else if(!shouldHide) {
t.setDisplayState("expanded")
t.setVisible(true)
}
}
const focusIndex = (selected === DynamicsPivot.ALL &&
haveTabs && this.state.tabs.getLength()>0) ? 0 :
(haveTabs &&
selected < this.state.tabs.getLength() ? selected : -1)
if(haveTabs && focusIndex >= 0)
this.state.tabs.get(focusIndex).setFocus()
}
}
handleClick = (pivotItem) =>
this.setState({selectedIndex: parseInt(pivotItem.props.itemKey)})
render() {
const pivots = []
for(let i=0; i<this.state.tabs.getLength(); i++) {
const t = this.state.tabs.get(i)
if(!this.state.alwaysHidden.includes(i))
pivots.push({linkText: this.getLabel(t), i})
}
pivots.push({linkText: "All", i: DynamicsPivot.ALL}) // ALL is last
this.processTabs() // kind of like a render
return (
<Pivot headersOnly onLinkClick={this.handleClick}>
{
pivots.map(p =>
<PivotItem linkText={p.linkText} itemKey={p.i} key={p.i}/>)
}
</Pivot>
)
}
}
export function run({target, collapse, labelMap}) {
const _xrm = window.parent.Xrm || Xrm
collapse = collapse || true
const dataStr = Xrm.Page.context.getQueryStringParameters()["data"]
const data = dataStr ? JSON.parse(dataStr) : {}
collapse = collapse || data.collapse || true
labelMap = labelMap || data.labelMap || {}
render(
<Fabric>
<DynamicsPivot tabs={_xrm.Page.ui.tabs}
collapse={collapse}
labelMap={labelMap}/>
</Fabric>,
target)
}
Here's the HTML entry point:
<html>
<head>
<meta charset="utf-8" />
<title>Navigation Privot</title>
<script src="../ClientGlobalContext.js.aspx"></script>
</head>
<body>
<div id="container"/>
<script type="text/javascript" src="./js/NavigationPivot.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
var el = document.getElementById("container");
NavigationPivot.run({target: el});
})
</script>
</body>
</html>