|
| 1 | +import * as React from "react"; |
| 2 | +import { IInputs, IOutputs } from "./generated/ManifestTypes"; |
| 3 | +import { CardCanvas } from "./components/CardCanvas"; |
| 4 | +import { CustomCardProps } from "./components/Component.types"; |
| 5 | +import { ManifestPropertyNames, Orientation, Size, StringConstants } from "./ManifestConstants"; |
| 6 | +import { getItemsFromDataset } from "./components/Toolbar/datasetmapping"; |
| 7 | +import { IToolbarItem } from "./components/Toolbar/Component.types"; |
| 8 | +import { getUrlfromImage } from "./components/helper"; |
| 9 | +import { ContextEx } from "./components/ContextExtended"; |
| 10 | + |
| 11 | +export class Card implements ComponentFramework.ReactControl<IInputs, IOutputs> { |
| 12 | + context: ComponentFramework.Context<IInputs>; |
| 13 | + items: IToolbarItem[]; |
| 14 | + componentKey = "powerapps-corecontrol-toolbar"; |
| 15 | + height?: number; |
| 16 | + notifyOutputChanged: () => void; |
| 17 | + /** |
| 18 | + * Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here. |
| 19 | + * Data-set values are not initialized here, use updateView. |
| 20 | + * @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. |
| 21 | + * @param notifyOutputChanged A callback method to alert the framework that the control has new outputs ready to be retrieved asynchronously. |
| 22 | + * @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. |
| 23 | + */ |
| 24 | + public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void): void { |
| 25 | + this.notifyOutputChanged = notifyOutputChanged; |
| 26 | + this.context = context; |
| 27 | + this.onSelect = this.onSelect.bind(this); |
| 28 | + context.mode.trackContainerResize(true); |
| 29 | + } |
| 30 | + |
| 31 | + /** |
| 32 | + * 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. |
| 33 | + * @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 |
| 34 | + * @returns ReactElement root react element for the control |
| 35 | + */ |
| 36 | + public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement { |
| 37 | + this.context = context; |
| 38 | + const datasetChanged = context.updatedProperties.indexOf(ManifestPropertyNames.dataset) > -1 || !this.items; |
| 39 | + const allocatedWidth = parseInt(context.mode.allocatedWidth as unknown as string); |
| 40 | + const allocatedHeight = parseInt(context.mode.allocatedHeight as unknown as string); |
| 41 | + if (datasetChanged) { |
| 42 | + this.items = getItemsFromDataset(context.parameters.Items); |
| 43 | + // When the items or layout change, |
| 44 | + // re-render the toolbar to remeasure, i.e. shrink/grow accordingly |
| 45 | + this.componentKey = this.componentKey.concat("_1"); |
| 46 | + } |
| 47 | + |
| 48 | + const props = { |
| 49 | + key: this.componentKey, |
| 50 | + items: this.items.filter((i) => i.visible).slice(0, 2), |
| 51 | + width: allocatedWidth, |
| 52 | + height: allocatedHeight, |
| 53 | + onSelected: this.onSelect, |
| 54 | + disabled: context.mode.isControlDisabled || (context as unknown as ContextEx).mode.isRead, |
| 55 | + title: context.parameters.Title.raw, |
| 56 | + subTitle: context.parameters.Subtitle.raw, |
| 57 | + headerImage: getUrlfromImage(context.parameters.HeaderImage.raw), |
| 58 | + visible: context.mode.isVisible, |
| 59 | + size: Size[context.parameters.Size.raw], |
| 60 | + onResize: this.onResize, |
| 61 | + ariaLabel: context.parameters.AccessibleLabel?.raw ?? "", |
| 62 | + orientation: Orientation[context.parameters.Alignment.raw], |
| 63 | + placePreview: context.parameters.ImagePlacement?.raw ?? StringConstants.AboveHeader, |
| 64 | + accessibleLabel: context.parameters.AccessibleLabel.raw ?? "", |
| 65 | + image: getUrlfromImage(context.parameters.Image.raw), |
| 66 | + description: context.parameters.Description.raw, |
| 67 | + getPopoverRoot: this.getPopoverRoot, |
| 68 | + } as CustomCardProps; |
| 69 | + |
| 70 | + return React.createElement(CardCanvas, props); |
| 71 | + } |
| 72 | + |
| 73 | + private onResize = (height: number): void => { |
| 74 | + this.height = height; |
| 75 | + this.notifyOutputChanged(); |
| 76 | + }; |
| 77 | + |
| 78 | + private onSelect = (item?: IToolbarItem): void => { |
| 79 | + if (item && item.data) { |
| 80 | + this.context.parameters.Items.openDatasetItem(item.data.getNamedReference()); |
| 81 | + } else if (this.items && this.items.length > 0) { |
| 82 | + this.context.parameters.Items.openDatasetItem(this.items[0].data.getNamedReference()); |
| 83 | + } else { |
| 84 | + this.context.events.OnSelect(); |
| 85 | + } |
| 86 | + this.notifyOutputChanged(); |
| 87 | + }; |
| 88 | + |
| 89 | + /** |
| 90 | + * It is called by the framework prior to a control receiving new data. |
| 91 | + * @returns an object based on nomenclature defined in manifest, expecting object[s] for property marked as "bound" or "output" |
| 92 | + */ |
| 93 | + public getOutputs(): IOutputs { |
| 94 | + return { AutoHeight: this.height || parseInt(this.context.mode.allocatedHeight as unknown as string) }; |
| 95 | + } |
| 96 | + |
| 97 | + /** |
| 98 | + * Called when the control is to be removed from the DOM tree. Controls should use this call for cleanup. |
| 99 | + * i.e. cancelling any pending remote calls, removing listeners, etc. |
| 100 | + */ |
| 101 | + public destroy(): void { |
| 102 | + // Add code to cleanup control if necessary |
| 103 | + } |
| 104 | + |
| 105 | + private getPopoverRoot(): HTMLElement { |
| 106 | + // if we can't find the target layer, we fallback to the fluent provider, then finally <body> |
| 107 | + // note: the ID below should always match the one set in Studio's `ThemeWrapperHelper.tsx` |
| 108 | + const root = |
| 109 | + document.querySelector("#__fluentv9popover__") || document.querySelector(".fui-FluentProvider") || document.body; |
| 110 | + return root as HTMLElement; |
| 111 | + } |
| 112 | +} |
0 commit comments