Skip to content

Commit

Permalink
Added new PCF LookupToDropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
MscrmTools committed Dec 13, 2022
1 parent d5c6a66 commit 396f3b7
Show file tree
Hide file tree
Showing 16 changed files with 7,136 additions and 2 deletions.
19 changes: 19 additions & 0 deletions LookupToPicklist/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"env": {
"browser": true,
"es2021": true
},
"extends": [
"standard"
],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 12,
"sourceType": "module"
},
"plugins": [
"@typescript-eslint"
],
"rules": {
}
}
14 changes: 14 additions & 0 deletions LookupToPicklist/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules

# generated directory
**/generated

# output directory
/out

# msbuild output directories
/bin
/obj
46 changes: 46 additions & 0 deletions LookupToPicklist/LookupToPicklist.pcfproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<PowerAppsTargetsPath>$(MSBuildExtensionsPath)\Microsoft\VisualStudio\v$(VisualStudioVersion)\PowerApps</PowerAppsTargetsPath>
</PropertyGroup>

<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" />
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.props')"/>

<PropertyGroup>
<Name>LookupToPicklist</Name>
<ProjectGuid>03248b2e-2d43-4145-8fd3-a31668802ec9</ProjectGuid>
<OutputPath>$(MSBuildThisFileDirectory)out\controls</OutputPath>
</PropertyGroup>

<PropertyGroup>
<TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>
<!--Remove TargetFramework when this is available in 16.1-->
<TargetFramework>net462</TargetFramework>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.PowerApps.MSBuild.Pcf" Version="1.*"/>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
</ItemGroup>

<ItemGroup>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\.gitignore"/>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\bin\**"/>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\obj\**"/>
<ExcludeDirectories Include="$(OutputPath)\**"/>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj"/>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.pcfproj.user"/>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\*.sln"/>
<ExcludeDirectories Include="$(MSBuildThisFileDirectory)\node_modules\**"/>
</ItemGroup>

<ItemGroup>
<None Include="$(MSBuildThisFileDirectory)\**" Exclude="@(ExcludeDirectories)"/>
</ItemGroup>

<Import Project="$(MSBuildToolsPath)\Microsoft.Common.targets" />
<Import Project="$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets" Condition="Exists('$(PowerAppsTargetsPath)\Microsoft.PowerApps.VisualStudio.Pcf.targets')"/>

</Project>
26 changes: 26 additions & 0 deletions LookupToPicklist/LookupToPicklist/ControlManifest.Input.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest>
<control namespace="MscrmTools" constructor="LookupToPicklist" version="0.0.2" display-name-key="Control_Display_Name_Key" description-key="Control_Description_Key" control-type="standard" preview-image="img/LookupToPicklist_preview.png">
<property name="lookup" display-name-key="lookup_Display_Key" description-key="lookup_Desc_Key" of-type="Lookup.Simple" usage="bound" required="true" />
<property name="attributemask" display-name-key="attributemask_Display_Key" description-key="attributemask_Desc_Key" of-type="SingleLine.Text" usage="input" required="false" />
<property name="sortByName" display-name-key="sortByName_Display_Key" description-key="sortByName_Desc_Key" of-type="Enum" default-value="0" usage="input" required="true">
<value name="Yes" display-name-key="yes_Display_Key" description-key="yes_Desc_Key">1</value>
<value name="No" display-name-key="no_Display_Key" description-key="no_Desc_Key">0</value>
</property>
<property name="addNew" display-name-key="addNew_Display_Key" description-key="addNew_Desc_Key" of-type="Enum" default-value="0" usage="input" required="true">
<value name="Yes" display-name-key="yes_Display_Key" description-key="yes_Desc_Key">1</value>
<value name="No" display-name-key="no_Display_Key" description-key="no_Desc_Key">0</value>
</property>
<resources>
<code path="index.ts" order="1" />
<resx path="strings/LookupToPicklist.1036.resx" version="1.0.0" />
<resx path="strings/LookupToPicklist.1033.resx" version="1.0.0" />
</resources>
<external-service-usage enabled="false">
</external-service-usage>
<feature-usage>
<uses-feature name="Utility" required="true" />
<uses-feature name="WebAPI" required="true" />
</feature-usage>
</control>
</manifest>
84 changes: 84 additions & 0 deletions LookupToPicklist/LookupToPicklist/RecordSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import * as React from 'react';
import { Dropdown, IDropdownOption, IDropdownStyleProps, IDropdownStyles } from '@fluentui/react/lib/Dropdown';
import { Icon } from '@fluentui/react/lib/Icon';

export interface IRecordSelectorProps {
selectedRecordId: string | undefined;
availableOptions: IDropdownOption[];
isDisabled: boolean;
onChange: (selectedOption?: IDropdownOption) => void;
}

export const RecordSelector: React.FunctionComponent<IRecordSelectorProps> = props => {
return (
<Dropdown
selectedKey={props.selectedRecordId}
options={props.availableOptions}
disabled={props.isDisabled}
onChange={(e: any, option?: IDropdownOption) => {
props.onChange(option);
}}
styles={DropdownStyle}
onRenderOption={(option): JSX.Element=>{
if(option?.data && option.data.isMenu && option.data.icon){
return (
<div>
<Icon style={iconStyles} iconName={option.data.icon} aria-hidden="true" title={option.data.icon} />
<span style={italicStyle}>{option?.text}</span>
</div>
);
}
else{
return (
<div><span>{option?.text}</span>
</div>
);
}
}}
/>);
}
const iconStyles = { marginRight: '8px' };
const italicStyle = { fontStyle: 'italic', align:'right' };

export const DropdownStyle = (props: IDropdownStyleProps): Partial<IDropdownStyles> => ({
...(props.disabled ? {
root: {
width: "100%"
},
title: {
color: "rgb(50, 49, 48)",
borderColor: "transparent",
backgroundColor: "transparent",
fontWeight: 600,
":hover": {
backgroundColor: "rgb(226, 226, 226)"
}
},
caretDown: {
color: "transparent"
}
}: {
root: {
width: "100%"
},
title: {
borderColor: "transparent",
fontWeight: 600,
":hover": {
borderColor: "rgb(96, 94, 92)",
fontWeight: 400
}
},
caretDown: {
color: "transparent",
":hover": {
color: "rgb(96, 94, 92)"
}
},
dropdown: {
":focus:after": {
borderColor: "transparent"
}
}
})
});
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
155 changes: 155 additions & 0 deletions LookupToPicklist/LookupToPicklist/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { IInputs, IOutputs } from './generated/ManifestTypes'
import * as React from 'react'
import * as ReactDom from 'react-dom'
import { RecordSelector } from './RecordSelector'
import { DropdownMenuItemType, IDropdownOption } from '@fluentui/react/lib/Dropdown'

export class LookupToPicklist implements ComponentFramework.StandardControl<IInputs, IOutputs> {

private _context: ComponentFramework.Context<IInputs>;
private container: HTMLDivElement;
private notifyOutputChanged: () => void;
private entityName: string;
private entityIdFieldName: string;
private entityNameFieldName: string;
private entityDisplayName: string;
private viewId: string;
private availableOptions: IDropdownOption[];
private actionOptions: IDropdownOption[];
private currentValue?: ComponentFramework.LookupValue[];

constructor() {
}

public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement): void {
this._context = context;
this.container = container
this.notifyOutputChanged = notifyOutputChanged

this.entityName = context.parameters.lookup.getTargetEntityType()
this.viewId = context.parameters.lookup.getViewId();
var self = this;

context.utils.getEntityMetadata(this.entityName).then(metadata => {

self.entityIdFieldName = metadata.PrimaryIdAttribute
self.entityNameFieldName = metadata.PrimaryNameAttribute
self.entityDisplayName = metadata.DisplayName;

// Add items to add a new record
self.actionOptions = new Array<IDropdownOption>();
self.actionOptions.push({
key:"divider",
text:"",
itemType: DropdownMenuItemType.Divider
});
self.actionOptions.push({
key:"new",
text:context.resources.getString("AddNew_Display_Key") + " " + self.entityDisplayName,
itemType: DropdownMenuItemType.Normal,
data:{icon:"Add", isMenu:true}
});

self.retrieveRecords();
})
}

private retrieveRecords(){
var thisCtrl = this;

thisCtrl._context.webAPI.retrieveRecord('savedquery', this.viewId, "?$select=fetchxml,returnedtypecode").then(view =>{
thisCtrl._context.webAPI.retrieveMultipleRecords(view.returnedtypecode, '?fetchXml=' + view.fetchxml).then(result => {
thisCtrl.availableOptions = result.entities.map(r => {
let localizedEntityFieldName = ''
let mask = thisCtrl._context.parameters.attributemask.raw

if(mask){
localizedEntityFieldName = mask.replace('{lcid}', thisCtrl._context.userSettings.languageId.toString())
}

return {
key: r[thisCtrl.entityIdFieldName],
text: (r[localizedEntityFieldName] ? r[localizedEntityFieldName] : r[thisCtrl.entityNameFieldName]) ?? 'Display Name is not available'
}
});

this.renderControl(thisCtrl._context)
})
})
}

public updateView(context: ComponentFramework.Context<IInputs>): void {
this.renderControl(context);
}

private renderControl(context: ComponentFramework.Context<IInputs>) {
var thisCtrl = this;
const recordId = context.parameters.lookup.raw != null && context.parameters.lookup.raw.length > 0
? context.parameters.lookup.raw[0].id
: '---'

if(context.parameters.sortByName.raw === "1"){
this.availableOptions = this.availableOptions.sort((n1,n2) => {
if (n1.text > n2.text) {
return 1;
}

if (n1.text < n2.text) {
return -1;
}

return 0;
})
}

let options = [{key: '---', text:'---'},...this.availableOptions];
if(thisCtrl._context.parameters.addNew.raw === "1")
{
// If rights to read and create entity
if(thisCtrl._context.utils.hasEntityPrivilege(thisCtrl.entityName, 1, 0)
&& thisCtrl._context.utils.hasEntityPrivilege(thisCtrl.entityName, 2, 0)){
options.push(...thisCtrl.actionOptions);
}
}

const recordSelector = React.createElement(RecordSelector, {
selectedRecordId: recordId,
availableOptions: options,
isDisabled: context.mode.isControlDisabled,
onChange: (selectedOption?: IDropdownOption) => {
if(selectedOption?.key === "new"){
context.navigation.openForm({
entityName : this.entityName,
useQuickCreateForm: true,
windowPosition: 2
}).then(()=>{
thisCtrl.retrieveRecords();
});
}
else if (typeof selectedOption === 'undefined' || selectedOption.key === '---') {
this.currentValue = undefined
} else {
this.currentValue = [{
id: <string>selectedOption.key,
name: selectedOption.text,
entityType: this.entityName
}]
}

this.notifyOutputChanged();
}
})

ReactDom.render(recordSelector, this.container);
}

public getOutputs(): IOutputs {
return {
lookup: this.currentValue
}
}

public destroy(): void {
ReactDom.unmountComponentAtNode(this.container)
}
}
Loading

0 comments on commit 396f3b7

Please sign in to comment.