Hi,
I am new to PCF, React and Fluent UI, but I started playing with some components form the PCF Gallery, mainly the Action button made by MscrmTools.
My goal was to make the button width responsive to the available space in the container the button is located in.
I have found the following function which is supposed to make this possible:
This function allows the button to read the dimensions of the container through context.mode.allocatedWidth and context.mode.allocatedHeight and also kicks off the UpdateView if any of the container dimensions change.
This function does indeed kick of the UpdateView but for some reason the width of the button does not change. For example when I change the zoom of the page in a model-driven app it does put the correct pixel value into the width prop of the component but does not update the button accordingly.
I have added the following parts to the code of MscrmTools:
index.ts
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import ButtonControl, { IButtonControlProps } from "./QssFancyButton";
import { initializeIcons } from '@fluentui/font-icons-mdl2';
export class QssFancyButton implements ComponentFramework.StandardControl<IInputs, IOutputs> {
private container: HTMLDivElement;
private notifyOutputChanged: () => void;
private currentValue: string | null;
private buttonLabel: string;
private id: string;
private sendId: boolean;
private controlType: string;
/**
* Empty constructor.
*/
constructor()
{
initializeIcons();
}
/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
* Data-set values are not initialized here, use updateView.
* @Param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to property names defined in the manifest, as well as utility functions.
* @Param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously.
* @Param state A piece of data that persists in one session for a single user. Can be set at any point in a controls life cycle by calling 'setControlState' in the Mode interface.
* @Param container If a control is marked control-type='standard', it will receive an empty div element within which it can render its content.
*/
public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement)
{
this.container = container;
this.notifyOutputChanged = notifyOutputChanged;
this.buttonLabel = context.parameters.buttonLabel.raw ?? "";
this.id = context.parameters.buttonId.raw ?? "";
this.sendId = context.parameters.sendId.raw === "1";
context.mode.trackContainerResize(context.parameters.trackContainerResizeValue.raw === "1" ? true : false);
// @TS-ignore
this.controlType = context.parameters.buttonAttribute.attributes.Type;
if (this.buttonLabel.trim().startsWith("{"))
{
let json = JSON.parse(this.buttonLabel);
try
{
this.buttonLabel = json[context.userSettings.languageId];
if (this.buttonLabel === undefined)
{
this.buttonLabel = json[parseInt(Object.keys(json)[0])];
}
}
catch
{
this.buttonLabel = json[parseInt(Object.keys(json)[0])];
}
}
this.renderControl(context);
}
private renderControl(context: ComponentFramework.Context<IInputs>): void
{
let props: IButtonControlProps =
{
text: this.buttonLabel,
disabled: context.parameters.enableButtonOnDisabledForm.raw === "1" ? false : context.mode.isControlDisabled,
style:
{
backgroundColor: context.parameters.backgroundColor.raw ?? "#0078d4",
borderColor: context.parameters.borderColor.raw ?? "#0078d4",
color: context.parameters.textColor.raw ?? "#FFFFFF",
width: context.parameters.trackContainerResizeValue.raw === "1" ? `${context.mode.allocatedWidth}px` : context.parameters.trackContainerResizeValue.raw === "0" && context.parameters.width.raw !== null ? context.parameters.width.raw : "auto"
},
hoverBackgroundColor: context.parameters.hoverBackgroundColor.raw ?? "#106EBE",
hoverBorderColor: context.parameters.hoverBorderColor.raw ?? "#106EBE",
hoverColor: context.parameters.hoverTextColor.raw ?? "#FFFFFF",
checkedBackgroundColor: context.parameters.pressedBackgroundColor.raw ?? "#0078d4",
checkedBorderColor: context.parameters.pressedBorderColor.raw ?? "#0078d4",
checkedColor: context.parameters.pressedTextColor.raw ?? "#FFFFFF",
iconName: context.parameters.iconName.raw,
toolTip: context.parameters.tooltipText.raw ?? "",
onClick: () => { this.notifyOutputChanged(); }
}
ReactDOM.render(React.createElement(ButtonControl, props), this.container);
}
/**
* Called when any value in the property bag has changed. This includes field values, data-sets, global values such as container height and width, offline status, control metadata values such as label, visible, etc.
* @Param context The entire property bag available to control via Context Object; It contains values as set up by the customizer mapped to names defined in the manifest, as well as utility functions
*/
public updateView(context: ComponentFramework.Context<IInputs>): void
{
this.renderControl(context);
this.currentValue = context.parameters.buttonAttribute.raw ?? "";
}
/**
* It is called by the framework prior to a control receiving new data.
* @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as “bound” or “output”
*/
public getOutputs(): IOutputs
{
if (this.controlType === "string")
{
return {
buttonAttribute: this.sendId ? this.id : this.buttonLabel
};
}
return {
buttonAttribute: new Date()
};
}
/**
* Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup.
* i.e. cancelling any pending remote calls, removing listeners, etc.
*/
public destroy(): void {
// Add code to cleanup control if necessary
}
}
Inside the public init:
context.mode.trackContainerResize(context.parameters.trackContainerResizeValue.raw === "1" ? true : false);
Inside the private renderControl:
width: context.parameters.trackContainerResizeValue.raw === "1" ? `${context.mode.allocatedWidth}px` : context.parameters.trackContainerResizeValue.raw === "0" && context.parameters.width.raw !== null ? context.parameters.width.raw : "auto"
ControlManifest.Input.xml
<property name="trackContainerResizeValue" display-name-key="trackContainerResizeValue_Display_Key" description-key="trackContainerResizeValue_Desc_Key" of-type="Enum" default-value="0" usage="input" required="true"> <value name="true" display-name-key="yes_Resize_Display_Key" description-key="yes_Resize_Desc_Key">1</value> <value name="false" display-name-key="no_Resize_Display_Key" description-key="no_Resize_Desc_Key">0</value> </property>
I did not change anything in the .tsx as there was no need to, if I'm not mistaken.
The following happens when changing the width of the component container in npm start watch, or from a model-driven app:
If anybody is willing to help me out that would be great, if you need my files, let me know but they are basically the same as MscrmTools his code with the lines above added to it.
A coworker of mine has found the solution.
Ignoring context.mode.trackContainerResize and instead utilizing width 100% he managed to fix the issue.
Apparently, in this component if you don't set the width of the tooltip with the width of the button this behavior happens.
So my coworker fixed it with the following added:
The code now works as intended and looks as follows:
import * as React from 'react'
import { Stack } from '@fluentui/react/lib/Stack';
import { IBaseButtonProps, IBaseButtonState, IButtonStyles, PrimaryButton, IconButton } from '@fluentui/react/lib/Button';
import { IIconProps } from '@fluentui/react';
import { TooltipHost, ITooltipHostStyles } from '@fluentui/react/lib/Tooltip';
export interface IButtonControlProps extends IBaseButtonProps {
hoverBackgroundColor: string,
hoverBorderColor: string,
hoverColor: string,
checkedBackgroundColor: string,
checkedBorderColor: string,
checkedColor: string,
iconName: string | null,
toolTip: string | undefined,
}
export default class ButtonControl extends React.Component<IButtonControlProps, IBaseButtonState>{
constructor(props: IButtonControlProps) {
super(props);
}
styles: IButtonStyles = {
root: {
backgroundColor: this.props.style?.backgroundColor ?? "#0078d4",
borderColor: this.props.style?.borderColor ?? "#0078d4",
color: this.props.style?.color ?? "#FFFFFF",
width: this.props.style?.width
},
rootHovered: {
backgroundColor: this.props.hoverBackgroundColor ?? "#106EBE",
borderColor: this.props.hoverBorderColor ?? "#106EBE",
color: this.props.hoverColor ?? "#FFFFFF",
width: this.props.style?.width
},
rootPressed: {
backgroundColor: this.props.checkedBackgroundColor ?? "#0078d4",
borderColor: this.props.checkedBorderColor ?? "#0078d4",
color: this.props.checkedColor ?? "#FFFFFF",
width: this.props.style?.width
}
}
tooltipStyles: ITooltipHostStyles = {
root: {
width: this.props.style?.width
}
}
icon: IIconProps = { iconName: this.props.iconName ?? "" };
render() {
const toolTipId = `tooltip_${this.props.iconName}`;
return (
<Stack horizontal>
{this.props.text?.trim().length ? (
<TooltipHost content={this.props.toolTip} id={toolTipId} styles={this.tooltipStyles}>
<PrimaryButton
iconProps={this.icon}
styles={this.styles}
text={this.props.text}
disabled={this.props.disabled}
onClick={this.props.onClick}
aria-describedby={toolTipId}
/>
</TooltipHost>
) : (
<TooltipHost content={this.props.toolTip} id={toolTipId} styles={this.tooltipStyles}>
<IconButton
iconProps={this.icon}
styles={this.styles}
disabled={this.props.disabled}
onClick={this.props.onClick}
aria-describedby={toolTipId}
/>
</TooltipHost>
)}
</Stack>
);
}
}
Hi a33ik,
Thank you for your response.
Unfortunately, when I put 100% into the width variable it just looks at the PCF container itself which is as big as the label and icon with some margin, it does not look, natively, at the container in a Model-driven app it is located.
I build in the possibility to put a width manually which you can see in the following screenshot:
But it does not change the width of the button to fit the whole section as you can see in the left part of the screenshot.
To translate the options for the control:
Once I put in a value like 250% or 500px into the width property manually it does make the button bigger:
But when context.mode.trackContainerResize dynamically, for example when the size of the page changes, puts in a pixel value through context.mode.allocatedWidth into the width property of the button it does not change. which is strange.
Hello,
Did you try to set the button width to "100%"?
WarrenBelz
109
Most Valuable Professional
Michael E. Gernaey
82
Super User 2025 Season 1
MS.Ragavendar
72