Skip to content

Commit

Permalink
added language pack support (#96)
Browse files Browse the repository at this point in the history
* added language pack

* added build process in readme
  • Loading branch information
khoait authored Feb 8, 2024
1 parent 892a082 commit ee6271e
Show file tree
Hide file tree
Showing 17 changed files with 583 additions and 49 deletions.
2 changes: 1 addition & 1 deletion LookdownComponent/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll": "explicit"
}
}
7 changes: 4 additions & 3 deletions LookdownComponent/Lookdown/ControlManifest.Input.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?>
<manifest>
<control namespace="DCEPCF" constructor="Lookdown" version="1.1.0" display-name-key="Lookdown v1.1.0" description-key="Display a lookup control as a dropdown" control-type="virtual" >
<control namespace="DCEPCF" constructor="Lookdown" version="1.2.0" display-name-key="Lookdown v1.2.0" description-key="Display a lookup control as a dropdown" control-type="virtual" >
<external-service-usage enabled="false">
<!--UNCOMMENT TO ADD EXTERNAL DOMAINS
<domain></domain>
Expand All @@ -9,7 +9,7 @@
</external-service-usage>
<!-- property node identifies a specific, configurable piece of data that the control expects from CDS -->
<property name="lookupField" display-name-key="Lookup Field" description-key="The lookup field that the component is bound to." of-type="Lookup.Simple" usage="bound" required="true" />
<property name="customFilter" display-name-key="Custom Filter" description-key="Additional filter to be added to the Lookup view. Custom filter should be serialized as a FetchXML <filter> node." of-type-group="text" usage="input" required="false" />
<property name="customFilter" display-name-key="Custom Filter" description-key="Additional filter to be added to the Lookup view. Custom filter should be serialized as a FetchXML filter node." of-type-group="text" usage="input" required="false" />
<property name="groupByField" display-name-key="Group By Field" description-key="Group the options dropdown by this field." of-type="SingleLine.Text" usage="input" required="false" />
<property name="optionTemplate" display-name-key="Option Display Template" description-key="Dynamic template to display option text." of-type="SingleLine.Text" usage="input" required="false" />
<property name="selectedItemTemplate" display-name-key="Selected Option Display Template" description-key="Dynamic template to display selected option text. Default to option text if not provided." of-type="SingleLine.Text" usage="input" required="false" />
Expand All @@ -35,6 +35,7 @@
<value name="no" display-name-key="No">0</value>
<value name="yes" display-name-key="Yes">1</value>
</property>
<property name="languagePackPath" display-name-key="Language Pack Path" description-key="Path to a resx web resource, relative to the environment url (eg. /webresources/new_/resx/Lookdown.1033.resx), to be used as Language Pack. Default to English if not provided." of-type="SingleLine.Text" usage="input" required="false" />
<type-group name="text">
<type>SingleLine.Text</type>
<type>SingleLine.TextArea</type>
Expand All @@ -46,8 +47,8 @@
<platform-library name="Fluent" version="8.29.0" />
<!-- UNCOMMENT TO ADD MORE RESOURCES
<css path="css/Lookdown.css" order="1" />
<resx path="strings/Lookdown.1033.resx" version="1.0.0" />
-->
<resx path="strings/Lookdown.1033.resx" version="1.0.0" />
</resources>
<!-- UNCOMMENT TO ENABLE THE SPECIFIED API
<feature-usage>
Expand Down
41 changes: 25 additions & 16 deletions LookdownComponent/Lookdown/components/LookdownControl.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import React from "react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import {
Dropdown,
DropdownMenuItemType,
IStyle,
IContextualMenuItemStyles,
IContextualMenuProps,
IDropdownOption,
Stack,
Image,
Label,
ImageFit,
ILabelStyles,
IIconProps,
IContextualMenuProps,
ILabelStyles,
IStyle,
IconButton,
IContextualMenuItemStyles,
Image,
ImageFit,
Label,
Stack,
} from "@fluentui/react";
import { getCurrentRecord, useFetchXmlData, useMetadata } from "../services/DataverseService";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import Handlebars from "handlebars";
import React from "react";
import { getCurrentRecord, useFetchXmlData, useLanguagePack, useMetadata } from "../services/DataverseService";
import { LanguagePack } from "../types/languagePack";

const queryClient = new QueryClient();

Expand Down Expand Up @@ -51,6 +52,8 @@ export interface LookdownProps {
allowQuickCreate?: boolean;
allowLookupPanel?: boolean;
disabled?: boolean;
defaultLanguagePack: LanguagePack;
languagePackPath?: string;
onChange?: (selectedItem: ComponentFramework.LookupValue | null) => void;
}

Expand Down Expand Up @@ -115,8 +118,14 @@ const Body = ({
allowQuickCreate,
allowLookupPanel,
disabled,
defaultLanguagePack,
languagePackPath,
onChange,
}: LookdownProps) => {
const { data: loadedLanguagePack } = useLanguagePack(languagePackPath, defaultLanguagePack);

const languagePack = loadedLanguagePack ?? defaultLanguagePack;

const {
data: metadata,
isLoading: isLoadingMetadata,
Expand Down Expand Up @@ -152,7 +161,7 @@ const Body = ({
if (groupBy) {
const grouped: Record<string, ComponentFramework.WebApi.Entity[]> = {};
fetchData.forEach((item) => {
const key = item[groupBy]?.toString() ?? "(Blank)";
const key = item[groupBy]?.toString() ?? languagePack.BlankValueLabel;

if (!grouped[key]) grouped[key] = [];
grouped[key].push(item);
Expand Down Expand Up @@ -190,7 +199,7 @@ const Body = ({
if (options.length === 0) {
options.push({
key: "no-records",
text: "No records found",
text: languagePack.EmptyListMessage,
disabled: true,
});
}
Expand Down Expand Up @@ -353,23 +362,23 @@ const Body = ({
items: [
{
key: "open-record",
text: "Open record",
text: languagePack.OpenRecordLabel,
iconProps: { iconName: "OpenInNewWindow" },
itemProps: { styles: !openRecordMode ? hiddenCommandStyle : visibleCommandStyle },
disabled: !selectedId,
onClick: onOpenRecordCommandClick,
},
{
key: "quick-create",
text: "Quick create",
text: languagePack.QuickCreateLabel,
itemProps: { styles: !allowQuickCreate ? hiddenCommandStyle : visibleCommandStyle },
iconProps: { iconName: "Add" },
disabled: !allowQuickCreate,
onClick: onQuickCreateCommandClick,
},
{
key: "lookup-panel",
text: "Lookup panel",
text: languagePack.LookupPanelLabel,
itemProps: { styles: !allowLookupPanel ? hiddenCommandStyle : visibleCommandStyle },
iconProps: { iconName: "LookupEntities" },
disabled: !allowLookupPanel,
Expand Down
14 changes: 12 additions & 2 deletions LookdownComponent/Lookdown/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as React from "react";
import LookdownControl, { IconSizes, OpenRecordMode, ShowIconOptions } from "./components/LookdownControl";
import { IInputs, IOutputs } from "./generated/ManifestTypes";
import * as React from "react";
import { LanguagePack } from "./types/languagePack";

export class Lookdown implements ComponentFramework.ReactControl<IInputs, IOutputs> {
private theComponent: ComponentFramework.ReactControl<IInputs, IOutputs>;
private notifyOutputChanged: () => void;
private output: ComponentFramework.LookupValue | null;
private context: ComponentFramework.Context<IInputs>;
private languagePack: LanguagePack;

/**
* Empty constructor.
Expand All @@ -28,7 +30,13 @@ export class Lookdown implements ComponentFramework.ReactControl<IInputs, IOutpu
): void {
this.notifyOutputChanged = notifyOutputChanged;
this.context = context;
console.log("init", context);
this.languagePack = {
BlankValueLabel: context.resources.getString("BlankValueLabel"),
EmptyListMessage: context.resources.getString("EmptyListMessage"),
OpenRecordLabel: context.resources.getString("OpenRecordLabel"),
QuickCreateLabel: context.resources.getString("QuickCreateLabel"),
LookupPanelLabel: context.resources.getString("LookupPanelLabel"),
};
}

/**
Expand Down Expand Up @@ -61,6 +69,8 @@ export class Lookdown implements ComponentFramework.ReactControl<IInputs, IOutpu
allowQuickCreate: context.parameters.commandQuickCreate?.raw === "1",
allowLookupPanel: context.parameters.commandQuickCreate?.raw === "1",
disabled: context.mode.isControlDisabled,
defaultLanguagePack: this.languagePack,
languagePackPath: context.parameters.languagePackPath?.raw ?? undefined,
onChange: (value) => {
this.output = value;
this.notifyOutputChanged();
Expand Down
45 changes: 41 additions & 4 deletions LookdownComponent/Lookdown/services/DataverseService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios from "axios";
import { useQuery } from "@tanstack/react-query";
import axios from "axios";
import { LanguagePack } from "../types/languagePack";
import { IEntityDefinition, IMetadata, IViewDefinition, IViewLayout } from "../types/metadata";
import Handlebars from "handlebars";

const tableDefinitionColumns = [
"LogicalName",
Expand All @@ -27,7 +27,7 @@ export function useMetadata(
entityIcon = false
) {
return useQuery({
queryKey: [`${lookupTable}_${lookupViewId}_metadata`, { lookupTable, lookupViewId }],
queryKey: ["metadata", lookupTable, lookupViewId],
queryFn: () => getMetadata(lookupTable ?? "", lookupViewId ?? ""),
enabled: !!lookupTable && !!lookupViewId,
});
Expand All @@ -41,14 +41,51 @@ export function useFetchXmlData(
groupBy?: string | null
) {
return useQuery({
queryKey: [`${entitySetName}_${lookupViewId}_fetchdata`, { entitySetName, lookupViewId, fetchXml }],
queryKey: ["fetchdata", entitySetName, lookupViewId, fetchXml],
queryFn: () => {
return retrieveMultipleFetch(entitySetName ?? "", fetchXml ?? "", groupBy);
},
enabled: !!entitySetName && !!lookupViewId && !!fetchXml,
});
}

export function useLanguagePack(webResourcePath: string | undefined, defaultLanguagePack: LanguagePack) {
return useQuery({
queryKey: ["languagePack", webResourcePath],
queryFn: () => getLanguagePack(webResourcePath, defaultLanguagePack),
enabled: !!webResourcePath,
});
}

export function getLanguagePack(
webResourceUrl: string | undefined,
defaultLanguagePack: LanguagePack
): Promise<LanguagePack> {
const languagePack: LanguagePack = { ...defaultLanguagePack };

if (webResourceUrl === undefined) return Promise.resolve(languagePack);

return axios
.get(webResourceUrl)
.then((res) => {
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(res.data, "text/xml");

const nodes = xmlDoc.getElementsByTagName("data");
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const key = node.getAttribute("name");
const value = node.getElementsByTagName("value")[0].childNodes[0].nodeValue ?? "";
if (key && value) {
languagePack[key as keyof LanguagePack] = value;
}
}

return languagePack;
})
.catch(() => languagePack);
}

async function getMetadata(lookupTable: string, lookupViewId: string): Promise<IMetadata> {
const [lookupEntity, lookupView] = await Promise.all([
getEntityDefinition(lookupTable),
Expand Down
Loading

0 comments on commit ee6271e

Please sign in to comment.