Usar a API de diálogo do Office em suplementos do Office

Use a API de diálogo do Office para abrir caixas de diálogo no suplemento do Office. Este artigo fornece orientações para usar a API de Caixa de diálogo em seu Suplemento do Office. Considere abrir uma caixa de diálogo de um painel de tarefas, suplemento de conteúdo ou comando de suplemento para fazer o seguinte:

  • Entre em um usuário com um recurso como Google, Facebook ou identidade da Microsoft. Para obter mais informações, consulte Autenticar com a API de diálogo do Office.
  • Fornecer mais espaço na tela, ou até uma tela inteira, para algumas tarefas no seu suplemento.
  • Hospedar um vídeo que seria muito pequeno se fosse confinado em um painel de tarefas.

Observação

Como a sobreposição de elementos de IU não são recomendáveis, evite abrir uma caixa de diálogo em um painel de tarefas a menos que seu cenário o obrigue a fazer isso. Ao considerar como usar a área de superfície de um painel de tarefas, observe que painéis de tarefas podem ter guias. Para obter um exemplo de um painel de tarefas com guias, consulte o exemplo JavaScript SalesTracker do Suplemento do Excel .

A imagem abaixo mostra um exemplo de uma caixa de diálogo.

Caixa de diálogo de entrada com plataforma de identidade da Microsoft no Word.

A caixa de diálogo sempre é aberta no centro da tela. O usuário pode movê-la e redimensioná-la. A janela não é demodal — um usuário pode continuar a interagir com o documento no aplicativo do Office e com a página no painel de tarefas, se houver um.

Abrir uma caixa de diálogo em uma página de host

As APIs JavaScript para Office incluem um objetoDialog e duas funções no namespace Office.context.ui.

Para abrir uma caixa de diálogo, seu código, geralmente uma página no painel de tarefas chama o método displayDialogAsync e transmite a ele a URL do recurso que você deseja abrir. A página em que esse método é chamado é conhecida como "página host". Por exemplo, se você chamar esse método no script index.html em um painel de tarefas, index.html será a página do host da caixa de diálogo que o método abre.

O recurso aberto na página de diálogo geralmente é uma página, mas pode ser um método controlador em um aplicativo MVC, uma rota, um método de serviço Web ou qualquer outro recurso. Neste artigo, 'página' ou 'site' refere-se ao recurso na caixa de diálogo. O código a seguir é um exemplo simples.

Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html');
  • A URL usa o protocolo HTTPS. Isso é obrigatório para todas as páginas carregadas em uma caixa diálogo, não apenas para a primeira página carregada.
  • A caixa de diálogo é igual ao domínio da página de host, que pode ser a página em um painel de tarefas ou o arquivo de função de um comando de suplemento. É necessário que a página, o método do controlador ou outro recurso passado para o displayDialogAsync método deve estar no mesmo domínio que a página de host.

Importante

A página de host e o recurso que abrem na caixa de diálogo devem ter o mesmo domínio inteiro. Se você tentar passar displayDialogAsync para um subdomínio do domínio do suplemento, ele não funcionará. O domínio completo, incluindo qualquer subdomínio, deve corresponder.

Após o carregamento da primeira página (ou de outro recurso), um usuário pode usar links ou outra interface de usuário para qualquer site (ou outro recurso) que usa HTTPS. Também é possível criar a primeira página para redirecionar imediatamente para outro site.

Por padrão, a caixa de diálogo ocupará 80% da altura e da largura na tela do dispositivo, mas você pode definir porcentagens diferentes. Basta transmitir um objeto de configuração para o método, como mostra o exemplo a seguir.

Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html', {height: 30, width: 20});

Para obter um suplemento de exemplo que faça isso, consulte Criar suplementos do Office para Excel. Para obter mais exemplos que usam displayDialogAsync, confira Exemplos de código.

Defina os dois valores como 100% para ter uma verdadeira experiência de tela inteira. O máximo efetivo é de 99,5%, e a janela ainda é movevel e res considerável.

Apenas uma caixa de diálogo pode ser aberta em uma janela do host. Tentar abrir outra caixa de diálogo gera um erro. Por exemplo, se um usuário abrir uma caixa de diálogo de um painel de tarefas, não poderá abrir uma segunda caixa de diálogo de uma página diferente no painel de tarefas. No entanto, quando uma caixa de diálogo é aberta em um comando de suplemento, o comando abre um arquivo HTML novo (mas não visto) sempre que ele é selecionado. Isso cria uma nova janela do host (não vista) para que cada janela possa iniciar sua própria caixa de diálogo. Para obter mais informações, confira Erros de displayDialogAsync.

Aproveite uma opção de desempenho no Office na Web

A propriedade displayInIframe é uma propriedade adicional no objeto de configuração que você pode passar para odisplayDialogAsync. Quando essa propriedade for definida como true e o suplemento estiver em execução em um documento aberto no Office Online, a caixa de diálogo será aberta como um iframe flutuante, em vez de uma janela independente, o que faz com que ela seja aberta mais rapidamente. Apresentamos um exemplo a seguir.

Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html', {height: 30, width: 20, displayInIframe: true});

O valor padrão é false, que é o mesmo que omitir a propriedade inteiramente. Se o suplemento não estiver em execução no Office na Web, o displayInIframe será ignorado.

Observação

Você não deve usar displayInIframe: true se a caixa de diálogo em qualquer ponto redirecionar para uma página que não pode ser aberta em um iframe. Por exemplo, as páginas de entrada de muitos serviços Web populares, como Google e Microsoft, não podem ser abertas em um iframe.

Envie informações da caixa de diálogo para a página host

O código na caixa de diálogo usa a função messageParent para enviar uma mensagem de cadeia de caracteres para a página do host. A cadeia de caracteres pode ser uma palavra, frase, blob XML, JSON stringified ou qualquer outra coisa que possa ser serializada para uma cadeia de caracteres ou lançada para uma cadeia de caracteres. Para usar o messageParent método, a caixa de diálogo deve primeiro inicializar a API JavaScript do Office.

Observação

Para obter clareza, nesta seção chamamos a mensagem de destino da página do host, mas estritamente falando as mensagens estão indo para o Runtime no painel de tarefas (ou o runtime que está hospedando um arquivo de função). A distinção só é significativa no caso de mensagens entre domínios. Para obter mais informações, mensagens entre domínios para o runtime do host.

O exemplo a seguir mostra como inicializar o Office JS e enviar uma mensagem para a página do host.

Office.onReady(function() {
   // Add any initialization code for your dialog here.
});

// Called when dialog signs in the user.
function userSignedIn() {
    Office.context.ui.messageParent(true.toString());
}

O próximo exemplo mostra como retornar uma cadeia de caracteres JSON que contém informações de perfil.

function userProfileSignedIn(profile) {
    const profileMessage = {
        "name": profile.name,
        "email": profile.email,
    };
    Office.context.ui.messageParent(JSON.stringify(profileMessage));
}

A messageParent função é uma das duas únicas APIs JS do Office que podem ser chamadas na caixa de diálogo. A outra API JS que pode ser chamada na caixa de diálogo é Office.context.requirements.isSetSupported. Para obter informações sobre isso, consulte Especificar aplicativos do Office e requisitos de API. No entanto, na caixa de diálogo, essa API não tem suporte no Outlook 2016 perpétuo licenciado por volume (ou seja, na versão MSI).

A página host deve ser configurada para receber a mensagem. Você pode fazer isso adicionando um parâmetro de retorno de chamada à chamada original de displayDialogAsync. O retorno de chamada atribui um manipulador ao evento DialogMessageReceived. Apresentamos um exemplo a seguir.

let dialog; // Declare dialog as global for use in later functions.
Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html', {height: 30, width: 20},
    function (asyncResult) {
        dialog = asyncResult.value;
        dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
    }
);

O Office transmite um objeto AsyncResult para o retorno de chamada. Ele representa o resultado de tentativas de abrir a caixa de diálogo, Ela não representa o resultado de eventos na caixa diálogo. Para saber mais sobre essa distinção, confira Manipular erros e eventos.

  • A propriedade value do asyncResult é definida como um objeto Dialog que existe na página host, não no contexto da execução da caixa de diálogo.
  • O processMessage é a função que manipula o evento. Você pode dar a ele o nome que desejar.
  • A variável dialog é declarada em um escopo mais amplo do que o retorno de chamada porque ela também é referenciada em processMessage.

Veja a seguir um exemplo simples de um manipulador para o evento DialogMessageReceived.

function processMessage(arg) {
    const messageFromDialog = JSON.parse(arg.message);
    showUserName(messageFromDialog.name);
}

O Office transmite o objeto arg para o manipulador. Sua message propriedade é a cadeia de caracteres enviada pela chamada da caixa de messageParent diálogo. Neste exemplo, ele é uma representação em cadeia de caracteres do perfil de um usuário de um serviço, como conta microsoft ou Google, portanto, ele é desserializado de volta para um objeto com JSON.parse. A showUserName implementação não é mostrada. Ela pode exibir uma mensagem de boas-vindas personalizada no painel de tarefas.

Quando a interação do usuário com a caixa de diálogo for concluída, seu manipulador de mensagem fechará a caixa de diálogo, conforme mostrado neste exemplo.

function processMessage(arg) {
    dialog.close();
    // message processing code goes here;
}

O objeto dialog deve ser o mesmo que é retornado pela chamada de displayDialogAsync. Você precisa declarar o dialog objeto como uma variável global. Ou você pode escopo do dialog objeto para a displayDialogAsync chamada com uma função de retorno de chamada anônima, conforme mostrado no exemplo a seguir. No exemplo, não é necessário fechar a caixa de diálogo, processMessage pois o close método é chamado na função de retorno de chamada anônima.

Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html', {height: 30, width: 20},
    function (asyncResult) {
        const dialog = asyncResult.value;
        dialog.addEventHandler(Office.EventType.DialogMessageReceived, (arg) => {
            dialog.close();
            processMessage(arg);
        });
      }
    );

Se o suplemento precisa abrir uma página diferente do painel de tarefas depois de receber a mensagem, é possível usar o método window.location.replace (ou window.location.href) como a última linha do manipulador. Apresentamos um exemplo a seguir.

function processMessage(arg) {
    // message processing code goes here;
    window.location.replace("/newPage.html");
    // Alternatively ...
    // window.location.href = "/newPage.html";
}

Para ver um exemplo de um suplemento que faz isso, consulte Inserir gráficos do Excel usando o Microsoft Graph em um Suplemento do PowerPoint.

Mensagens condicionais

Como você pode enviar várias chamadas messageParent a partir da caixa de diálogo, mas tem apenas um manipulador na página host do evento DialogMessageReceived, o manipulador tem que usar a lógica condicional para distinguir mensagens diferentes. Por exemplo, se a caixa de diálogo solicitar que um usuário entre em um provedor de identidade, como conta microsoft ou Google, ele enviará o perfil do usuário como uma mensagem. Se a autenticação falhar, a caixa de diálogo enviará informações de erro para a página do host, como no exemplo a seguir.

if (loginSuccess) {
    const userProfile = getProfile();
    const messageObject = {messageType: "signinSuccess", profile: userProfile};
    const jsonMessage = JSON.stringify(messageObject);
    Office.context.ui.messageParent(jsonMessage);
} else {
    const errorDetails = getError();
    const messageObject = {messageType: "signinFailure", error: errorDetails};
    const jsonMessage = JSON.stringify(messageObject);
    Office.context.ui.messageParent(jsonMessage);
}

Sobre o exemplo anterior, observe:

  • A variável loginSuccess poderia ser inicializada por meio da leitura da resposta HTTP no provedor de identidade.
  • A implementação das getProfile funções e getError não é mostrada. Cada uma delas obtém dados de um parâmetro de consulta ou do corpo da resposta HTTP.
  • São enviados objetos anônimos de diferentes tipos se a entrada for bem-sucedida ou não. Ambos têm uma propriedade messageType, mas um tem uma propriedade profile e o outro tem uma propriedade error.

O código do manipulador na página host usa o valor da propriedade messageType para ramificar como no exemplo a seguir. A função showUserName é a mesma do exemplo anterior e a função showNotification exibe o erro na interface do usuário da página host.

function processMessage(arg) {
    const messageFromDialog = JSON.parse(arg.message);
    if (messageFromDialog.messageType === "signinSuccess") {
        dialog.close();
        showUserName(messageFromDialog.profile.name);
        window.location.replace("/newPage.html");
    } else {
        dialog.close();
        showNotification("Unable to authenticate user: " + messageFromDialog.error);
    }
}

A showNotification implementação não é mostrada. Ele pode exibir status em uma barra de notificação no painel de tarefas.

Mensagens entre domínios para o runtime do host

Depois que a caixa de diálogo for aberta, a caixa de diálogo ou o runtime pai poderão navegar para longe do domínio do suplemento. Se alguma dessas coisas acontecer, uma chamada de messageParent falhará, a menos que seu código especifique o domínio do runtime pai. Você faz isso adicionando um parâmetro DialogMessageOptions à chamada de messageParent. Esse objeto tem uma targetOrigin propriedade que especifica o domínio para o qual a mensagem deve ser enviada. Se o parâmetro não for usado, o Office pressupõe que o destino seja o mesmo domínio que a caixa de diálogo está hospedando no momento.

Observação

Usar messageParent para enviar uma mensagem entre domínios requer o conjunto de requisitos Origem do Diálogo 1.1. O DialogMessageOptions parâmetro é ignorado em versões mais antigas do Office que não dão suporte ao conjunto de requisitos, portanto, o comportamento do método não será afetado se você passá-lo.

A seguir está um exemplo de uso messageParent para enviar uma mensagem entre domínios.

Office.context.ui.messageParent("Some message", { targetOrigin: "https://resource.contoso.com" });

Se a mensagem não incluir dados confidenciais, você poderá definir o targetOrigin como "*" que permite que ele seja enviado para qualquer domínio. Apresentamos um exemplo a seguir.

Office.context.ui.messageParent("Some message", { targetOrigin: "*" });

Dica

O DialogMessageOptions parâmetro foi adicionado ao messageParent método como um parâmetro necessário em meados de 2021. Os suplementos mais antigos que enviam uma mensagem entre domínios com o método não funcionam mais até que sejam atualizados para usar o novo parâmetro. Até que o suplemento seja atualizado, somente no Office no Windows, usuários e administradores do sistema podem permitir que esses suplementos continuem funcionando especificando os domínios confiáveis com uma configuração de registro: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\WEF\AllowedDialogCommunicationDomains. Para fazer isso, crie um arquivo com uma .reg extensão, salve-o no computador Windows e clique duas vezes nele para executá-lo. A seguir está um exemplo do conteúdo de tal arquivo.

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\WEF\AllowedDialogCommunicationDomains]
"My trusted domain"="https://www.contoso.com"
"Another trusted domain"="https://fabrikam.com"

Transmitir informações para a caixa diálogo

Seu suplemento pode enviar mensagens da página do host para uma caixa de diálogo usando Dialog.messageChild.

Usar messageChild() na página host

Quando você chama a API da caixa de diálogo do Office para abrir uma caixa de diálogo, um objeto Dialog é retornado. Ele deve ser atribuído a uma variável que tenha escopo global para que você possa referenciá-la de outras funções. Apresentamos um exemplo a seguir.

let dialog; // Declare as global variable.
Office.context.ui.displayDialogAsync('https://www.contoso.com/myDialog.html',
    function (asyncResult) {
        dialog = asyncResult.value;
        dialog.addEventHandler(Office.EventType.DialogMessageReceived, processMessage);
    }
);

function processMessage(arg) {
    dialog.close();

  // message processing code goes here;

}

Esse Dialog objeto tem um método messageChild que envia qualquer cadeia de caracteres, incluindo dados stringified, para a caixa de diálogo. Isso gera um DialogParentMessageReceived evento na caixa de diálogo. Seu código deve lidar com esse evento, conforme mostrado na próxima seção.

Considere um cenário no qual a interface do usuário da caixa de diálogo está relacionada à planilha atualmente ativa e à posição dessa planilha em relação às outras planilhas. No exemplo a seguir, sheetPropertiesChanged envia as propriedades da planilha do Excel para a caixa de diálogo. Nesse caso, a planilha atual se chama "Minha Planilha" e é a segunda folha na pasta de trabalho. Os dados são encapsulados em um objeto e stringizados para que possam ser passados para messageChild.

function sheetPropertiesChanged() {
    const messageToDialog = JSON.stringify({
                               name: "My Sheet",
                               position: 2
                           });

    dialog.messageChild(messageToDialog);
}

Manipular DialogParentMessageReceived na caixa de diálogo

No JavaScript da caixa de diálogo, registre um manipulador para o DialogParentMessageReceived evento com o método UI.addHandlerAsync . Normalmente, isso é feito na função Office.onReady ou Office.initialize, conforme mostrado no seguinte. (Um exemplo mais robusto é incluído posteriormente neste artigo.)

Office.onReady(function () {
  Office.context.ui.addHandlerAsync(Office.EventType.DialogParentMessageReceived,
    onMessageFromParent);
});

Em seguida, defina o onMessageFromParent manipulador. O código a seguir continua o exemplo da seção anterior. Observe que o Office passa um argumento para o manipulador e que a message propriedade do objeto argumento contém a cadeia de caracteres da página host. Neste exemplo, a mensagem é reconvertida para um objeto e jQuery é usada para definir o título superior da caixa de diálogo para corresponder ao novo nome da planilha.

function onMessageFromParent(arg) {
  const messageFromParent = JSON.parse(arg.message);
  document.querySelector('h1').textContent = messageFromParent.name;
}

É uma prática recomendada verificar se o manipulador está registrado corretamente. Você pode fazer isso passando um retorno de chamada para o addHandlerAsync método. Isso é executado quando a tentativa de registrar o manipulador é concluída. Use o manipulador para registrar ou mostrar um erro se o manipulador não tiver sido registrado com êxito. Apresentamos um exemplo a seguir. Observe que reportError é uma função, não definida aqui, que registra ou exibe o erro.

Office.onReady(function () {
  Office.context.ui.addHandlerAsync(
    Office.EventType.DialogParentMessageReceived,
      onMessageFromParent,
      onRegisterMessageComplete);
});

function onRegisterMessageComplete(asyncResult) {
  if (asyncResult.status !== Office.AsyncResultStatus.Succeeded) {
    reportError(asyncResult.error.message);
  }
}

Mensagens condicionais da página pai para a caixa de diálogo

Como você pode fazer várias messageChild chamadas da página host, mas tem apenas um manipulador na caixa de diálogo para o DialogParentMessageReceived evento, o manipulador deve usar a lógica condicional para distinguir mensagens diferentes. Você pode fazer isso de uma maneira exatamente paralela à forma como você estruturaria mensagens condicionais quando a caixa de diálogo está enviando uma mensagem para a página do host, conforme descrito em mensagens condicionais.

Observação

Em algumas situações, a messageChild API, que faz parte do conjunto de requisitos DialogApi 1.2, pode não ter suporte. Por exemplo, messageChild não há suporte no Outlook 2016 perpétuo licenciado por volume e no Outlook 2019 perpétuo licenciado por volume. Algumas maneiras alternativas para mensagens pai-a-caixa de diálogo são descritas em formas alternativas de passar mensagens para uma caixa de diálogo de sua página de host.

Importante

O conjunto de requisitos DialogApi 1.2 não pode ser especificado na <seção Requisitos> de um manifesto de suplemento. Você terá que marcar para obter suporte para o DialogApi 1.2 no runtime usando o isSetSupported método conforme descrito em Verificações do Runtime para o método e suporte ao conjunto de requisitos. O suporte para requisitos de manifesto está em desenvolvimento.

Mensagens entre domínios no runtime da caixa de diálogo

Depois que a caixa de diálogo for aberta, a caixa de diálogo ou o runtime pai poderão navegar para longe do domínio do suplemento. Se alguma dessas coisas acontecer, as chamadas para messageChild falharão, a menos que o código especifique o domínio do runtime da caixa de diálogo. Você faz isso adicionando um parâmetro DialogMessageOptions à chamada de messageChild. Esse objeto tem uma targetOrigin propriedade que especifica o domínio para o qual a mensagem deve ser enviada. Se o parâmetro não for usado, o Office pressupõe que o destino seja o mesmo domínio que o runtime pai está hospedando no momento.

Observação

Usar messageChild para enviar uma mensagem entre domínios requer o conjunto de requisitos Origem do Diálogo 1.1. O DialogMessageOptions parâmetro é ignorado em versões mais antigas do Office que não dão suporte ao conjunto de requisitos, portanto, o comportamento do método não será afetado se você passá-lo.

A seguir está um exemplo de uso messageChild para enviar uma mensagem entre domínios.

dialog.messageChild(messageToDialog, { targetOrigin: "https://resource.contoso.com" });

Se a mensagem não incluir dados confidenciais, você poderá definir o targetOrigin como "*" que permite que ele seja enviado para qualquer domínio. Apresentamos um exemplo a seguir.

dialog.messageChild(messageToDialog, { targetOrigin: "*" });

Como o runtime que está hospedando a caixa de diálogo não pode acessar a <seção AppDomains> do manifesto e, assim, determinar se o domínio do qual a mensagem vem é confiável, você deve usar o DialogParentMessageReceived manipulador para determinar isso. O objeto que é passado para o manipulador contém o domínio que está hospedado atualmente no pai como sua origin propriedade. A seguir está um exemplo de como usar a propriedade.

function onMessageFromParent(arg) {
    if (arg.origin === "https://addin.fabrikam.com") {
        // process message
    } else {
        dialog.close();
        showNotification("Messages from " + arg.origin + " are not accepted.");
    }
}

Por exemplo, seu código pode usar a função Office.onReady ou Office.initialize para armazenar uma matriz de domínios confiáveis em uma variável global. Em arg.origin seguida, a propriedade pode ser verificada em relação a essa lista no manipulador.

Dica

O DialogMessageOptions parâmetro foi adicionado ao messageChild método como um parâmetro necessário em meados de 2021. Os suplementos mais antigos que enviam uma mensagem entre domínios com o método não funcionam mais até que sejam atualizados para usar o novo parâmetro. Até que o suplemento seja atualizado, somente no Office no Windows, usuários e administradores do sistema podem permitir que esses suplementos continuem funcionando especificando os domínios confiáveis com uma configuração de registro: HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\WEF\AllowedDialogCommunicationDomains. Para fazer isso, crie um arquivo com uma .reg extensão, salve-o no computador Windows e clique duas vezes nele para executá-lo. A seguir está um exemplo do conteúdo de tal arquivo.

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\SOFTWARE\Microsoft\Office\16.0\WEF\AllowedDialogCommunicationDomains]
"My trusted domain"="https://www.contoso.com"
"Another trusted domain"="https://fabrikam.com"

Fechar a caixa de diálogo

Você pode implementar um botão na caixa de diálogo para fechá-la. Para fazer isso, o manipulador de eventos de clique do botão deve usar messageParent para informar a página host em que o botão foi clicado. Apresentamos um exemplo a seguir.

function closeButtonClick() {
    const messageObject = {messageType: "dialogClosed"};
    const jsonMessage = JSON.stringify(messageObject);
    Office.context.ui.messageParent(jsonMessage);
}

O manipulador de página host de DialogMessageReceived poderia chamar dialog.close, como neste exemplo. (Veja exemplos anteriores que mostram como o objeto dialog é inicializado.)

function processMessage(arg) {
    const messageFromDialog = JSON.parse(arg.message);
    if (messageFromDialog.messageType === "dialogClosed") {
       dialog.close();
    }
}

Mesmo quando você não tem sua própria interface de usuário de diálogo de fechar, um usuário final pode fechar a caixa de diálogo escolhendo a opção X no canto superior direito. Essa ação aciona o evento DialogEventReceived. Se seu painel do host precisar saber quando isso acontece, ele deverá declarar um manipulador para esse evento. Confira a seção Erros e eventos na caixa de diálogo para ver os detalhes.

Exemplos de código

Todos os exemplos a seguir usam displayDialogAsync. Alguns têm servidores baseados em NodeJS e outros têm servidores ASP.NET/IIS-based, mas a lógica de usar o método é a mesma, independentemente de como o lado do servidor do suplemento é implementado.

Confira também