Pesquisa de códigos de erro

by Pedro Azevedo 16. February 2015 07:33

Boas pessoal,

O que eu mais gosto na comunidade é que colocam sempre desafios que me fazem pensar e perceber que ainda tenho muito para aprender. Quer dizer este post não está relacionado diretamente com uma pergunta  do fórum, tem a ver com a pergunta que respondi neste post. Onde o Dynamics CRM apenas era retorna o código de erro e não tínhamos qualquer descrição para tomarmos uma decisão.

Com este problema em mente decidi então criar uma biblioteca Javascript que permita essa pesquisa. Com base no número seja ele em inteiro ou hexadecimal, dar a mensagem correspondente a esse erro. Para além da biblioteca ainda criei um Recurso Web muito simples com numa caixa de texto para colocar o número de erro e chamando a biblioteca ele retorna a mensagem correspondente.

A solução deste problema foi bastante simples, foi importado o ficheiro xml de erros que vem junto do SDK como um Recurso Web e com o Javascript faço uma pesquisa nesse Recurso Web.

Em anexo estão os ficheiros da solução, brevemente espero colocar o código no github.

No futuro espero melhor este componente fazendo pesquisas nos fóruns de discussão do MSDN e Stackoverflow sobre esse erro, bem como arranjar uma maneira de mostrar o erro em várias línguas, algo que não consegui ainda realizar.

Updated: Atualizei as soluções para a versão mais recente.

Referências:

https://msdn.microsoft.com/en-us/library/gg328182.aspx

https://msdn.microsoft.com/en-us/library/gg328182%28v=crm.5%29.aspx

HelperErrorCodes_1_0_0_0_managed.zip (115,79 kb)

HelperErrorCodes_1_0_0_0_target_CRM_7.0_managed.zip (116,03 kb)

Tags: , , ,

Executar um FetchXML e exportar para CSV com Javascript

by Pedro Azevedo 19. October 2014 16:12

 

Boas pessoal,

Hoje um post muito rápido de como executar um FetchXML e exportar os resultados para um CSV, tudo isto em Javascript.

Para começar é possível executar um FetchXML através de uma requisição SOAP. Quando tenho este tipo de requisição recorro a biblioteca XrmServiceToolkit, no blog existem vários exemplos.

Para executar um FetchXml usamos a função Fetch:

function ExecuteFetch() {
    var fetchXml =
            "<fetch mapping='logical'>" +
               "<entity name='contact'>" +
                  "<attribute name='contactid' />" +
                  "<attribute name='firstname' />" +
                  "<attribute name='lastname' />" +
                  "<attribute name='middlename' />" +
                  "<filter>" +
                     "<condition attribute='parentcustomerid' operator='eq' value='" + Xrm.Page.data.entity.getId() + "' />" +
                  "</filter>" +
               "</entity>" +
            "</fetch>";

    var retrievedContacts = XrmServiceToolkit.Soap.Fetch(fetchXml);
}

Este código está preparado para correr dentro da entidade cliente, retorna todos os contactos desse cliente. A dificuldade a seguir é colocar os dados num ficheiro CSV e fazer download dele, existem várias estratégias:

var csvString = allValues.join("\n");

    var a  = document.createElement('a');
    a.href = 'data:attachment/csv,' + csvString;
    a.target = '_blank';
    a.download = 'myFile.csv';

    document.body.appendChild(a);
    a.click();

Vamos ver agora o código completo que inclui o tratamento dos dados retornados:

function ExecuteFetch() {
    var fetchXml =
            "<fetch mapping='logical'>" +
               "<entity name='contact'>" +
                  "<attribute name='contactid' />" +
                  "<attribute name='firstname' />" +
                  "<attribute name='lastname' />" +
                  "<attribute name='middlename' />" +
                  "<filter>" +
                     "<condition attribute='parentcustomerid' operator='eq' value='" + Xrm.Page.data.entity.getId() + "' />" +
                  "</filter>" +
               "</entity>" +
            "</fetch>";

    var retrievedContacts = XrmServiceToolkit.Soap.Fetch(fetchXml);

    var allValues = [];
    var strCol = "";
    var strVal = "";

    var cols = true;

     for (var i = 0; i < retrievedContacts.length; i++) {
      var attsValues = retrievedContacts[i].attributes;

      for (var att in attsValues) {
        strVal += attsValues[att].value + ";";

        if(cols){
          strCol += att + ";";
        }
      }

      if(cols){
        allValues.push(strCol);
        cols = false;
      }

      allValues.push(strVal);
      strVal = "";
    }

    var csvString = allValues.join("\n");

    var a  = document.createElement('a');
    a.href = 'data:attachment/csv,' + csvString;
    a.target = '_blank';
    a.download = 'myFile.csv';

    document.body.appendChild(a);
    a.click();
}

 

Até a próxima.

Tags: , , , ,

Como fazer debug dos Web Resources no browser

by Pedro Azevedo 17. October 2014 01:47

Boas pessoal,

Neste post vou continuar o assunto de realizar debug sobre as nossas soluções de CRM. No post anterior aprendemos a realizar debug sobre um plugin no CRM Online. Hoje vamos falar como realizar debug sobre o Javascript, como vamos utilizar as ferramentas de programador e vou tentar mostrar dos vários browsers, vamos dar um olhinho como é que obtemos os ids dos elementos que queremos trabalhar e aplicar um Javascript não customizado em cima.

Então como referi vamos usar essencialmente as ferramentas de programador de cada um dos browsers.

O firefox tem uma extensão muito conhecida chamada FireBug, por isso o primeiro passo é instalar esta extensão:

 Depois de instalar basta pressionar F12 ou utilizar um dos botões de atalho que vão aparecer no browser:

Este é o aspeto da nossa ferramenta que neste momento está presa no fundo da página mas podemos correr esta aplicação numa janela a parte. Na “Consola do Terminal” vamos ver todo o feedback sobre esta página, se por exemplo tivermos um console.log é nesta tab que vamos ver esse resultado, bem como se houver algum erro é aqui que vemos. Nesta tab ainda podermos executar código Javascript.

A tab do HTML permite-nos visualizar o DOM desta página e localizar elementos através do botão do retângulo com um cursor ao lado do símbolo do firebug, esta é uma das funcionalidades que mais uso pois permite-nos saber por exemplo o ID dos elementos HTML que usamos depois para aplicar um Javascript não customizado:

Como podemos comprovar eu carregando no icon para pesquisa de elementos, e selecionar um elemento posso ver todas as propriedades\eventos e com isso “sacar” o id desse elemento, que neste caso é o “form_pic_image”.

Na tab CSS posso visualizar todos os estilos que foram carregados nessa página.

No âmbito do assunto mais importante para este post temos a tab do Script que permite-nos ver todos os scripts que estão a ser carregados na página e mais importante permite-nos realizar debug sobre esses mesmos scripts:

Nesta tab temos a possibilidade de ver o código Javascript e conseguimos colocar variáveis de “Vigia” para podermos consultar o estado atual dessas mesmas variáveis. Ao selecionarmos na caixa de seleção podemos visualizar outros ficheiros de Javascript:

Aqui podemos visualizar os ficheiros dos nossos Recursos Web, sejam eles do tipo Javascript ou HTML, já que os ficheiros HTML podem também ter código Javascript. Escolhendo por exemplo o Recurso Web “new_putgridreadonly”:

Sobre este ficheiro podemos colocar breakpoints, basta para isso carregar no espaço em branco do lado direito no número da linha. Outra alternativa para o código Javascript parar é colocarmos a palavra reservada, no código Javascript:

debugger;

Se atualizarmos a página onde estamos vamos parar naquele sítio:

Como podemos comprovar pela seta amarela estou em debug e agora posso usar os icons de andar para a frente ou utilizar os mesmos shortcuts que usamos no Visual Studio F10 para continuar e F11 para entrar dentro da função, cuidado que o F5 aqui vai refrescar a página, neste caso é o F8. Como ainda podemos ver se eu colocar o cursor sobre a variável consigo ver o seu valor e posso ainda adicionar as variáveis de Vigia e ver mais pormenores.

Há mais uma tab que eu gostava de realçar que é a tab de Rede, uma das que mais utilizo e permite vermos o que está a ser carregado na página e por exemplo quanto tempo demorou a carregar, por exemplo para verificar o que uma chamada ODATA ou SOAP está a retornar é excelente:

Como podem ver na segunda imagem tenho uma chamada aos WebServices do CRM e onde posso ver o que foi enviado e a reposta que recebemos é muito útil.

Uma última coisa que não podemos esquecer que é a pesquisa que facilita muito as coisas, pois se tivermos a visualizar elementos pode-se colocar logo um id específico por exemplo e no Javascript pode-se pesquisar pelo nome de uma função.

Nos restantes browsers é tudo muito parecido, por exemplo no Chrome e no Opera as ferramentas de programador são muito parecidas e já vêm embutidas no browser, bastando no caso do chrome premir a tecla F12, no caso do Opera é diferente pois primeiro temos que ativar estas ferramentas e só depois é que as podemos invocar com Ctrl+Shift+I:

Os conceitos são exatamente iguais só que em tabs diferentes. Vamos ver especificamente o debug do Javascript:

As coisas só estão dispostas de maneira diferente mas o resultado vai ser igual, o breakpoint é sinalizado pelo bookmark azul. Quando atualizamos a página temos este ambiente:

No IE temos atualmente duas versões das ferramentas para o programador, uma antes do IE11 e com o IE11, estas ferramentas já vêm por defeito bastando premir o F12, mais uma vez as coisas são muito parecidas com os restantes browsers:

Uma coisa que acontece nesta ferramenta e que já não acontece com o IE11 (prometo brevemente colocar aqui imagens do IE11) é que nós temos que dizer que queremos começar a fazer debug ou nas capturas de rede que queremos começar a capturar, ou seja, ele por defeito não vai fazer nenhuma destas coisas, saltando diretamente para a tab “Script”:

Ao fazermos atualizarmos a página podermos realizar o debug ao Javascript, de referir que aqui o F5 já funciona. Como eu referi anteriormente é possível acrescentarmos um breakpoint escrevendo no código a palavra reservada debugger, mas para ele parar teremos primeiro que averiguar duas configurações em: Ferramentas -> Opções de Internet -> Avançadas e retirar o pisco desta duas configurações:

Como podem ver não é nada específico do Dynamics CRM vocês podem aplicar isto a qualquer aplicação Web. Em relação ao meu preferido não tenho, vario muitas vezes e quando não estou a conseguir ver num salta para outro que me dá outro tipo de feedback. Havia muito para falar mas foi uma breve introdução.

 

Até a próxima.

Tags: , , , ,

Tudo sobre WebResources IV

by Administrator 2. May 2014 06:58

Boas pessoal,

Um post rápido só para acrescentar informação ao post sobre utilização de WebResources, tinha referido que havia quatro áreas (Dashboard; Navegação no Formulário; SiteMap – Sub Area; Ribbon) onde os WebResources se poderiam utilizar.

Existe uma outra área onde esta funcionalidade pode ser utilizada, que é na página de configuração de uma solução. Que obrigatoriamente se quisermos ter uma página de informação ou configuração teremos que associar uma página HTML. Confesso que a nível profissional nunca tinha utilizado, mas como ando a trabalhar em dois add-ons para o Dynamics CRM, esta página fez toda a diferença.

Para configurarmos a nossa página de configuração temos que ir a solução e no nó “Informações” temos um campo chamado “Página de Configuração” permite colocarmos a nossa página HTML, com informações e que caso seja necessários parâmetros para configurarmos a nossa solução:

 

Após colocarmos a página de configuração, aparecerá um novo nó intitulado como “Configuração” onde se visualiza a página de configuração adicionada:

A página de configuração é muito simples apenas para vermos a causa efeito. Mas aqui pode-se ter vários campos onde se guarda parâmetros da solução. Imaginemos que esta solução permitia enviar emails e estes teriam que ter uma assinatura da empresa, então haveria um campo de texto que permitia guardar o texto da assinatura, quando o componente de envio de email desta solução fosse utilizado iria-se ler este parâmetro.

A próxima pergunta é como podemos guardar este tipo de configuração, este assunto será alvo de um novo post após eu lançar um dos add-ons.

 

Até a próxima.

Tags: , , , ,

Esconder botão dinâmicamente ribbon ou barra de comandos

by Pedro Azevedo 19. February 2014 23:30

Boas Pessoal,

Já queria fazer este post há muito tempo, mas fui adiando e nunca cheguei a criar sobre a versão CRM 2011. Por isso estou agora a fazê-lo sobre a versão CRM 2013 mas é similar a anterior, por isso poderão aplicar os mesmos conceitos sobre o CRM 2011.

Criei neste momento por causa de uma dúvida nos fóruns do MSDN, em que se quer inibir a criação de uma encomenda numa proposta. Neste caso o critério é que a razão de estado da oportunidade esteja preenchida e tem como requisito que seja feito via Javascript e não por plugin.

Para além de responder a questão do fórum este post tem como objetivo de ser um manual genérico para desabilitar um botão dinamicamente.

Bom o primeiro requisito é instalar esta ferramenta que podem ver aqui um pequeno manual.

A primeira coisa a fazer é criar uma solução que tenha a entidade Proposta (quote), depois desta solução criada teremos que fazer load dela na Ribbon Workbench e terá um layout deste género:

 

O que nós queremos é o botão que temos selecionado “Create Order” estar visível apenas quando uma determinada função Javascript retornar true. Para isso sobre este botão “Create Order” e com o botão direito do rato, carregar “Customise Button”, quando fazemos isto ele deve acrescentar o comando de criar encomenda na tab “Solution Elements”:

Para já aqui é tudo, voltamos mais a frente. Agora vamos criar uma regra de enable, na entrada “Enable Rules” escolham “Add New”:

 

Ele cria uma nova entrada, se quiserem podem modificar o nome, sobre esta nova entrada carreguem em “Add Rule”:

 

Com isto deve aparecer uma lista de regras que podemos aplicar, no nosso caso será uma regra de Javascript.

 

Depois de confirmarmos que queremos uma regra de Javascript, basta dizer qual a função a ser chamada e sobre qual o recurso web queremos aplicar.

Como podem comprovar vou chamar a função Javascript enablequotebutton do recurso web que especifiquei, este recurso web terá que estar na solução que carregamos dentro da ferramenta, neste caso na solução TesteRibbon.

O código presente neste webresource é o seguinte:

function enablequotebutton(){
  LoadWebResource('new_json');
  LoadWebResource('new_jquery');
  LoadWebResource('new_xrmservicetoolkit');
 
  var opId = Xrm.Page.getAttribute("opportunityid").getValue()[0].id;
  var cols = ["statuscode"];
  var retrievedOpportunity = XrmServiceToolkit.Soap.Retrieve("opportunity", opId, cols);

  if(retrievedOpportunity.attributes["statuscode"].value != 1){
    return true;
  }
  else{
    return false;
  }
}

function LoadWebResource(resource) {
  httpRequest = new XMLHttpRequest();
  httpRequest.open("GET", "/webresources/" + resource, false);
  httpRequest.send(null);
  try
    {
      eval(httpRequest.responseText); 
    }
    catch (e)
    {
      alert("Erro");
    }
}

Como podem ver no troço de código anterior estou a utilizar uma função auxiliar para poder carregar bibliotecas Javascript, pois estou a utilizar a biblioteca XrmServiceToolkit para poder ir buscar propriedades da oportunidade associada a proposta.

Depois disto não esqueçam de publicar a solução na Ribbon Workbench.

Até a próxima.

Tags: , , , , ,

CRM 2011 - Listar todas as opções num WebResource (Recurso Web)

by Pedro Azevedo 12. December 2013 01:39

Boas pessoal,

Foi colocada uma questão nos fóruns da Microsoft. Basicamente a questão era como obter todos os estados de uma entidade e listar em um Recurso Web. Como o código e os passos eram bastantes decidi colocar aqui no blog e ajudar futuramente outros programadores.

No final deste desenvolvimento como resultado teremos um Recurso Web a listar num OptionSet todas as opções de um OptionSet criado no CRM. Para isso teremos que aceder aos Metadados do CRM. Neste caso vou usar directamente uma mensagem SOAP para obter estes dados, usando a mensagem RetrieveAttributeRequest. Mas chamando através do Javasript.

O primeiro passo foi criar o Recurso Web no tipo HTML, neste Recurso Web coloquei o seguinte código:

<HTML xmlns:b xmlns:a xmlns:soapenv>
	<HEAD>
         </HEAD>
	<BODY>
		<SELECT id=myList/>
	</BODY>
</HTML>

Até agora nada de especial, vamos colocar algum código Javascript, como vamos estar em um Recurso Web vamos ter que obter informação do contexto para isso teremos que referenciar este aspx ClientGlobalContext.js.aspx (já está no CRM não é necessário referenciar por exemplo noutro Recurso Web).

Com esta referência poderemos então obter o contexto desta maneira:

var context = GetGlobalContext();
var serverUrl = context.getServerUrl();

NOTA: O método getServerUrl encontra-se deprecated com a vinda do UR12, para rollups superiores deve-se usar o método getClientUrl.

Agora "basta" construirmos o pedido SOAP para obter a informação que pretendemos:

var SoapPath = serverUrl + "/XRMServices/2011/Organization.svc/web";
var MetadataId = "00000000-0000-0000-0000-000000000000";
				
var request = " <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Body>"
request += "<Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
request += "<request i:type=\"a:RetrieveAttributeRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">";
request += "<a:Parameters xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
request += "<a:KeyValuePairOfstringanyType>";
request += "<b:key>EntityLogicalName</b:key>";
request += "<b:value i:type=\"c:string\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">" + entityLogicalName + "</b:value>";
request += "</a:KeyValuePairOfstringanyType>";
request += "<a:KeyValuePairOfstringanyType>";
request += "<b:key>MetadataId</b:key>";
request += "<b:value i:type=\"ser:guid\"  xmlns:ser=\"http://schemas.microsoft.com/2003/10/Serialization/\">" + MetadataId + "</b:value>";
request += "</a:KeyValuePairOfstringanyType>";
request += "<a:KeyValuePairOfstringanyType>";
request += "<b:key>RetrieveAsIfPublished</b:key>";
request += "<b:value i:type=\"c:boolean\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">" + retrieveAsIfPublished + "</b:value>";
request += "</a:KeyValuePairOfstringanyType>";
request += "<a:KeyValuePairOfstringanyType>";
request += "<b:key>LogicalName</b:key>";
request += "<b:value i:type=\"c:string\"   xmlns:c=\"http://www.w3.org/2001/XMLSchema\">" + attributeLogicalName + "</b:value>";
request += "</a:KeyValuePairOfstringanyType>";
request += "</a:Parameters>";
request += "<a:RequestId i:nil=\"true\" /><a:RequestName>RetrieveAttribute</a:RequestName></request>";
request += "</Execute>";
request += "</soapenv:Body></soapenv:Envelope>";
				
var req = new XMLHttpRequest();
req.open("POST", SoapPath, false);
req.setRequestHeader("Accept", "application/xml, text/xml, */*");
req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
req.send(request);

Com este pedido podemos aceder a todas as opções do OptionSet escolhido. A seguir vou colocar todo o código, inclusivé o que permite fazer parsing dos resultados e que coloca noutra OptionSet.

<HTML xmlns:b xmlns:a xmlns:soapenv><HEAD>
<SCRIPT src="ClientGlobalContext.js.aspx"></SCRIPT>

<SCRIPT type=text/javascript>
			function getOptionSet (entityLogicalName, attributeLogicalName, retrieveAsIfPublished) {				
				var context = GetGlobalContext();
                var serverUrl = context.getServerUrl();
	            var SoapPath = serverUrl + "/XRMServices/2011/Organization.svc/web";
				var MetadataId = "00000000-0000-0000-0000-000000000000";
				
                var request = " <soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\"><soapenv:Body>"
				request += "<Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
				request += "<request i:type=\"a:RetrieveAttributeRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\">";
				request += "<a:Parameters xmlns:b=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
				request += "<a:KeyValuePairOfstringanyType>";
				request += "<b:key>EntityLogicalName</b:key>";
				request += "<b:value i:type=\"c:string\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">" + entityLogicalName + "</b:value>";
				request += "</a:KeyValuePairOfstringanyType>";
				request += "<a:KeyValuePairOfstringanyType>";
				request += "<b:key>MetadataId</b:key>";
				request += "<b:value i:type=\"ser:guid\"  xmlns:ser=\"http://schemas.microsoft.com/2003/10/Serialization/\">" + MetadataId + "</b:value>";
				request += "</a:KeyValuePairOfstringanyType>";
				request += "<a:KeyValuePairOfstringanyType>";
				request += "<b:key>RetrieveAsIfPublished</b:key>";
				request += "<b:value i:type=\"c:boolean\" xmlns:c=\"http://www.w3.org/2001/XMLSchema\">" + retrieveAsIfPublished + "</b:value>";
				request += "</a:KeyValuePairOfstringanyType>";
				request += "<a:KeyValuePairOfstringanyType>";
				request += "<b:key>LogicalName</b:key>";
				request += "<b:value i:type=\"c:string\"   xmlns:c=\"http://www.w3.org/2001/XMLSchema\">" + attributeLogicalName + "</b:value>";
				request += "</a:KeyValuePairOfstringanyType>";
				request += "</a:Parameters>";
				request += "<a:RequestId i:nil=\"true\" /><a:RequestName>RetrieveAttribute</a:RequestName></request>";
				request += "</Execute>";
				request += "</soapenv:Body></soapenv:Envelope>";
				
				var req = new XMLHttpRequest();
				req.open("POST", SoapPath, false);
				req.setRequestHeader("Accept", "application/xml, text/xml, */*");
				req.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
				req.setRequestHeader("SOAPAction", "http://schemas.microsoft.com/xrm/2011/Contracts/Services/IOrganizationService/Execute");
				req.send(request);

				if (req.responseXML != null) {
					var attributeData = req.responseXML.selectSingleNode("//b:value");
					if (attributeData != null) {
						var attributeType = attributeData.selectSingleNode("c:AttributeType").text;
						switch (attributeType) {
							case "Picklist":
								return attributeData;
								break;
							default:
								break;
						}
					}
				}
			}
		</SCRIPT>

<META charset=utf-8></HEAD>
<BODY contentEditable=true><SELECT id=myList></SELECT>
<SCRIPT>
	var optionSet = getOptionSet("ret_accao", "ret_estadocadastro", true);
	var options = optionSet.selectSingleNode("c:OptionSet//c:Options");
    var myPickList = document.getElementById("myList");
	for (var i = 0; i < options.childNodes.length; i++) {
		var textPick = options.childNodes[i].selectSingleNode("c:Label").selectSingleNode("a:UserLocalizedLabel").selectSingleNode("a:Label").text;
        var option = document.createElement("option");
        option.text = textPick;
        myPickList.add(option, myPickList.options[null]);
	}
</SCRIPT>
</BODY></HTML>

Se não quiserem andarem a fazer copy paste do código tenho aqui a solution que contém este código. É fazer download e importá-lo na vossa organização e lançar num botão por exemplo.

NOTA: Atenção ao cache dos web resources, façam iisreset e caso não resulte forcem o reload (CTRL+F5) na própria páguina que contiver este web resource

ListadeEstados_1_0_0_0.zip (3,29 kb)

Tags: , , , ,

CRM 2013 - Obter o modo ou razão de um registo gravado

by Pedro Azevedo 17. November 2013 09:27

 

Boas pessoal,

Na sequência dos posts de manusear o AutoSave achei pertinente mostrar que como é que no CRM 2013 se obtêm o modo ou a razão que um registo é gravado, através da função getSaveMode, eis os valores que a função retorna:

 

Entity

Event Mode

Value

All

Guardar

1

All

Guardar e Fechar

2

All

Guardar e Novo

59

All

AutoSave

70

Activities

Guardar como Concluída

58

All

Ativar

5

All

Reativar

6

User or Team owned entities

Atribuir

47

Email (E-mail)

Enviar

7

Lead

Qualificar

16

Lead

Desqualificar

15

 

O problema é que esta lista é muito diminuta comparada com a que existia no CRM 2011, podemos comparar no seguinte artigo. Apanhar quando por exemplo uma Proposta era ganha ou perdida era muito usado, pois permitia agirmos mediante um destes eventos. No CRM 2013 não é lançado nenhum evento de guardar.

Para experimentarem coloquem o seguinte código no onsave do formulário:

function onclosequote(context)
{
   var saveMode = context.getEventArgs().getSaveMode();
   alert(saveMode);
}

E não se esqueçam de dizer para passar no primeiro parâmetro o contexto para que o método consiga executar.

Neste momento e dependendo do que queremos fazer podemos contornar, tenho contornado este problema utilizando plugins. Ou no onload do evento por exemplo da encomenda, onde verifico qual o tipo de formulário e se tem alguma proposta associada.

Até a próxima.

Tags: , ,

Tudo sobre WebResources III

by Pedro Azevedo 30. August 2013 16:21

Boas pessoal,

Estou de volta com o último post sobre o assunto dos WebResources agora vendo casos práticos de onde posso usar esta funcionalidade:

Dashboard

Para adicionar é semelhante ao que fizemos quando adicionamos um WebResource ao formulário. Vejam o resultado:

Em relação ao exemplo anterior retirei o acesso ao formulário pois aqui não tínhamos essa situação. Mais de resto o código é igual.

 

Navegação no Formulário

Vamos analisar como podemos adicionar no Sitemap:

 

 Vejam o resultado:

Até aqui nada de especial, conseguimos referenciar os WebResources sem uma única linha de código, vamos ver casos mais complicados.

SiteMap – Sub Area

Para adicionarmos este tipo de link é necessário trabalharmos sobre o sitemap, por isso vamos criar um WebResource com o sitemap e vamos utilizar uma ferramenta SiteMapEditor:

Vejam a forma de referenciar o WebResource através da directiva $webresource. Se não utilizarmos esta ferramenta, teríamos que ter adicionar o SiteMap a uma solução, exportar essa solução e mexer directamente no XML e afectar as propriedades que afectamos em cima e depois importar novamente esta solução.

Vejam o resultado:

 Falta-nos falar de um último sítio onde poderemos usar os WebResources, que é na ribbon. Ora na ribbon o interessante é executarmos código Javascript por exemplo para escondermos ou desabilitarmos um botão ou para abrir um WebResource. Já tenho planeado um post para falar como desabilitar um botão na ribbon, por isso aqui vamos abrir um WebResource.

Apenas como exemplo e sem grande ciência adicionei um botão (utilizei a ferramenta RibbonWorkbench) e adicionei uma acção para abrir um URL, neste caso coloquei como endereço $webresource:ret_testewr como podem verificar na imagem.

E podem ver a sua execução:

Para complementar a situação anterior, existe outra forma de abrirmos um WebResource, utilizando a função openWebResource que apareceu com o UR8. Para a utilizarmos em vez de colocarmos um acção do tipo URL vamos criar uma acção para chamar uma função Javascript:

Antes de mostrar o código da função openwr, reparem como ele referenciou o WebResource onde está a função, através da directiva $webresource. A função openwr é muito simples:

function openwr() {
  Xrm.Utility.openWebResource("ret_testewr");
}

E assim finalizamos esta temática.

 

Até a próxima.

Tags: , , ,

Obter dados de um SubGrid

by Pedro Azevedo 8. August 2013 01:12

Boas pessoal,

Tinha um requisito que era calcular o valor médio de uma lista. Neste caso vou demonstrar através da lista de oportunidades em que o objectivo é calcular a média do valor dos produtos inseridos.

Bom a minha primeira solução foi obter os registos directamente a grid, já que lá estavam os dados, assim fiz então baseei-me em alguns blogs ora vejam:

http://lakshmanindian.wordpress.com/2012/05/25/retrieve-subgrid-rows-in-crm-2011-using-jscript/

http://stackoverflow.com/questions/17651752/how-to-retrieve-all-record-ids-of-a-sub-grid-in-crm-2011-using-javascript

http://crmbusiness.wordpress.com/2011/05/19/crm-2011-javascript-and-subgrids-code-example/

Mas sempre tive problemas:

  • ·         Com o ir buscar a grid, com o código pré e após UR12
  • ·         Como ir buscar todos os elementos da grid, pois alguns dos métodos só vão buscar os registos que estão visíveis.
  • ·         Ter os campos necessários na lista

Então passei a enveredar por outra solução que é realizar uma query odata para obter todos os registos e apenas os campos que necessito. Tenho um overhead de obter todos os dados novamente, mas tenho uma solução que sei que vai perdurar no tempo pois qualquer mudança na forma de obter os registos de uma subgrid vou ficar imune.

Independentemente da forma como vou buscar os produtos da oportunidade terei que adicionar um Recurso Web em que neste caso terá apenas uma caixa de texto. Aqui podia não ter optado por um Recurso Web e ter colocado num campo que criasse como atributo da oportunidade. O Recurso Web permite ter campos dinâmicos, no requisito que referi tinha que realizar várias análises de qual o produto com maior valor, qual o produto com mais unidades, qual o produto que demorava mais a instalar. Por essa razão e porque o vosso caso pode ser semelhante vou manter a solução de um Recurso Web. Podem ver neste artigo um pouco mais sobre Recurso Web.

Vamos então adicionar um Recurso Web a nossa solução:

E colocar o seguinte código:

<HTML><HEAD><BASE>
<META charset=utf-8></HEAD>
<BODY contentEditable=true><INPUT id=quantity name=quantity>

<SCRIPT type=text/javascript src="ret_jquery"></SCRIPT>
<SCRIPT src="ClientGlobalContext.js.aspx"></SCRIPT>
<SCRIPT type=text/javascript>
  $(document).ready(function(){
    var entityId = window.parent.Xrm.Page.data.entity.getId();
    var serverUrl = window.location.protocol + "//" + window.location.host + "/" + Xrm.Page.context.getOrgUniqueName();
    var ODATA_ENDPOINT = "/XRMServices/2011/OrganizationData.svc";

    var odataSetName = "OpportunitySet";
    var relName = "/product_opportunities";

    var odataSelect = serverUrl + ODATA_ENDPOINT + "/" + odataSetName + "(guid'" + entityId.substr(1,36) + "')" + relName;

    return $.ajax({
       type: "GET",
       contentType: "application/json; charset=utf-8",
       datatype: "json",
       url: odataSelect,
       beforeSend: function (XMLHttpRequest) { XMLHttpRequest.setRequestHeader("Accept", "application/json"); },
       success: function (data, textStatus, XmlHttpRequest){
              RetrieveMultipleEntities(data.d.results);
       },
       error: function (XmlHttpRequest, textStatus, errorThrown) { alert('TextStatus:' + textStatus + "\nerror:" + errorThrown); }
    });
  });

  function RetrieveMultipleEntities(ManyEntities)
  {
    var total = 0;
    for( i=0; i< ManyEntities.length; i++){
      var Entity = ManyEntities[i];
      total = total + parseFloat(Entity.BaseAmount.Value)
    }
    $('#quantity').val(total/ManyEntities.length);
  }
</SCRIPT>

<P><FONT size=2 face=Tahoma></FONT> </P></BODY></HTML>

E o nosso desenvolvimento acabou, basta adicionarmos este Recurso Web dentro do formulário (que vamos fazer mais tarde) e este código funciona as mil maravilhas. Vamos explorar alguns pormenores que apesar de já estar explicado em outros artigos que escrevi, nunca é demais referir:

·         Este código pressupõe termos adicionado um Recurso Web com a biblioteca jQuery, vejam aqui como referencio:

<SCRIPT type=text/javascript src="ret_jquery"></SCRIPT>

·         A referência a biblioteca para podermos aceder ao contexto da entidade:

<SCRIPT src="ClientGlobalContext.js.aspx"></SCRIPT>

·         A construção do URL para obter o endpoint OData:

var serverUrl = window.location.protocol + "//" + window.location.host + "/" + Xrm.Page.context.getOrgUniqueName();
var ODATA_ENDPOINT = "/XRMServices/2011/OrganizationData.svc";

Vejam a utilização do Xrm.Page.context para obter o contexto, que sem a referenciação a biblioteca anterior não funcionava. O resultado da variável deve ficar algo do género:

http://<server>/<organization>/XRMServices/2011/OrganizationData.svc

·         Por último a obtenção do GUID do registo onde estamos:

var entityId = window.parent.Xrm.Page.data.entity.getId();

Vejam como eu referencio o Xrm.Page para poder aceder por exemplo aos campos do formulário.

Se tivéssemos optado por não utilizar um Recurso Web o código era semelhante, não teríamos que referenciar as duas bibliotecas Javascript (apesar de termos que incluir a biblioteca jQuery na solução) e na obtenção do id do registo podemos retirar a parte do window.parent.

Como tinha referenciado agora basta adicionarmos o Recurso Web ao formulário: 

 

Vejam o resultado final:

 

Até a próxima.

Tags: , , , ,

Tudo sobre WebResources II

by Pedro Azevedo 25. June 2013 22:07

 Boas pessoal,

Vamos continuar o exemplo anterior e vamos tentar complicar um pouco para percebermos como tirar o maior partido desta funcionalidade.

Vamos então perceber como podemos referenciar um WebResource directamente. Agora quero colocar “OLA Nome do Cliente Potencial”. Para isso vou usar jQuery então adicionei a biblioteca do jQuery a solução através de um WebResource e adicionei o seguinte código:

$(document).ready(function() {
  var nomeCompleto = window.parent.Xrm.Page.getAttribute("fullname").getValue();
  $('#sayHello').append(nomeCompleto);
});

Se voltar abrir um Cliente Potencial vai dar o erro seguinte:

Isto porque mesmo tendo na solução um WebResource com a biblioteca jQuery, o CRM não vai carregar, por essa razão vamos ter que adicionar uma referência ao WebResource que tem a biblioteca jQuery, desta maneira:

<SCRIPT type=text/javascript src="ret_jquery"></SCRIPT>

Gravando, publicando e abrindo novamente o registo temos este resultado:

 

Este tipo de referenciação funciona se tivermos numa estrutura flat. Se tivermos os WebResources em pastas teremos que nos posicionar. Imaginemos que o WebResource ret_testewr estava em html/ret_testewr e que a biblioteca jQuery estava em scripts/ret_jquery, então teríamos que referenciar da seguinte maneira:

<SCRIPT type=text/javascript src="../scripts/ret_jquery"></SCRIPT>

Aqui estamos a usar um URL relativo e é assim que devemos usar, outra maneira era referenciarmos o WebResource da seguinte maneira:

<SCRIPT type=text/javascript src="http://<server>/<organization>/webresources/ret_jquery"></SCRIPT>

Colocar todo o caminho vai funcionar e muitas das vezes é o URL que eu coloco no browser para testar. Mas isto é muito mau, basta dizer que você não vai conseguir passar esse código para outro ambiente de desenvolvimento, sem ir alterar o código, o que é mau, por isso devemos sempre usar o URL relativo. Já para não falar se tivermos os dois tipos de deploy (online e on-premise) que o URL muda completamente.

Uma pergunta válida é o porquê de querermos estruturar os WebResources na forma de pastas. Não é uma obrigação mas sim uma boa prática, principalmente se um utilizador pertencer a mais que uma organização num servidor. Isto porque quando colocamos /WebResources/<nome web resource> ele vai aceder a organização por defeito do utilizador. Isto poderá gerar um erro do tipo “File Not Found” caso ele esteja aceder a uma organização que não a de por defeito e que esse WebResource não exista na organização por defeito. Então como boa prática deveria-se ter a a seguinte estrutura:

“_<prefixo da organização>”/<nome da pasta>/<nome do Recurso Web>

Já vimos que conseguimos aceder ao formulário então e como conseguimos aceder a coisas do CRM fora do formulário, por exemplo saber o nome da organização, para isso usamos a seguinte função:

Xrm.Page.context.getOrgUniqueName()

Se executarmos este código obtemos o seguinte erro:

 

Mas em cima colocamos o prefixo window.parent mesmo pondo este prefixo não funciona porque ele serve para aceder ao formulário. Para que isto funcione necessitamos de referenciar uma outra biblioteca (não esquecer que estou numa estrutura flat de WebResources, se este WebResource tivesse dentro de uma pasta teria que reposicionar):

 

<SCRIPT src="ClientGlobalContext.js.aspx"></SCRIPT>

 

Esta biblioteca permite acedermos a outros parâmetros, vejam aqui a lista:

  •     getAuthenticationHeader

Cabeçalho de autenticação SOAP para os WebServices do Dynamics CRM 4.0.

  •     getOrgLcid

Retorna o valor LCID da linguagem de base da organização.

  •     getOrgUniqueName

Retorna o nome da organização.

  •    getQueryStringParameters

Retorna um array de chave valor representando os argumentos em query string passados a página.

  •   getServerUrl

Retorna o URL base do servidor. Quando se está no modo offline ele retorna localhost.

  •   getUserId

Retorna o Guid do utilizador actual.

  •    getUserLcid

Retorna o valor LCID da linguagem seleccionada pelo utilizador.

  •   getUserRoles

Retorna um array de Guid que representam as roles associadas ao utilizador.

 Existe outra forma de referenciar WebResources através da directiva “$webresource:<Nome do WebResource>”, devemos usar sempre este tipo de referenciação, pois ele resolve as dependências de solução. Mas apenas podemos usar quando estamos a usar o SiteMap ou Ribbon. A questão das dependências é muito importante porque ele vai conseguir gerar o URL e com isso vai possibilitar haver cache do WebResource, vejam o URL para referenciar o WebResource:

/<organização>/%7B635318792490000000%7D/WebResources/ret_testewr

Se colocarmos um URL absoluto para além dos problemas anteriores ainda vamos perder a capacidade de fazer cache. Existe um artigo muito bom e que explica também como podemos gerar este URL.

Em relação a como aplicamos a directiva na prática vamos ver mais detalhes no próximo post.

 

Até a próxima

 

Fontes:

http://msdn.microsoft.com/en-us/library/gg328541.aspx

Tags: , , ,

About

Muito bem casado, Pai babado e um gosto muito grande pela tecnologia.

Tenho um lema "Sharing is Learning"

Mais aqui -> http://www.psazevedo.com

Month List