-
Power Apps 코드 구성 요소(PCF) - Dataverse API 사용Power Platform/Power Apps 2022. 3. 24. 12:31728x90
Power Apps 코드 구성요소 - Dataverse API 사용
Power Apps 코드 구성요소는 캔버스 앱 뿐만 아니라 Dynamics 365에서 데이터 요소에서 기본적으로 제공하는 컨트롤(텍스트,날짜 입력 등)을 대체하여 넣을 수 있다. 여기서는 기본으로 제공하는 테이블 '거래처' 에서 연간 매출을 자동으로 입력된 행을 만드는 컨트롤을 만들어보자
코드 구성요소 초기화
- 다음 명령을 파워쉘에서 실행해 초기화된 프로젝트를 만든다.
```PowerShell pac pcf init --namespace SampleNamespace --name TSWebAPI --template field ```
- 다음 명령어를 입력하여 프로젝트에 종속 라이브러리를 로드한다.
```powershell npm install ```
코드 구성요소 로직 구현
- 코드 구성 요소의 매니페스트 파일(ControlManifest.Input.xml)을 열고 다음과 같이 바꾼다.
```xml <?xml version="1.0" encoding="utf-8"?> <manifest> <control namespace="SampleNamespace" constructor="TSWebAPI" version="1.0.0" display-name-key="TS_WebAPI_Display_Key" description-key="TS_WebAPI_Desc_Display_Key" control-type="standard"> <property name="stringProperty" display-name-key="stringProperty_Display_Key" description-key="stringProperty_Desc_Key" of-type="SingleLine.Text" usage="bound" required="true" /> <resources> <code path="index.ts" order="1" /> <css path="css/TS_WebAPI.css" order="2" /> </resources> <feature-usage> <uses-feature name="WebAPI" required="true" /> </feature-usage> </control> </manifest> ```
- index.ts 파일을 연다.
- 클래스 내부 최상단에 컨트롤로 적용할 다음 private 변수를 삽입
```ts // Reference to the control container HTMLDivElement // This element contains all elements of our custom control example private _container: HTMLDivElement; // Reference to ComponentFramework Context object private _context: ComponentFramework.Context<IInputs>; // Flag if control view has been rendered private _controlViewRendered: Boolean; // References to button elements that are rendered by example custom control private _createEntity1Button: HTMLButtonElement; private _createEntity2Button: HTMLButtonElement; private _createEntity3Button: HTMLButtonElement; private _deleteRecordButton: HTMLButtonElement; private _fetchXmlRefreshButton: HTMLButtonElement; private _oDataRefreshButton: HTMLButtonElement; // References to div elements that are rendered by the example custom control private _odataStatusContainerDiv: HTMLDivElement; private _resultContainerDiv: HTMLDivElement; ```
- 다음 테이블의 이름,열을 나타내는. private 변수를 추가로 삽입
```ts // Name of entity to use for example Web API calls that are performed by this control private static _entityName: string = "account"; // Required field on _entityName of type 'single line of text' // Example Web API calls that are performed by the example custom control will set this field for new record creation examples private static _requiredAttributeName: string = "name"; // Value that the _requiredAttributeName field will be set to for new created records private static _requiredAttributeValue: string = "Web API Custom Control (Sample)"; // Name of currency field on _entityName to populate during record create // Example Web API calls that are performed by the example custom control will set and read this field private static _currencyAttributeName: string = "revenue"; // Friendly name of currency field (only used for control UI - no functional impact) private static _currencyAttributeNameFriendlyName: string = "annual revenue"; ```
- 다음 로직을 init 함수 안에 삽입
this._context = context; this._controlViewRendered = false; this._container = document.createElement("div"); this._container.classList.add("TSWebAPI_Container"); container.appendChild(this._container);
- updateView 함수안에 다음 로직 추가
if (!this._controlViewRendered) { this._controlViewRendered = true; // Render Web API Examples this.renderCreateExample(); this.renderDeleteExample(); this.renderFetchXmlRetrieveMultipleExample(); this.renderODataRetrieveMultipleExample(); // Render result div to display output of Web API calls this.renderResultsDiv();}
- html을 랜더링하기위해 다음 함수들을 추가
```ts /** * Helper method to create HTML button that is used for CreateRecord Web API Example * @param buttonLabel : Label for button * @param buttonId : ID for button * @param buttonValue : Value of button (attribute of button) * @param onClickHandler : onClick event handler to invoke for the button */ private createHTMLButtonElement(buttonLabel: string, buttonId: string, buttonValue: string | null, onClickHandler: (event?: any) => void): HTMLButtonElement { let button: HTMLButtonElement = document.createElement("button"); button.innerHTML = buttonLabel; if (buttonValue) { button.setAttribute("buttonvalue", buttonValue); } button.id = buttonId; button.classList.add("SampleControl_WebAPI_ButtonClass"); button.addEventListener("click", onClickHandler); return button; } /** * Helper method to create HTML Div Element * @param elementClassName : Class name of div element * @param isHeader : True if 'header' div - adds extra class and post-fix to ID for header elements * @param innerText : innerText of Div Element */ private createHTMLDivElement(elementClassName: string, isHeader: Boolean, innerText?: string): HTMLDivElement { let div: HTMLDivElement = document.createElement("div"); if (isHeader) { div.classList.add("SampleControl_WebAPI_Header"); elementClassName += "_header"; } if (innerText) { div.innerText = innerText.toUpperCase(); } div.classList.add(elementClassName); return div; } /** * Renders a 'result container' div element to inject the status of the example Web API calls */ private renderResultsDiv() { // Render header label for result container let resultDivHeader: HTMLDivElement = this.createHTMLDivElement("result_container", true, "Result of last action"); this._container.appendChild(resultDivHeader); // Div elements to populate with the result text this._resultContainerDiv = this.createHTMLDivElement("result_container", false, undefined); this._container.appendChild(this._resultContainerDiv); // Init the result container with a notification that the control was loaded this.updateResultContainerText("Web API sample custom control loaded"); } /** * Helper method to inject HTML into result container div * @param statusHTML : HTML to inject into result container */ private updateResultContainerText(statusHTML: string): void { if (this._resultContainerDiv) { this._resultContainerDiv.innerHTML = statusHTML; } } /** * Helper method to inject error string into result container div after failed Web API call * @param errorResponse : error object from rejected promise */ private updateResultContainerTextWithErrorResponse(errorResponse: any): void { if (this._resultContainerDiv) { // Retrieve the error message from the errorResponse and inject into the result div let errorHTML: string = "Error with Web API call:"; errorHTML += "<br />" errorHTML += errorResponse.message; this._resultContainerDiv.innerHTML = errorHTML; } } /** * Helper method to generate Label for Create Buttons * @param entityNumber : value to set _currencyAttributeNameFriendlyName field to for this button */ private getCreateRecordButtonLabel(entityNumber: string): string { return "Create record with " + TSWebAPI._currencyAttributeNameFriendlyName + " of " + entityNumber; } /** * Helper method to generate ID for Create button * @param entityNumber : value to set _currencyAttributeNameFriendlyName field to for this button */ private getCreateButtonId(entityNumber: string): string { return "create_button_" + entityNumber; } ```
- 다음 oncClick 이벤트 핸들러를 추가해서 CURD 작업을 실행하도록 만든다
```ts /** * Event Handler for onClick of create record button * @param event : click event */ private createButtonOnClickHandler(event: Event): void { // Retrieve the value to set the currency field to from the button's attribute const currencyAttributeValue: number = parseInt( (event.target as Element)?.attributes?.getNamedItem("buttonvalue")?.value ?? "0" ); // Generate unique record name by appending timestamp to _requiredAttributeValue const recordName = `${TSWebAPI._requiredAttributeValue}_${Date.now()}`; // Set the values for the attributes we want to set on the new record // If you want to set additional attributes on the new record, add to data dictionary as key/value pair const data: ComponentFramework.WebApi.Entity = {}; data[TSWebAPI._requiredAttributeName] = recordName; data[TSWebAPI._currencyAttributeName] = currencyAttributeValue; // Invoke the Web API to creat the new record this._context.webAPI.createRecord(TSWebAPI._entityName, data).then( (response: ComponentFramework.LookupValue) => { // Callback method for successful creation of new record // Get the ID of the new record created const id: string = response.id; // Generate HTML to inject into the result div to showcase the fields and values of the new record created let resultHtml = `Created new ${ TSWebAPI._entityName } record with below values:`; resultHtml += "<br />"; resultHtml += "<br />"; resultHtml += `id: ${id}`; resultHtml += "<br />"; resultHtml += "<br />"; resultHtml += `${TSWebAPI._requiredAttributeName}: ${recordName}`; resultHtml += "<br />"; resultHtml += "<br />"; resultHtml += `${TSWebAPI._currencyAttributeName}: ${currencyAttributeValue}`; this.updateResultContainerText(resultHtml); }, (errorResponse) => { // Error handling code here - record failed to be created this.updateResultContainerTextWithErrorResponse(errorResponse); } ); } /** * Event Handler for onClick of delete record button * @param event : click event */ private deleteButtonOnClickHandler(): void { // Invoke a lookup dialog to allow the user to select an existing record of type _entityName to delete const lookUpOptions: any = { entityTypes: [TSWebAPI._entityName] }; const lookUpPromise = this._context.utils.lookupObjects(lookUpOptions); lookUpPromise.then( // Callback method - invoked after user has selected an item from the lookup dialog // Data parameter is the item selected in the lookup dialog (data: ComponentFramework.LookupValue[]) => { if (data && data[0]) { // Get the ID and entityType of the record selected by the lookup const id: string = data[0].id; const entityType: string = data[0].entityType; // Invoke the deleteRecord method of the WebAPI to delete the selected record this._context.webAPI.deleteRecord(entityType, id).then( (response: ComponentFramework.LookupValue) => { // Record was deleted successfully const responseId: string = response.id; const responseEntityType: string = response.entityType; // Generate HTML to inject into the result div to showcase the deleted record this.updateResultContainerText(`Deleted ${responseEntityType} record with ID: ${responseId}`); }, (errorResponse) => { // Error handling code here this.updateResultContainerTextWithErrorResponse(errorResponse); } ); } }, (error) => { // Error handling code here this.updateResultContainerTextWithErrorResponse(error); } ); } /** * Event Handler for onClick of calculate average value button * @param event : click event */ private calculateAverageButtonOnClickHandler(): void { // Build FetchXML to retrieve the average value of _currencyAttributeName field for all _entityName records // Add a filter to only aggregate on records that have _currencyAttributeName not set to null let fetchXML = "<fetch distinct='false' mapping='logical' aggregate='true'>"; fetchXML += `<entity name='${TSWebAPI._entityName}'>`; fetchXML += `<attribute name='${TSWebAPI._currencyAttributeName}' aggregate='avg' alias='average_val' />`; fetchXML += "<filter>"; fetchXML += `<condition attribute='${TSWebAPI._currencyAttributeName}' operator='not-null' />`; fetchXML += "</filter>"; fetchXML += "</entity>"; fetchXML += "</fetch>"; // Invoke the Web API RetrieveMultipleRecords method to calculate the aggregate value this._context.webAPI.retrieveMultipleRecords(TSWebAPI._entityName, `?fetchXml=${ fetchXML}`).then( (response: ComponentFramework.WebApi.RetrieveMultipleResponse) => { // Retrieve multiple completed successfully -- retrieve the averageValue const averageVal: number = response.entities[0].average_val; // Generate HTML to inject into the result div to showcase the result of the RetrieveMultiple Web API call const resultHTML = `Average value of ${TSWebAPI._currencyAttributeNameFriendlyName} attribute for all ${TSWebAPI._entityName} records: ${averageVal}`; this.updateResultContainerText(resultHTML); }, (errorResponse) => { // Error handling code here this.updateResultContainerTextWithErrorResponse(errorResponse); } ); } /** * Event Handler for onClick of calculate record count button * @param event : click event */ private refreshRecordCountButtonOnClickHandler(): void { // Generate OData query string to retrieve the _currencyAttributeName field for all _entityName records // Add a filter to only retrieve records with _requiredAttributeName field which contains _requiredAttributeValue const queryString = `?$select=${TSWebAPI._currencyAttributeName }&$filter=contains(${TSWebAPI._requiredAttributeName},'${TSWebAPI._requiredAttributeValue}')`; // Invoke the Web API Retrieve Multiple call this._context.webAPI.retrieveMultipleRecords(TSWebAPI._entityName, queryString).then( (response: ComponentFramework.WebApi.RetrieveMultipleResponse) => { // Retrieve Multiple Web API call completed successfully let count1 = 0; let count2 = 0; let count3 = 0; // Loop through each returned record for (const entity of response.entities) { // Retrieve the value of _currencyAttributeName field const value: number = entity[TSWebAPI._currencyAttributeName]; // Check the value of _currencyAttributeName field and increment the correct counter if (value == 100) { count1++; } else if (value == 200) { count2++; } else if (value == 300) { count3++; } } // Generate HTML to inject into the fetch xml status div to showcase the results of the OData retrieve example let innerHtml = "Use above buttons to create or delete a record to see count update"; innerHtml += "<br />"; innerHtml += "<br />"; innerHtml += `Count of ${TSWebAPI._entityName} records with ${TSWebAPI._currencyAttributeName} of 100: ${count1}`; innerHtml += "<br />"; innerHtml += `Count of ${TSWebAPI._entityName} records with ${TSWebAPI._currencyAttributeName} of 200: ${count2}`; innerHtml += "<br />"; innerHtml += `Count of ${TSWebAPI._entityName} records with ${TSWebAPI._currencyAttributeName} of 300: ${count3}`; // Inject the HTML into the fetch xml status div if (this._odataStatusContainerDiv) { this._odataStatusContainerDiv.innerHTML = innerHtml; } // Inject a success message into the result div this.updateResultContainerText("Record count refreshed"); }, (errorResponse) => { // Error handling code here this.updateResultContainerTextWithErrorResponse(errorResponse); } ); } ```
- 다음 함수를 추가하여 CURD작업의 결과를 랜더링
```ts /** * Renders example use of CreateRecord Web API */ private renderCreateExample() { // Create header label for Web API sample let headerDiv: HTMLDivElement = this.createHTMLDivElement("create_container", true, "Click to create " + TSWebAPI._entityName + " record"); this._container.appendChild(headerDiv); // Create button 1 to create a record with the revenue field set to 100 let value1: string = "100"; this._createEntity1Button = this.createHTMLButtonElement( this.getCreateRecordButtonLabel(value1), this.getCreateButtonId(value1), value1, this.createButtonOnClickHandler.bind(this)); // Create button 2 to create a record with the revenue field set to 200 let value2: string = "200"; this._createEntity2Button = this.createHTMLButtonElement( this.getCreateRecordButtonLabel(value2), this.getCreateButtonId(value2), value2, this.createButtonOnClickHandler.bind(this)); // Create button 3 to create a record with the revenue field set to 300 let value3: string = "300"; this._createEntity3Button = this.createHTMLButtonElement( this.getCreateRecordButtonLabel(value3), this.getCreateButtonId(value3), value3, this.createButtonOnClickHandler.bind(this)); // Append all button HTML elements to custom control container div this._container.appendChild(this._createEntity1Button); this._container.appendChild(this._createEntity2Button); this._container.appendChild(this._createEntity3Button); } /** * Renders example use of DeleteRecord Web API */ private renderDeleteExample(): void { // Create header label for Web API sample let headerDiv: HTMLDivElement = this.createHTMLDivElement("delete_container", true, "Click to delete " + TSWebAPI._entityName + " record"); // Render button to invoke DeleteRecord Web API call this._deleteRecordButton = this.createHTMLButtonElement( "Select record to delete", "delete_button", null, this.deleteButtonOnClickHandler.bind(this)); // Append elements to custom control container div this._container.appendChild(headerDiv); this._container.appendChild(this._deleteRecordButton); } /** * Renders example use of RetrieveMultiple Web API with OData */ private renderODataRetrieveMultipleExample(): void { let containerClassName: string = "odata_status_container"; // Create header label for Web API sample let statusDivHeader: HTMLDivElement = this.createHTMLDivElement(containerClassName, true, "Click to refresh record count"); this._odataStatusContainerDiv = this.createHTMLDivElement(containerClassName, false, undefined); // Create button to invoke OData RetrieveMultiple Example this._fetchXmlRefreshButton = this.createHTMLButtonElement( "Refresh record count", "odata_refresh", null, this.refreshRecordCountButtonOnClickHandler.bind(this)); // Append HTML elements to custom control container div this._container.appendChild(statusDivHeader); this._container.appendChild(this._odataStatusContainerDiv); this._container.appendChild(this._fetchXmlRefreshButton); } /** * Renders example use of RetrieveMultiple Web API with Fetch XML */ private renderFetchXmlRetrieveMultipleExample(): void { let containerName: string = "fetchxml_status_container"; // Create header label for Web API sample let statusDivHeader: HTMLDivElement = this.createHTMLDivElement(containerName, true, "Click to calculate average value of " + TSWebAPI._currencyAttributeNameFriendlyName); let statusDiv: HTMLDivElement = this.createHTMLDivElement(containerName, false, undefined); // Create button to invoke Fetch XML RetrieveMultiple Web API example this._oDataRefreshButton = this.createHTMLButtonElement( "Calculate average value of " + TSWebAPI._currencyAttributeNameFriendlyName, "odata_refresh", null, this.calculateAverageButtonOnClickHandler.bind(this)); // Append HTML Elements to custom control container div this._container.appendChild(statusDivHeader); this._container.appendChild(statusDiv); this._container.appendChild(this._oDataRefreshButton); } ```
- 모든 작업이 완료되면 소스는 다음과 비슷할 것이다.
```ts import {IInputs, IOutputs} from "./generated/ManifestTypes"; export class TSWebAPI implements ComponentFramework.StandardControl<IInputs, IOutputs> { // Reference to the control container HTMLDivElement // This element contains all elements of our custom control example private _container: HTMLDivElement; // Reference to ComponentFramework Context object private _context: ComponentFramework.Context<IInputs>; // Name of entity to use for example Web API calls performed by this control private static _entityName = "account"; // Required field on _entityName of type 'single line of text' // Example Web API calls performed by example custom control will set this field for new record creation examples private static _requiredAttributeName = "name"; // Value the _requiredAttributeName field will be set to for new created records private static _requiredAttributeValue = "Web API Custom Control (Sample)"; // Name of currency field on _entityName to populate during record create // Example Web API calls performed by example custom control will set and read this field private static _currencyAttributeName = "revenue"; // Friendly name of currency field (only used for control UI - no functional impact) private static _currencyAttributeNameFriendlyName = "annual revenue"; // Flag if control view has been rendered private _controlViewRendered: boolean; // References to button elements rendered by example custom control private _createEntity1Button: HTMLButtonElement; private _createEntity2Button: HTMLButtonElement; private _createEntity3Button: HTMLButtonElement; private _deleteRecordButton: HTMLButtonElement; private _fetchXmlRefreshButton: HTMLButtonElement; private _oDataRefreshButton: HTMLButtonElement; // References to div elements rendered by the example custom control private _odataStatusContainerDiv: HTMLDivElement; private _resultContainerDiv: HTMLDivElement; /** * 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): void { this._context = context; this._controlViewRendered = false; this._container = document.createElement("div"); this._container.classList.add("TSWebAPI_Container"); container.appendChild(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._context = context; if (!this._controlViewRendered) { this._controlViewRendered = true; // Render Web API Examples this.renderCreateExample(); this.renderDeleteExample(); this.renderFetchXmlRetrieveMultipleExample(); this.renderODataRetrieveMultipleExample(); // Render result div to display output of Web API calls this.renderResultsDiv(); } } /** * 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 { // no-op: method not leveraged by this example custom control return {}; } /** * 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 { // no-op: method not leveraged by this example custom control } /** * Renders example use of CreateRecord Web API */ private renderCreateExample() { // Create header label for Web API sample const headerDiv: HTMLDivElement = this.createHTMLDivElement("create_container", true, `Click to create ${TSWebAPI._entityName} record`); this._container.appendChild(headerDiv); // Create button 1 to create record with revenue field set to 100 const value1 = "100"; this._createEntity1Button = this.createHTMLButtonElement( this.getCreateRecordButtonLabel(value1), this.getCreateButtonId(value1), value1, this.createButtonOnClickHandler.bind(this)); // Create button 2 to create record with revenue field set to 200 const value2 = "200"; this._createEntity2Button = this.createHTMLButtonElement( this.getCreateRecordButtonLabel(value2), this.getCreateButtonId(value2), value2, this.createButtonOnClickHandler.bind(this)); // Create button 3 to create record with revenue field set to 300 const value3 = "300"; this._createEntity3Button = this.createHTMLButtonElement( this.getCreateRecordButtonLabel(value3), this.getCreateButtonId(value3), value3, this.createButtonOnClickHandler.bind(this)); // Append all button HTML elements to custom control container div this._container.appendChild(this._createEntity1Button); this._container.appendChild(this._createEntity2Button); this._container.appendChild(this._createEntity3Button); } /** * Renders example use of DeleteRecord Web API */ private renderDeleteExample(): void { // Create header label for Web API sample const headerDiv: HTMLDivElement = this.createHTMLDivElement("delete_container", true, `Click to delete ${TSWebAPI._entityName} record`); // Render button to invoke DeleteRecord Web API call this._deleteRecordButton = this.createHTMLButtonElement( "Select record to delete", "delete_button", null, this.deleteButtonOnClickHandler.bind(this)); // Append elements to custom control container div this._container.appendChild(headerDiv); this._container.appendChild(this._deleteRecordButton); } /** * Renders example use of RetrieveMultiple Web API with OData */ private renderODataRetrieveMultipleExample(): void { const containerClassName = "odata_status_container"; // Create header label for Web API sample const statusDivHeader: HTMLDivElement = this.createHTMLDivElement(containerClassName, true, "Click to refresh record count"); this._odataStatusContainerDiv = this.createHTMLDivElement(containerClassName, false, undefined); // Create button to invoke OData RetrieveMultiple Example this._fetchXmlRefreshButton = this.createHTMLButtonElement( "Refresh record count", "odata_refresh", null, this.refreshRecordCountButtonOnClickHandler.bind(this)); // Append HTML elements to custom control container div this._container.appendChild(statusDivHeader); this._container.appendChild(this._odataStatusContainerDiv); this._container.appendChild(this._fetchXmlRefreshButton); } /** * Renders example use of RetrieveMultiple Web API with Fetch XML */ private renderFetchXmlRetrieveMultipleExample(): void { const containerName = "fetchxml_status_container"; // Create header label for Web API sample const statusDivHeader: HTMLDivElement = this.createHTMLDivElement(containerName, true, `Click to calculate average value of ${TSWebAPI._currencyAttributeNameFriendlyName}`); const statusDiv: HTMLDivElement = this.createHTMLDivElement(containerName, false, undefined); // Create button to invoke Fetch XML RetrieveMultiple Web API example this._oDataRefreshButton = this.createHTMLButtonElement( `Calculate average value of ${TSWebAPI._currencyAttributeNameFriendlyName}`, "odata_refresh", null, this.calculateAverageButtonOnClickHandler.bind(this)); // Append HTML Elements to custom control container div this._container.appendChild(statusDivHeader); this._container.appendChild(statusDiv); this._container.appendChild(this._oDataRefreshButton); } /** * Renders a 'result container' div element to inject the status of the example Web API calls */ private renderResultsDiv() { // Render header label for result container const resultDivHeader: HTMLDivElement = this.createHTMLDivElement("result_container", true, "Result of last action"); this._container.appendChild(resultDivHeader); // Div elements to populate with the result text this._resultContainerDiv = this.createHTMLDivElement("result_container", false, undefined); this._container.appendChild(this._resultContainerDiv); // Init the result container with a notification the control was loaded this.updateResultContainerText("Web API sample custom control loaded"); } /** * Event Handler for onClick of create record button * @param event : click event */ private createButtonOnClickHandler(event: Event): void { // Retrieve the value to set the currency field to from the button's attribute const currencyAttributeValue: number = parseInt( (event.target as Element)?.attributes?.getNamedItem("buttonvalue")?.value ?? "0" ); // Generate unique record name by appending timestamp to _requiredAttributeValue const recordName = `${TSWebAPI._requiredAttributeValue}_${Date.now()}`; // Set the values for the attributes we want to set on the new record // If you want to set additional attributes on the new record, add to data dictionary as key/value pair const data: ComponentFramework.WebApi.Entity = {}; data[TSWebAPI._requiredAttributeName] = recordName; data[TSWebAPI._currencyAttributeName] = currencyAttributeValue; // Invoke the Web API to creat the new record this._context.webAPI.createRecord(TSWebAPI._entityName, data).then( (response: ComponentFramework.LookupValue) => { // Callback method for successful creation of new record // Get the ID of the new record created const id: string = response.id; // Generate HTML to inject into the result div to showcase the fields and values of the new record created let resultHtml = `Created new ${ TSWebAPI._entityName } record with below values:`; resultHtml += "<br />"; resultHtml += "<br />"; resultHtml += `id: ${id}`; resultHtml += "<br />"; resultHtml += "<br />"; resultHtml += `${TSWebAPI._requiredAttributeName}: ${recordName}`; resultHtml += "<br />"; resultHtml += "<br />"; resultHtml += `${TSWebAPI._currencyAttributeName}: ${currencyAttributeValue}`; this.updateResultContainerText(resultHtml); }, (errorResponse) => { // Error handling code here - record failed to be created this.updateResultContainerTextWithErrorResponse(errorResponse); } ); } /** * Event Handler for onClick of delete record button * @param event : click event */ private deleteButtonOnClickHandler(): void { // Invoke a lookup dialog to allow the user to select an existing record of type _entityName to delete const lookUpOptions: any = { entityTypes: [TSWebAPI._entityName] }; const lookUpPromise = this._context.utils.lookupObjects(lookUpOptions); lookUpPromise.then( // Callback method - invoked after user has selected an item from the lookup dialog // Data parameter is the item selected in the lookup dialog (data: ComponentFramework.LookupValue[]) => { if (data && data[0]) { // Get the ID and entityType of the record selected by the lookup const id: string = data[0].id; const entityType: string = data[0].entityType; // Invoke the deleteRecord method of the WebAPI to delete the selected record this._context.webAPI.deleteRecord(entityType, id).then( (response: ComponentFramework.LookupValue) => { // Record was deleted successfully const responseId: string = response.id; const responseEntityType: string = response.entityType; // Generate HTML to inject into the result div to showcase the deleted record this.updateResultContainerText(`Deleted ${responseEntityType} record with ID: ${responseId}`); }, (errorResponse) => { // Error handling code here this.updateResultContainerTextWithErrorResponse(errorResponse); } ); } }, (error) => { // Error handling code here this.updateResultContainerTextWithErrorResponse(error); } ); } /** * Event Handler for onClick of calculate average value button * @param event : click event */ private calculateAverageButtonOnClickHandler(): void { // Build FetchXML to retrieve the average value of _currencyAttributeName field for all _entityName records // Add a filter to only aggregate on records that have _currencyAttributeName not set to null let fetchXML = "<fetch distinct='false' mapping='logical' aggregate='true'>"; fetchXML += `<entity name='${TSWebAPI._entityName}'>`; fetchXML += `<attribute name='${TSWebAPI._currencyAttributeName}' aggregate='avg' alias='average_val' />`; fetchXML += "<filter>"; fetchXML += `<condition attribute='${TSWebAPI._currencyAttributeName}' operator='not-null' />`; fetchXML += "</filter>"; fetchXML += "</entity>"; fetchXML += "</fetch>"; // Invoke the Web API RetrieveMultipleRecords method to calculate the aggregate value this._context.webAPI.retrieveMultipleRecords(TSWebAPI._entityName, `?fetchXml=${ fetchXML}`).then( (response: ComponentFramework.WebApi.RetrieveMultipleResponse) => { // Retrieve multiple completed successfully -- retrieve the averageValue const averageVal: number = response.entities[0].average_val; // Generate HTML to inject into the result div to showcase the result of the RetrieveMultiple Web API call const resultHTML = `Average value of ${TSWebAPI._currencyAttributeNameFriendlyName} attribute for all ${TSWebAPI._entityName} records: ${averageVal}`; this.updateResultContainerText(resultHTML); }, (errorResponse) => { // Error handling code here this.updateResultContainerTextWithErrorResponse(errorResponse); } ); } /** * Event Handler for onClick of calculate record count button * @param event : click event */ private refreshRecordCountButtonOnClickHandler(): void { // Generate OData query string to retrieve the _currencyAttributeName field for all _entityName records // Add a filter to only retrieve records with _requiredAttributeName field which contains _requiredAttributeValue const queryString = `?$select=${TSWebAPI._currencyAttributeName }&$filter=contains(${TSWebAPI._requiredAttributeName},'${TSWebAPI._requiredAttributeValue}')`; // Invoke the Web API Retrieve Multiple call this._context.webAPI.retrieveMultipleRecords(TSWebAPI._entityName, queryString).then( (response: ComponentFramework.WebApi.RetrieveMultipleResponse) => { // Retrieve Multiple Web API call completed successfully let count1 = 0; let count2 = 0; let count3 = 0; // Loop through each returned record for (const entity of response.entities) { // Retrieve the value of _currencyAttributeName field const value: number = entity[TSWebAPI._currencyAttributeName]; // Check the value of _currencyAttributeName field and increment the correct counter if (value == 100) { count1++; } else if (value == 200) { count2++; } else if (value == 300) { count3++; } } // Generate HTML to inject into the fetch xml status div to showcase the results of the OData retrieve example let innerHtml = "Use above buttons to create or delete a record to see count update"; innerHtml += "<br />"; innerHtml += "<br />"; innerHtml += `Count of ${TSWebAPI._entityName} records with ${TSWebAPI._currencyAttributeName} of 100: ${count1}`; innerHtml += "<br />"; innerHtml += `Count of ${TSWebAPI._entityName} records with ${TSWebAPI._currencyAttributeName} of 200: ${count2}`; innerHtml += "<br />"; innerHtml += `Count of ${TSWebAPI._entityName} records with ${TSWebAPI._currencyAttributeName} of 300: ${count3}`; // Inject the HTML into the fetch xml status div if (this._odataStatusContainerDiv) { this._odataStatusContainerDiv.innerHTML = innerHtml; } // Inject a success message into the result div this.updateResultContainerText("Record count refreshed"); }, (errorResponse) => { // Error handling code here this.updateResultContainerTextWithErrorResponse(errorResponse); } ); } /** * Helper method to inject HTML into result container div * @param statusHTML : HTML to inject into result container */ private updateResultContainerText(statusHTML: string): void { if (this._resultContainerDiv) { this._resultContainerDiv.innerHTML = statusHTML; } } /** * Helper method to inject error string into result container div after failed Web API call * @param errorResponse : error object from rejected promise */ private updateResultContainerTextWithErrorResponse(errorResponse: any): void { if (this._resultContainerDiv) { // Retrieve the error message from the errorResponse and inject into the result div let errorHTML = "Error with Web API call:"; errorHTML += "<br />"; errorHTML += errorResponse.message; this._resultContainerDiv.innerHTML = errorHTML; } } /** * Helper method to generate Label for Create Buttons * @param entityNumber : value to set _currencyAttributeNameFriendlyName field to for this button */ private getCreateRecordButtonLabel(entityNumber: string): string { return `Create record with ${TSWebAPI._currencyAttributeNameFriendlyName} of ${entityNumber}`; } /** * Helper method to generate ID for Create Button * @param entityNumber : value to set _currencyAttributeNameFriendlyName field to for this button */ private getCreateButtonId(entityNumber: string): string { return `create_button_${entityNumber}`; } /** * Helper method to create HTML Button used for CreateRecord Web API Example * @param buttonLabel : Label for button * @param buttonId : ID for button * @param buttonValue : value of button (attribute of button) * @param onClickHandler : onClick event handler to invoke for the button */ private createHTMLButtonElement(buttonLabel: string, buttonId: string, buttonValue: string | null, onClickHandler: (event?: any) => void): HTMLButtonElement { const button: HTMLButtonElement = document.createElement("button"); button.innerHTML = buttonLabel; if (buttonValue) { button.setAttribute("buttonvalue", buttonValue); } button.id = buttonId; button.classList.add("SampleControl_TSWebAPI_ButtonClass"); button.addEventListener("click", onClickHandler); return button; } /** * Helper method to create HTML Div Element * @param elementClassName : Class name of div element * @param isHeader : True if 'header' div - adds extra class and post-fix to ID for header elements * @param innerText : innerText of Div Element */ private createHTMLDivElement(elementClassName: string, isHeader: boolean, innerText?: string): HTMLDivElement { const div: HTMLDivElement = document.createElement("div"); if (isHeader) { div.classList.add("SampleControl_TSWebAPI_Header"); elementClassName += "_header"; } if (innerText) { div.innerText = innerText.toUpperCase(); } div.classList.add(elementClassName); return div; } } ``` ## 코드 구성요소의 스타일 추가(CSS)
- TSWebAPI 폴더 안에 CSS 폴더를 추가한다.
- CSS 폴더 안에 TS_WebAPI.css 파일을 추가한다.
- TS_WebAPI.css 에 다음 css 스타일을 입력한다.
```css .SampleNamespace\.TSWebAPI { font-family: 'SegoeUI-Semibold', 'Segoe UI Semibold', 'Segoe UI Regular', 'Segoe UI'; color: #1160B7; } .SampleNamespace\.TSWebAPI .TSWebAPI_Container { overflow-x: auto; } .SampleNamespace\.TSWebAPI .SampleControl_WebAPI_Header { color: rgb(51, 51, 51); font-size: 1rem; padding-top: 20px; } .SampleNamespace\.TSWebAPI .result_container { padding-bottom: 20px; } .SampleNamespace\.TSWebAPI .SampleControl_WebAPI_ButtonClass { text-decoration: none; display: inline-block; font-size: 14px; cursor: pointer; color: #1160B7; background-color: #FFFFFF; border: 1px solid black; padding: 5px; text-align: center; min-width: 300px; margin-top: 10px; margin-bottom: 5px; display: block; } ```
빌드 및 실행
npm run build
를 파워쉘에서 실행해 빌드한다.npm start
로 실행해서 잘 적용됬는지 확인한다.- 이후 솔루션에 포함시켜서 업로드를 한다.
- 업로드한 코드 구성 요소를 '거래처' 테이블의 양식에서 적당한 행에 대체하여 넣는다.(코드 구성요소를 모델 기반 앱에서 사용하는 법은 이후 올리겠다.)
- 실제 모델 기반 앱에서 실행되는 것을 버튼을 클릭하며 확인한다.
728x90반응형'Power Platform > Power Apps' 카테고리의 다른 글
Power Apps 코드 구성요소(PCF) - 팝업 (0) 2022.03.26 Power Apps 코드 구성요소(PCF) - 서식(Formatting) 사용 (0) 2022.03.25 Power Apps 코드 구성요소(PCF) - React 사용 (0) 2022.03.23 AI Builder - 양식 처리 (0) 2022.03.22 Power Apps 사용자 지정 구성요소 - 2 솔루션 만들어 포함시키기 (0) 2022.03.20