Introdução à criação de um componente de código

Concluído

O Power Apps fornece inúmeras funcionalidades integradas para que os criadores de aplicativos criem seus aplicativos, mas, às vezes, você precisará criar experiências de usuário personalizadas para atender a seus requisitos exclusivos. Os exemplos podem ser a substituição de um valor percentual por um medidor, a renderização de um código de barras em vez de uma ID ou a substituição de controles existentes por outros com mais funcionalidades, como uma exibição de grade do tipo "arrastar e soltar". Você também pode envolver o Power Apps component framework em torno de componentes existentes escritos em outras estruturas da Web, como React ou Angular.

A criação desses componentes permite usar toda a amplitude do ecossistema moderno de desenvolvimento para a Web: as bibliotecas, as estruturas e outras ferramentas que podem ser familiares, e reunir esses recursos em um formato que permita aos criadores de aplicativos criar aplicativos usando o seu código como se fosse parte da plataforma.

Os componentes personalizados do Power Apps são frequentemente chamados de componentes de código, pois exigem código personalizado para criá-los. Consistem em três elementos: um manifesto, uma implementação e recursos.

No exercício a seguir, você criará um componente de código personalizado para lidar com um cenário para sua empresa. Sua empresa gostaria que alguns campos do formulário do aplicativo fossem somente leitura até que o usuário iniciasse manualmente uma edição no valor dos dados. A equipe identificou que nenhum dos controles integrados funcionará, então, solicitaram que você criasse um componente de código personalizado.

Para resolver esse requisito, você criará um componente personalizado de campo editável semelhante à imagem a seguir. O valor será somente leitura até que o usuário selecione editar.

Captura de tela do componente de código personalizado.

Este componente escutará as alterações provenientes do aplicativo host e permitirá ao usuário fazer alterações que são enviadas por push para o aplicativo host. As etapas a seguir ajudarão você a criar esse componente.

Instalar o Power Platform CLI

Para preparar seu computador para compilar componentes de código, execute estas etapas:

  1. Instale o Node.js (fornecido com o npm). Recomendamos que você use uma versão de LTS (Suporte de Longo Prazo) como esta localizada aqui. Convém verificar se o Node/NPM já não está instalado. Você pode fazer isso acessando um prompt de comando e digitando o seguinte:

    // Launch a standard command prompt and type both of the following
    
    npm --version
    Node --version
    

    Se receber um erro ao executar esses comandos, você precisará instalar o Node.js usando os links acima.

    Uma instalação bem-sucedida do nó retornará os números de versão na janela de comando quando você inserir os comandos acima, conforme visto abaixo:

    // If installed properly you will see something like the output below. The version numbers may be slightly different based on the version installed.
    
    C:\npm --version
    10.5.0
    
    C:\Node --version
    v20.12.2
    
  2. Instalar o Visual Studio Code.

  3. Instale a extensão Power Platform Tools. Verifique se você concluiu a instalação do Visual Studio Code antes de instalar o Power Platform Tools.

    O Power Platform Tools não permitirá que você execute comandos da CLI para o Power Platform em um prompt de comando fora do Visual Studio Code. É recomendável instalar também a CLI MSI para PowerPlatform.

    Para instalar a CLI no nível do Windows, siga estas instruções. Você pode ter a CLI e a extensão Power Platform Tools instaladas simultaneamente.

Criar um novo projeto de componente

Antes de iniciar a compilação dos componentes, verifique se os componentes instalados listados acima estão funcionando corretamente. Os comandos NPM e CLI funcionarão em uma Janela de Terminal no Visual Studio Code. Se tiver problemas para executá-los corretamente no VS Code, você poderá optar por executar os comandos do Terminal fornecidos nas etapas abaixo em um Prompt de Comando, se tiver instalado a CLI para o Power Platform.

No Visual Studio Code, navegue até Terminal > Novo Terminal (ou pressione CTRL+SHFT+`). Na janela Terminal no VS Code, repita as instruções de versão do npm e do Node. Se tudo retornar números de versão adequados, você poderá prosseguir com a criação da solução.

// Test installs from Steps 1-3 listed above

// This will verify NPM installation
C:\npm --version
10.5.0

// This will verify Node installation
C:\Node --version
v20.12.2

// This will launch Visual Studio Code from the command line.
C:\Code

Agora que você está pronto para criar um novo projeto de componente, siga estas etapas para começar:

Crie um diretório no qual você criará seu componente. Neste exemplo, você colocará o componente em C:\source\Editable-pcf. Para criar seu próprio diretório, você usará o Visual Studio Code. Como alternativa, você poderá criar as pastas usando o prompt de comando, se tiver problemas com o uso do Terminal do VS Code.

  1. Inicie o Visual Studio Code.

  2. Selecione Terminal e, depois, Novo Terminal.

    Captura de tela da nova seleção de terminal no Visual Studio Code.

  3. Por padrão, sua sessão do Terminal será a última pasta que você usou. Isso será ilustrado na área do prompt de comando de TERMINAL, conforme ilustrado abaixo:

    //Note Your PS will not list exactly what is seen below, but will be specific to your starting path.
    
    PS C:\Users\Name\Folder
    

    Mude o diretório para um local em que você deseja criar essa solução. Você pode usar o comando CD para navegar até um local apropriado.

    Observação

    Lembre-se de que a pasta em que você executa o NPM ou outros comandos é importante. Sempre verifique se você está na pasta Projects antes de executar comandos de compilação. Se isso não for feito, a compilação poderá ser corrompida, e poderá haver problemas na produção de resultados ideais.

    Para criar uma nova pasta no local padrão, use md (make directory), conforme mostrado abaixo na Janela do Terminal do VS Code.

    md source
    cd source
    

    Isso criará um diretório chamado source e levará você a esse diretório usando o comando cd (change directory).

  4. No diretório source criado, crie um diretório chamado editable-pcf. Esse será o diretório PROJECT em que todos os arquivos do projeto serão armazenados. Também mudaremos o diretório para nosso novo diretório Project.

    md editable-pcf
    cd editable-pcf
    

    Captura de tela mostrando o terminal com comandos para criar e alterar o diretório.

  5. Inicialize o projeto de componente usando a Power Platform CLI com o seguinte comando:

    pac pcf init --namespace SampleNamespace --name EditablePCF --template field
    

    A imagem a seguir mostra um exemplo da saída que deve ser exibida.

    Cuidado

    Se o comando PAC PCF INIT não for executado em uma janela de terminal no VS Code e você tiver instalado a Power Platform CLI, poderá optar por executar um prompt de comando e CD em seu diretório editable-pcf. Quando estiver lá, você poderá inserir o comando no Prompt de Comando, e ele funcionará corretamente. Você deverá ver a mesma saída listada acima.

  6. Instale as ferramentas de compilação de projeto usando o comandonpm install. Alguns avisos podem ser exibidos; no entanto, você pode ignorá-los. Verifique se você está no diretório PROJECT antes de emitir esse comando.

    npm install
    

    Cuidado

    Se a instalação do npm não for executada em uma janela de terminal no VS Code e você tiver instalado a Power Platform CLI, poderá optar por executar um prompt de comando e CD em seu diretório editable-pcf. Quando estiver lá, você poderá inserir o comando no Prompt de Comando, e ele funcionará corretamente.

    Você pode verificar se tudo está funcionando executando um comando DIR na Janela do Terminal no VS Code ou no Prompt de Comando, se tiver optado por criar fora do Visual Studio Code. Você deverá ver uma série de arquivos e pastas em seu diretório editable-pcf. Esse é o projeto que você criou nas etapas acima e que compilaremos usando o VS Code.

  7. Execute o comando abaixo para abrir o projeto no Visual Studio Code ou se estiver usando um prompt de comando na janela do prompt de comando. Isso deve iniciar seu projeto criado no VS Code.

    code -a .
    
  8. O conteúdo do projeto deve ser semelhante à imagem abaixo.

    Captura de tela mostrando os arquivos do projeto.

Atualizar o manifesto do componente de código

Atualize o arquivo de manifesto para representar seu controle com precisão.

  1. Expanda a pasta Editable-pcf e abra o arquivo ControlManifest.Input.xml.

    Captura de tela mostrando o arquivo XML de entrada do manifesto de controle.

  2. Altere a versão para 1.0.0 e description-key para Editar o nome do projeto.

  3. Localize o nó property.

  4. Altere name value para Nome, display-name-key para Nome e description-key para Um nome.

  5. Localize o nó resources.

  6. Inclua uma referência a um arquivo CSS chamado editable-pcf.css que você criará nas etapas abaixo.

    <css path="css/EditablePCF.css" order="1" />
    

    Captura de tela mostrando as alterações feitas no nó recursos.

  7. Salve suas alterações selecionando Arquivo e Salvar ou pressione CTRL+S para salvar o arquivo.

Adicionar estilo ao seu componente de código

Para adicionar estilo ao seu componente de código, siga estas etapas:

  1. Verifique se você ainda tem o arquivo ControlManifest.Input.xml selecionado e selecione Nova Pasta.

    Captura de tela mostrando o botão Adicionar nova pasta.

  2. Nomeie a nova pasta como css.

  3. Selecione a pasta css que você criou e, depois, Novo Arquivo.

  4. Nomeie o novo arquivo como EditablePCF.css (ou o nome que você deu ao arquivo css na Etapa 6 acima).

  5. Abra o novo arquivo EditablePCF.css que você criou e cole o trecho de CSS a seguir. Esse é o nome de referência do Recurso que você usou anteriormente quando adicionou o código do caminho css ao arquivo de manifesto.

    .SampleNamespace\.HelloPCF {
          font-size: 1.5em;
        }
    
  6. Agora, o conteúdo do arquivo CSS deve ser semelhante à imagem abaixo.

    Captura de tela mostrando o conteúdo do arquivo CSS.

  7. Selecione Arquivo e Salvar ou pressione CTRL+S para salvar o arquivo.

Compilar seu componente de código

Antes de poder implementar a lógica do componente, você precisará compilar seu componente. Isso garante que os tipos corretos de TypeScript sejam gerados para corresponder às propriedades do documento ControlManifest.xml.

Volte para o terminal no VS Code e crie seu projeto usando o comando a seguir. Se, por algum motivo, você tiver problemas ao usar o Terminal no Visual Studio Code, poderá navegar até a pasta usando o prompt de comando e executar o comando nela.

Cuidado

Verifique se você está na pasta PROJECT em seu terminal antes de emitir esse comando.

npm run build

O componente é compilado no diretório out/controls/EditablePCF. Os artefatos de compilação incluem:

  • pasta css

  • bundle.js: código-fonte do componente agrupado

  • ControlManifest.xml: arquivo de manifesto do componente real que é carregado para a organização do Microsoft Dataverse

    Captura de tela mostrando o conteúdo da pasta de saída.

Aviso

O erro mais comum geralmente recebido aqui é um erro de digitação no nome do arquivo CSS que você criou anteriormente. Se isso acontecer, simplesmente renomeie os arquivos de maneira adequada e execute novamente o comando npm run build até que ele seja executado até a conclusão sem erros. Verifique sua seção RECURSO no arquivo COntrolManifest.Input.xml com o arquivo criado na pasta CSS. Eles devem ser 100% correspondentes.

Implementar a lógica do seu componente de código

Para implementar a lógica do componente de código, siga estas etapas quando a compilação for concluída após as etapas acima. No Visual Studio Code, no EXPLORER, procure um arquivo chamado index.ts. É aí que começaremos a escrever o código do componente.

  1. Abra o arquivo index.ts no Visual Studio Code.

  2. Acima do método constructor, insira as seguintes variáveis particulares:

    // The PCF context object\
    private context: ComponentFramework.Context<IInputs>;
    // The wrapper div element for the component\
    private container: HTMLDivElement;
    // The callback function to call whenever your code has made a change to a bound or output property\
    private notifyOutputChanged: () => void;
    // Flag to track if the component is in edit mode or not\
    private isEditMode: boolean;
    // Tracking variable for the name property\
    private name: string | null;
    
  3. Localize o método init público e substitua-o pelo método abaixo.

    public init(context: ComponentFramework.Context<IInputs>, notifyOutputChanged: () => void, state: ComponentFramework.Dictionary, container: HTMLDivElement) {
    
        // Track all the things
        this.context = context;
        this.notifyOutputChanged = notifyOutputChanged;
        this.container = container;
        this.isEditMode = false;
    
        // Create the span element to hold the project name
        const message = document.createElement("span");
        message.innerText = `Project name ${this.isEditMode ? "" :context.parameters.Name.raw}`;
    
        // Create the textbox to edit the name
        const text = document.createElement("input");
        text.type = "text";
        text.style.display = this.isEditMode ? "block" : "none";
    
        if (context.parameters.Name.raw) {
            text.value = context.parameters.Name.raw;
            // Wrap the two above elements in a div to box out the content
            const messageContainer = document.createElement("div");
            messageContainer.appendChild(message);
            messageContainer.appendChild(text);
    
            // Create the button element to switch between edit and read modes
    
            const button = document.createElement("button");
            button.textContent = this.isEditMode ? "Save" : "Edit";
            button.addEventListener("click", () => { this.buttonClick(); });
    
            // Add the message container and button to the overall control container
            this.container.appendChild(messageContainer);
            this.container.appendChild(button);
        }
    
    }
    

    Aviso

    Você pode notar que o EventListener de buttonClick está sublinhado em vermelho. Não se preocupe: criaremos o método para esse evento abaixo. Se você vir outras seções em vermelho, verifique se tudo foi copiado ou inserido corretamente.

  4. Adicione o método de manipulador de seleção de botão. Adicione o método a seguir abaixo do método init.

    public buttonClick() {
        // Get our controls via DOM queries
        const text = this.container.querySelector("input")!;
        const message = this.container.querySelector("span")!;
        const button = this.container.querySelector("button")!;
    
        // If not in edit mode, copy the current name value to the textbox
        if (!this.isEditMode) {
            text.value = this.name ?? "";
        } 
        else if (text.value != this.name) {
            // if in edit mode, copy the textbox value to name and call the notify callback
            this.name = text.value;
            this.notifyOutputChanged();
        }
    
        // flip the mode flag
        this.isEditMode = !this.isEditMode;
    
        // Set up the new output based on changes
        message.innerText = `Project name ${this.isEditMode ? "" : this.name}`;
        text.style.display = this.isEditMode ? "inline" : "none";
        text.value = this.name ?? "";
        button.textContent = this.isEditMode ? "Save" : "Edit";
    }
    
  5. Localize o método updateView e substitua-o pelo método abaixo.

    public updateView(context: ComponentFramework.Context<IInputs>): void {
    
    // Checks for updates coming in from outside
    
    this.name = context.parameters.Name.raw;
    const message = this.container.querySelector("span")!;
    message.innerText = `Project name ${this.name}`;
    }
    
  6. Localize o getOutputs que será substituído pelo método abaixo.

    public getOutputs(): IOutputs {
    return {
    // If our name variable is null, return undefined instead
    Name: this.name ?? undefined
    };
    }
    
  7. Localize o método destroy e substitua-o pelo método abaixo.

    public destroy() {
    // Remove the event listener we created in init
    this.container.querySelector("button")!.removeEventListener("click", this.buttonClick);
    }
    
  8. Seu Index.ts final agora deve ser semelhante ao código abaixo:

    import { IInputs, IOutputs } from "./generated/ManifestTypes";
    
    export class EditablePCF implements ComponentFramework.StandardControl<IInputs, IOutputs> {
    
        /**
        * Empty constructor.
        */
    
        // The PCF context object\
        private context: ComponentFramework.Context<IInputs>;
        // The wrapper div element for the component\
        private container: HTMLDivElement;
        // The callback function to call whenever your code has made a change to a bound or output property\
        private notifyOutputChanged: () => void;
        // Flag to track if the component is in edit mode or not\
        private isEditMode: boolean;
        // Tracking variable for the name property\
        private name: string | null;
    
        constructor()
        {
    
        }
    
        /**
        * 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) {
    
            // Track all the things
            this.context = context;
            this.notifyOutputChanged = notifyOutputChanged;
            this.container = container;
            this.isEditMode = false;
    
            // Create the span element to hold the project name
    
            const message = document.createElement("span");
            message.innerText = `Project name ${this.isEditMode ? "" :context.parameters.Name.raw}`;
    
            // Create the textbox to edit the name
            const text = document.createElement("input");
            text.type = "text";
            text.style.display = this.isEditMode ? "block" : "none";
    
            if (context.parameters.Name.raw) {
                text.value = context.parameters.Name.raw;
                // Wrap the two above elements in a div to box out the content
                const messageContainer = document.createElement("div");
                messageContainer.appendChild(message);
                messageContainer.appendChild(text);
    
                // Create the button element to switch between edit and read modes
    
                const button = document.createElement("button");
                button.textContent = this.isEditMode ? "Save" : "Edit";
                button.addEventListener("click", () => { this.buttonClick(); });
    
                // Add the message container and button to the overall control container
                this.container.appendChild(messageContainer);
                this.container.appendChild(button);
            }
    
        }
    
        public buttonClick() {
            // Get our controls via DOM queries
    
            const text = this.container.querySelector("input")!;
            const message = this.container.querySelector("span")!;
            const button = this.container.querySelector("button")!;
    
            // If not in edit mode, copy the current name value to the textbox
    
            if (!this.isEditMode) {
                text.value = this.name ?? "";
            } 
            else if (text.value != this.name) {
    
                // if in edit mode, copy the textbox value to name and call the notify callback
                this.name = text.value;
                this.notifyOutputChanged();
            }
    
            // flip the mode flag
            this.isEditMode = !this.isEditMode;
    
            // Set up the new output based on changes
            message.innerText = `Project name ${this.isEditMode ? "" : this.name}`;
            text.style.display = this.isEditMode ? "inline" : "none";
            text.value = this.name ?? "";
            button.textContent = this.isEditMode ? "Save" : "Edit";
        }
    
        /**
        * 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 {
    
            // Checks for updates coming in from outside
            this.name = context.parameters.Name.raw;
            const message = this.container.querySelector("span")!;
            message.innerText = `Project name ${this.name}`;
        }
    
        /**
        * 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 {
            return {
            // If our name variable is null, return undefined instead
            Name: this.name ?? undefined
            };
        }
    
        /**
        * 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() {
            // Remove the event listener we created in init
            this.container.querySelector("button")!.removeEventListener("click", this.buttonClick);
        }
    }
    

Recompilar e executar o componente de código

Para recompilar e executar o componente de código, siga estas etapas:

  1. Agora que a lógica do componente está implementada, volte ao terminal e recrie-o usando o comando abaixo. Você pode executar isso diretamente no VS Code ou por meio do prompt de comando, desde que navegue primeiro até a pasta editable-pcf.

    npm run build
    
  2. A compilação deve ser bem-sucedida.

    Captura de tela mostrando o resultado da criação.

  3. Execute o componente no agente de teste do Node executando o comando abaixo. Se ainda não tiver feito isso, você deverá iniciar um navegador com o componente recém-criado exibido.

    npm start
    

    Observação

    Você também pode habilitar o modo de inspeção para garantir que qualquer alteração nos ativos a seguir seja feita automaticamente sem ter que reiniciar o agente de teste usando o comando npm start watch.

    • Arquivo index.ts.

    • Arquivo ControlManifest.Input.xml

    • Bibliotecas importadas em index.ts

    • Todos os recursos listados no arquivo de manifesto

  4. Uma nova janela do navegador deve carregar o Agente de teste. (A janela deve ser aberta automaticamente, mas você também pode fazer referência ao endereço encontrado na janela de comando.)

  5. Selecione Editar.

    Captura de tela mostrando o botão de edição no agente de teste.

  6. Entre no Projeto Um e selecione Salvar.

  7. Você pode alterar o tamanho do contêiner.

  8. Agora o agente de teste deve ser semelhante à imagem abaixo.

    Captura de tela mostrando o controle no agente de teste.

  9. Feche a janela do navegador do agente de teste.

  10. Volte para o terminal ou prompt de comando (se você não estiver usando o VS Code Terminal) e pare o observador mantendo pressionadas as teclas [CONTROL] + C.

  11. Digite Y e [ENTER].