Novos métodos na API de Javascript–Autocomplete

by Administrator 20. December 2016 15:19

Boas pessoal,

Continuando os posts de Javascript, como viram existiu uma grande mudança  com os processos.

Entre o CRM 2015 e o CRM 2016 a grande diferença está na disponibilização de controlos AutoComplete e a disponibilização via API, onde temos os seguintes métodos:

 

Apenas com estes métodos não conseguiríamos fazer nada, pois temos que apanhar quando o utilizador começa a escrever. Para ajudar foram acrescentados os seguintes métodos:

 

Para além de podermos adicionar e remover um handler para o evento KeyPress ainda podemos forçar o evento KeyPress.

Vamos então fazer um exemplo da utilização destes métodos, para isso tentei pesquisar um webservice público e encontrei um muito giro que tem informações sobre países do mundo, então o objetivo foi ter um campo que definisse o continente e filtrar logo a partida e depois houvesse um autocomplete no campo do país:

image

Como podem ver pela imagem esta melhoria traz um novo potencial ao Dynamics 365.

Até a próxima

Tags: , , ,

Novos métodos na API de Javascript–Dynamics 365

by Pedro Azevedo 15. December 2016 16:59

Boas pessoal,

Continuando a senda dos novos métodos da API de Javascript, hoje apresento-vos os métodos para gerir as instâncias de processos e métodos para sabermos quando o estado de um processo é modificado.

Isto porque com a chegada do Dynamcis 365 novas funcionalidades foram acrescentadas, e os processos de negócio não fugiram a regra, a Microsoft tenta sempre que estas mudanças também sigam a mesma atualização.

O que temos de novo nos processos de negócio? Bom eu sei que o que foi mais partilhado foi a nova interface e está linda! Mas não é o assunto deste post, queria falar das outras novidades que é a possibilidade de podermos ter várias instâncias de processos a decorrer ao mesmo tempo.

Para dar suporte a esta nova funcionalidade a Microsoft acrescentou mais duas funções:

Chamo mais uma vez atenção que estas funções trabalham com instâncias de processos e não com processos como vimos nos posts anteriores.

Então para começar escolhi a entidade Lead por já ter um processo de negócio por defeito, para além do que vem por defeito criei outro processo, como podem ver pela imagem abaixo ele apenas deteta um processo instanciado, isto é porque apesar de ter dois tipos de processo disponíveis apenas um está neste momento instanciado:

image

A seguir vou instanciar o outro e correr novamente a página, como podem ver já mostra dois processos instanciados:

image

image

Este foi o código que utilizei para obter estas imagens:

Como em vários outros métodos para obter os dados temos que passar um callback e que recebe um objeto com os vários processos, os vários processos vêm como propriedades desse objeto e dentro de cada as várias propriedades que podemos obter, como vamos ver mais a frente necessitamos por exemplo do guid para fazermos operações.

Agora que tenho dois processos instanciados posso neste momento trocar entre os duas instâncias, como tinha mostrado tinha ativado o segundo processo agora vou trocar novamente para o primeiro:

Aqui temos então o método setActiveProcessInstance que recebe o guid do processo a ser ativado e um callback a dizer se essa operação teve sucesso ou não.

Por hoje foi tudo, mas ao escrever este post senti falta de explicar um pouco mais toda esta funcionalidade dos processos de negócio que com esta versão sofreu algumas alterações fica prometido um post a explicar toda esta funcionalidade.

Até a próxima

Tags: , , ,

Novos métodos na API de Javascript – Processos II

by Pedro Azevedo 14. November 2016 16:38

Boas pessoal,

Depois do post anterior a falar sobre a barra de processos e de ter falado apenas do processo vamos agora falar sobre os stages. Vamos ver a que dados podemos aceder, sobre os stages:

var StageCollection = activeProcess.getStages();
 
StageCollection.forEach(function (stage, n) {
    Xrm.Utility.alertDialog("Categoria Stage -> " + stage.getCategory().getValue());
    Xrm.Utility.alertDialog("Id do Stage -> " + stage.getId());
 
    Xrm.Utility.alertDialog("Nome da Entidade da Stage -> " + stage.getEntityName());
    Xrm.Utility.alertDialog("Nome da Stage -> " + stage.getName());
    Xrm.Utility.alertDialog("Estado da Stage -> " + stage.getStatus());
 
    Xrm.Utility.alertDialog("Número de Steps -> " + stage.getSteps().getLength());
});

E este é o resultado:

image_thumb11_thumb

image_thumb13_thumb

image_thumb15_thumb

image_thumb16_thumb

image_thumb17_thumb

image_thumb18_thumb

image_thumb23_thumb

image53_thumb_thumb

image_thumb24_thumb

Não coloquei aqui os passos todos, podemos ver todas as informações do primeiro stage, que é o que está ativo, depois mostro o próximo stage e vejam que já está noutra entidade e que neste momento está inativo.

Vamos ver como podemos obter apenas o stage que está ativo:

function onload() {
    var activeStage = Xrm.Page.data.process.getActiveStage();
    Xrm.Utility.alertDialog(activeStage.getId() + " - " + activeStage.getName());
}

image_thumb1_thumb

Como podemos ver na imagem temos o guid e o nome do stage que está ativo. Claro que podemos ativar um stage com o seguinte código:

Xrm.Page.data.process.setActiveStage("15322A8F-67B8-47FB-8763-13A28686C29D", function(result) {
    if(result == "success") {
        Xrm.Utility.alertDialog("Success");
    } else {
        Xrm.Utility.alertDialog("Invalid");
    }
});

No post anterior não referi mas a função que colocar outro stage ativo e semelhança do que coloca o processo ativo, a função para além do sucesso e insucesso, e principalmente no caso de insucesso ele dá-nos mais razões para o insucesso, vamos ver o exemplo para este caso que outros valores podem ser passados a função:

  • crossEntity – o stage terá que ser sobre a entidade corrente;
  • invalid – pode ser três razões:
    • O Id do stage não é válido
    • O stage ativo não é o que está selecionado
    • O registo não está salvo
  • unreachable – o passo está noutro path
  • dirtyForm – se os dados da página não está gravado

Um método interessante é o getActivePath que nos dá qual é o caminho dos stages que faltam baseado nas regras de ramificações (mediante valores do formulário o nosso processo de negócio pode ter várias ramificações) e os dados presentes no registo, vejam um exemplo:

var activePathCollection = Xrm.Page.data.process.getActivePath();
activePathCollection.forEach(function (stage, n) {
    Xrm.Utility.alertDialog("Id do Stage -> " + n);
    
    var stageSteps = stage.getSteps();
    stageSteps.forEach(function (step, i) {
        Xrm.Utility.alertDialog("Nome do Passo -> " + step.getName());
        Xrm.Utility.alertDialog("Atributo do Passo -> " + step.getAttribute());
        Xrm.Utility.alertDialog("Passo Necessário -> " + step.isRequired());
    })
});

Com o método anterior, também vemos os métodos que podem ser executados sobre cada step, ou seja, podemos ver o nome do passo, qual o atributo e saber se esse atributo é necessário ou não.

Só nos falta falar nos eventos, aqui a API possibilita-nos saber quando um processo é alterado ou selecionado, da seguinte maneira:

Xrm.Page.data.process.addOnStageChange(function () {
    Xrm.Utility.alertDialog("O Stage foi modificado");
});
 
Xrm.Page.data.process.addOnStageSelected(function () {
    Xrm.Utility.alertDialog("O Stage foi selecionado");
});

image_thumb[3]

image_thumb[1]

A primeira mensagem é quando eu digo que quero mudar de processo, ou seja, disse que quero ir para a próxima fase. A segunda mensagem é quando eu seleciono, ou seja, por exemplo quero ver o que está na próxima fase, ou o que já foi escolhido na fase anterior.

Até a próxima

Tags: , ,

Novos métodos na API de Javascript–Processos I

by Pedro Azevedo 4. October 2016 19:30

Boas pessoal,

Há algum tempo atrás realizei alguns posts sobre a API de Javascript no CRM 2013, com o CRM 2015 apareceram novos métodos. A principal alteração foi ao nível da barra de processos de negócio, vamos conhecer melhor estas alterações:

image

Assinalado a vermelho vemos o que foi adicionado entre as versões 2013 e 2015, que tem a ver com os processos, em que estes podem ser divididos em fases (stages) e passos (steps).

image

Começando pelo Xrm.Page.ui.process, onde conseguimos controlar a visibilidade e colocar expandida ou recolhida:

Xrm.Page.ui.process.setDisplayState("collapsed");
Xrm.Page.ui.process.setVisible(false);

image

image

Podemos ver o comportamento com cada um dos código individualmente executados. Para além de definirmos, podemos ver o estado:

//Pode retornar "collapsed" ou "expanded"
Xrm.Page.ui.process.getDisplayState();
 
//Devolve true ou false mediante estar visível ou não
Xrm.Page.ui.process.getVisible();

Passando para o Xrm.Page.data.process, vamos saber qual é o processo selecionado, para isso vamos chamar o seguinte script:

var activeProcess = Xrm.Page.data.process.getActiveProcess();
Xrm.Utility.alertDialog(activeProcess.getId() + " - " + activeProcess.getName());

image

Como podemos ver na imagem temos o guid e o nome do processo que está ativo. Agora vamos forçar que o processo mude para o próximo ou anterior stage, com o seguinte código:

function onload()
    var activeProcess = Xrm.Page.data.process.getActiveProcess;
    if (activeProcess != null) {
        Xrm.Page.data.process.movePrevious(function (result) {
            ReadResult(result)
        });
    }
    
    var activeProcess = Xrm.Page.data.process.getActiveProcess();
    if (activeProcess != null) {
        Xrm.Page.data.process.moveNext(function (result) {
            ReadResult(result)
        });
    }
}
 
function ReadResult(result){
    if (result == "success") {
        Xrm.Utility.alertDialog("Funciona");
    } else {
        Xrm.Utility.alertDialog("Temos um problema");
    }
}

image

Na imagem vemos o que aconteceu quando tentamos ir para o próximo stage do nosso processo. Aqui dá um problema porque para avançar a Lead é necessário Qualificar, ou seja, necessita de uma oportunidade criada.

Neste momento temos estado a mexer no processo que está selecionado. Mas uma das funcionalidades mais interessantes é o utilizador poder mudar de processo, então como posso eu mudar de processo com código? Vamos começar por obter todos os processos que um utilizador pode mudar, com o seguinte código:

Xrm.Page.data.process.getEnabledProcesses(function (processes) {
    for (var processId in processes) {
        Xrm.Utility.alertDialog("id: " + processId + " name: " + processes[processId]);
    }
});

image

image

Com um Id do processo que quero mudar, posso mudar com o seguinte código:

Xrm.Page.data.process.setActiveProcess("c0d9760e-b261-4a3b-8355-f75eb45b9c1c", function(result) {
    if(result == "success") {
     alert("Success");
    } else {
     alert("Invalid");
    }
});

image

Em relação a apenas a parte do processo é tudo, no próximo artigo falarei, sobre os Stages.

 

Até a próxima

Tags: , ,

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: , , ,

Limpar contactos ou contas de atividade marketing falhadas

by Pedro Azevedo 11. December 2014 11:10

Boas pessoal,

Fui deparado com um problema que apareceu no fórum do Dynamics CRM em português. O objetivo do desafio passava por ter uma maneira mais expedita para eliminar\desativar contas ou contactos que tenham dado erro aquando de uma distribuição de uma campanha.

O primeiro desafio foi obter estes erros. Na interface gráfica os erros aparecem no seletor “Falhas”. Então a minha primeira tentativa foi através da interface REST e sobre a entidade CampaignActivity (Atividades de Campanha) mas sem sucesso porque aqui não aparecem os erros.

 

Depois de ver melhor a imagem reparei que a vista se chama “Vista associada de falhas de operações em massa”. Logo fui estudar a entidade BulkOperation mas aqui mais uma vez só encontrei a atividade chamada “Distribuição de Email” e não as atividades geradas. Depois de ver todas as relações da entidade, cheguei a entidade BulkOperationLog esta sim tem todas as atividades que foram geradas, as que correram bem e as que deram erro. Como não existe muita informação sobre esta entidade, executei uma chamada para ver o esquema desta entidade:

Esta entidade tem um campo o ErrorNumber que se tiver valor diferente de 0 é porque esta atividade deu erro. Infelizmente só tem mesmo o número do erro e não tem qualquer descrição. Bem este foi outro desafio. Eu já tinha escrito um post sobre erros e explicado como chegar a descrição. Para já vai ficar com o número do erro mas é meu objetivo de criar algo para me dar também a descrição do erro.

Tendo então descoberto qual a entidade que tinha a informação que necessitava e para além do erro ainda possuí qual o cliente\contacto através do campo RegardingObjectId. Podem ver a pré-visualização do resultado final:

Para listar esta informação utilizei um plugin jQuery chamado jqGrid e usei esta versão, já que a versão que tinha utilizado até aqui passou a ter uma licença paga. Para aqui também já evoluí este componente do jqGrid para facilitar listar entidades do CRM, mais tarde vou escrever um post sobre este componente.

Falando mais concretamente da implementação a primeira coisa foi colocar um botão ao nível da atividade de campanha. Botão este que irá chamar um recurso web html, este botão foi criado mais uma vez com recurso a ferramenta Ribbon Workbench:

Este botão vai chamar uma função Javascript onde é passado qual a campanha que estamos a falar neste momento e com isso mostrar todas as atividades que falharam:

function openWebResource(campaignId) {
    var customParameters = encodeURIComponent("?campaignId=" + campaignId);
    Xrm.Utility.openWebResource("new_failedcampaign/failedcampaign.html", customParameters, 300,300);
}

Este recurso web tem o plugin jqGrid e a sua inicialização, o método fetchGridData vai ser responsável por ir obter as atividades com falha e preencher a tabela.

$("#jqGrid").jqGrid({
        datatype: "local",
        height: '100%',
        colModel: [ {label: 'Id Registo', name: 'ObjectId', hidden: true},
                    {label: 'Tipo Registo', name: 'TypeRegister', width: 90},
                    {label: 'Nome do Registo', name: 'NomeRegisto', width: 200},
                    {label: 'Erro', name: 'ErrorNumber', width: 140 }
        ],
        viewrecords: true, // show the current page, data rang and total records on the toolbar
        caption: 'Carregar Atividades de Campanha Falhadas',
        pager: "#jqGridPager",
        gridComplete: initGrid
    });

    fetchGridData();
});

function fetchGridData() {
    var campaignActivityId;
    if (location.search != "") {
        vals = decodeURIComponent(location.search).split("=");
        campaignActivityId = vals[2].toString().split(',');
    }

    var gridArrayData = [];
    var odataquery = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
    odataquery += "/CampaignActivitySet(guid'" + campaignActivityId + "')";
    odataquery += "?$expand=CampaignActivity_BulkOperations";
    odataquery += "&$select=CampaignActivity_BulkOperations/ActivityId";

    $.getJSON(odataquery, function(data) {
        if(data.d.CampaignActivity_BulkOperations) {
            var bulkOperations = data.d.CampaignActivity_BulkOperations.results;
            for (var idxBulkOper = 0; idxBulkOper < bulkOperations.length; ++idxBulkOper) {
                var activityId = bulkOperations[idxBulkOper].ActivityId;

                var odataquery2 = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
                odataquery2 += "/BulkOperationSet(guid'" + activityId + "')";
                odataquery2 += "?$expand=BulkOperation_logs";
                odataquery2 += "&$select=BulkOperation_logs/ErrorNumber,BulkOperation_logs/RegardingObjectId";

                $.getJSON(odataquery2, function(data) {
                    var bulkLogs = data.d.BulkOperation_logs.results;

                    for (var i = 0; i < bulkLogs.length; i++) {
                        gridArrayData.push({
                            ObjectId: bulkLogs[i].RegardingObjectId.Id,
                            TypeRegister: bulkLogs[i].RegardingObjectId.LogicalName,
                            NomeRegisto: bulkLogs[i].RegardingObjectId.Name,
                            ErrorNumber: bulkLogs[i].ErrorNumber
                        });
                    };

                    $("#jqGrid").jqGrid('setGridParam', { data: gridArrayData});
                    $("#jqGrid").trigger('reloadGrid');
                });
            }
        }
    });
}

Quero realçar o seguinte código:

var odataquery = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
odataquery += "/CampaignActivitySet(guid'" + campaignActivityId + "')";
odataquery += "?$expand=CampaignActivity_BulkOperations";
odataquery += "&$select=CampaignActivity_BulkOperations/ActivityId";

Este código vai obter todas as atividade de campanha e sobre cada uma obtenho os erros que aconteceram, como se pode ver a seguir:

var odataquery2 = Xrm.Page.context.getClientUrl() + "/XRMServices/2011/OrganizationData.svc";
odataquery2 += "/BulkOperationSet(guid'" + activityId + "')";
odataquery2 += "?$expand=BulkOperation_logs";
odataquery2 += "&$select=BulkOperation_logs/ErrorNumber,BulkOperation_logs/RegardingObjectId";

Sobre o plugin jqGrid usei a extensão Context Menu para poder realizar ações sobre os registos listados, entre eles o inativar e o apagar. Para inativar usei a mensagem SetStateRequest, vejam aqui a função que utilizei:

function SetStateRequest(active, entity) {
    var grid = $("#jqGrid");
    var rowKey = grid.getGridParam("selrow");
    var rowData = grid.getRowData(rowKey);
    var entityId = rowData.ObjectId;

    var state, status;
    if(active){
        state = 0;
        status = 1;
    }
    else{
        state = 1;
        status = 2;
    }

   var requestMain = ""
   requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
   requestMain += "  <s:Body>";
   requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
   requestMain += "      <request i:type=\"b:SetStateRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
   requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
   requestMain += "          <a:KeyValuePairOfstringanyType>";
   requestMain += "            <c:key>EntityMoniker</c:key>";
   requestMain += "            <c:value i:type=\"a:EntityReference\">";
   requestMain += "              <a:Id>" + entityId + "</a:Id>";
   requestMain += "              <a:LogicalName>" + entity + "</a:LogicalName>";
   requestMain += "              <a:Name i:nil=\"true\" />";
   requestMain += "            </c:value>";
   requestMain += "          </a:KeyValuePairOfstringanyType>";
   requestMain += "          <a:KeyValuePairOfstringanyType>";
   requestMain += "            <c:key>State</c:key>";
   requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
   requestMain += "              <a:Value>" + state + "</a:Value>";
   requestMain += "            </c:value>";
   requestMain += "          </a:KeyValuePairOfstringanyType>";
   requestMain += "          <a:KeyValuePairOfstringanyType>";
   requestMain += "            <c:key>Status</c:key>";
   requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
   requestMain += "              <a:Value>" + status + "</a:Value>";
   requestMain += "            </c:value>";
   requestMain += "          </a:KeyValuePairOfstringanyType>";
   requestMain += "        </a:Parameters>";
   requestMain += "        <a:RequestId i:nil=\"true\" />";
   requestMain += "        <a:RequestName>SetState</a:RequestName>";
   requestMain += "      </request>";
   requestMain += "    </Execute>";
   requestMain += "  </s:Body>";
   requestMain += "</s:Envelope>";
   var req = new XMLHttpRequest();
   req.open("POST", _getServerUrl(), true)
   // Responses will return XML. It isn't possible to return JSON.
   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");
   var successCallback = null;
   var errorCallback = null;
   req.onreadystatechange = function () { SetStateResponse(req, successCallback, errorCallback); };
   req.send(requestMain);
}

Para apagar o registo usei a interface REST:

function DeleteEntity(accountId, entity) {
    var serverUrl = Xrm.Page.context.getClientUrl();
    var ODATA_ENDPOINT = "/XRMServices/2011/OrganizationData.svc/" + entity + "Set";
    var ODataPath = serverUrl + ODATA_ENDPOINT;
    $.ajax({
        type: "POST",
        contentType: "application/json; charset=utf-8",
        datatype: "json",
        url: ODataPath + "(guid'" + accountId + "')",
        beforeSend: function (XMLHttpRequest) {
            XMLHttpRequest.setRequestHeader("Accept", "application/json");
            XMLHttpRequest.setRequestHeader("X-HTTP-Method", "DELETE");
        },
        error: function (xmlHttpRequest, textStatus, errorThrown) {
            alert("Status: " + textStatus + "; ErrorThrown: " + errorThrown);
        }
    });
} 

Reparem que para apagar temos que especificar o header “X-HTTP-Method” como delete.

Nesta solução a grande dificuldade foi mesmo a obtenção do tipo de erros referentes a esta comunicação o resto são coisas que já tínhamos visto anteriormente. Deixo-vos o ficheiro HTML do recurso web que usei, bem como a solução específica para o Dynamics CRM 2015. O meu objetivo é tornar esta solução num género de AddOn tanto para qualquer versão do CRM.

 

Até a próxima.

Tags: , , , , , , ,

CRM 2013 Javascript Object Model - Control

by Pedro Azevedo 30. November 2014 22:04

Boas pessoal,

Hoje na sequência dos posts do object model do Javascript, desta vez vamos ver as opções que temos ao nível do controlo.

getAttribute

function getAttributeTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getAttribute().getValue());
}

getControlType

Valores possíveis de retorno:

Return Value

Description

standard

A Standard control.

iframe

An IFRAME control

lookup

A Lookup control.

optionset

An OptionSet control

subgrid

A subgrid control

webresource

A web resource control

notes

A Notes control

function getControlTypeTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getControlType());
}

getDisabled

function getDisabledTest() {
	Xrm.Utility.alertDialog("subject está com disabled a " + Xrm.Page.getControl("subject").getDisabled());
	Xrm.Utility.alertDialog("lastusedincampaign está com disabled a " + Xrm.Page.getControl("lastusedincampaign").getDisabled());
}

setDisabled

function setDisabledTest() {
	Xrm.Page.getControl("subject").setDisabled(true);
} 

getLabel

function getLabelTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getLabel());
}

setLabel

function setLabelTest() {
	Xrm.Page.getControl("subject").setLabel("Nova Label");
}

getName

function getNameTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getName());
}

getParent

Dá a secção onde está dentro.

function getParentTest() {
	Xrm.Utility.alertDialog(Xrm.Page.getControl("subject").getParent().getLabel ());
} 

getVisible

function getVisibleTest() {
	Xrm.Utility.alertDialog("O controlo subject está com visible a " + Xrm.Page.getControl("subject").getVisible());
}

setVisible

function setVisibleTest() {
	Xrm.Page.getControl("subject").setVisible(false);
}

setFocus

function setFocusTest() {
	Xrm.Page.getControl("subject").setFocus(true);
}

setNotification

function setNotificationTest() {
	Xrm.Page.getControl("subject").setNotification("Teste a função setNotification");
}

 

clearNotification

function clearNotificationTest() {
	Xrm.Page.getControl("subject").setNotification("Teste a função setNotification", "UMID");
	Xrm.Page.getControl("subject").clearNotification("UMID");
}

Até a próxima

Tags: , , ,

CRM 2013 Javascript Object Model - Lookup

by Pedro Azevedo 19. November 2014 19:50

Boas pessoal,

Continuando o conjunto de posts sobre a API de Javascript e também continuando o post de lookups no CRM 2013, hoje vou falar sobre o que temos na API de Javascript para interagir com os lookups. Este post já tinha sido feito para a versão 2011, por isso não vou voltar a falar das funções addCustomView, setDefaultView isso porque os métodos mantém os mesmos parâmetros e objetivos.

Ainda dentro dos métodos que existiam no CRM 2011, existe o getDefaultView, segue um exemplo:

function onload() {
        alert('default view->' + Xrm.Page.getControl('parentaccountid').getDefaultView());
}

Depois temos os métodos addCustomFilter, addPreSearch, removePreSearch, estes métodos com já foi dito aparecem na versão 2013. Para usar temos que fazer a combinação entre o addCustomerFilter e addPreSearch, para adicionamos um pré filtro temos que chamar o addPreSearch onde temos que adicionar um event handler que vai adicionar um filtro através do método addCustomerFilter:

function addPreFilter() {
	var city = Xrm.Page.getAttribute("address1_city").getValue();

	var filter ="<filter type='and'>" +
	             "<condition attribute='address1_city' operator='eq' value='" + city + "'/>" +
	             "</filter>";

	Xrm.Page.getControl("parentaccountid").addCustomFilter(filter);
}


function addPreSearchFilter() {
	Xrm.Page.getControl("parentaccountid").addPreSearch(addPreFilter);
}

Vejam as imagens em que na primeira não existe qualquer pré-filtro e na segunda aplicando o método anterior, ou seja, em vez de aparecer todos os registos consegue-se aplicar um filtro mais eficaz e com mais possibilidade de escolher sem ter que abrir uma nova janela.

 

Para se retirar este pré filtro temos que chamar o método removePreSearch e passar o event handler que passamos no método addPreSearch.

function removePreSearchFilter() {
	Xrm.Page.getControl("parentaccountid").removePreSearch(addPreFilter);
}

Este métodos podem ser de grande ajuda quando bem utilizados.

 

Até a próxima

Tags: , , , , ,

Criar um botão de cancelar para a Proposta

by Pedro Azevedo 7. November 2014 07:53

Boas pessoal,

O fórum ataca novamente Cool e foi colocada a seguinte questão. Quando temos uma proposta e criamos uma encomenda, a proposta fica num estado final como Ganha. Se por algum motivo a encomenda não se efetuar o Dynamics CRM permite o seu cancelamento:

A questão prende-se se quisermos cancelar a Proposta também e o que se constata é que não é possível realizar essa operação, apenas existe a opção eliminar:

Penso que a opção eliminar é muito drástica, e já agora ele não vai deixar porque existe a ligação com a encomenda. Então o objetivo de hoje é realizarmos um botão de cancelar. A nível funcional para algumas empresas não fará sentido eu mexer na proposta, já que se ela foi no fundo adjudicada então não devemos mudar o estado para sabermos que este negócio foi cancelado aquando da encomenda. Mas neste caso é apontado como uma incoerência de dados o que também se pode justificar.

Para começar nada melhor que verificarmos como é que mensagens temos para mudar o estado, todo o trabalho poderia ser feito com a mensagem SetStateRequest em que passamos qual o State e o Status que queremos que a proposta fique. Mas para podermos fechar a Proposta necessitamos de recorrer a mensagem CloseQuoteRequest, vejam o erro que ele dá se utilizarmos esta estratégia (SetStateRequest):

This message can not be used to set the state of quote to Closed. In order to set state of quote to Closed, use the CloseQuoteRequest message instead.

Para sermos brutos vamos tentar fechar a Proposta diretamente com o CloseQuoteRequest, como é de esperar ele não deixa pois temos que seguir o fluxo de estados estabelecido para a Proposta:

 E já agora vamos rever quais os valores que estão para cada um destes estados:

Estado

Valor

Razão do Estado

Valor

Ativo

1

Em progresso

Aberto

2

3

Fechado

3

Perdido

Cancelado

Revisto

5

6

7

Rascunho

0

Em Progresso

1

Ganha

2

Ganha

4

Vamos ver a mensagem que dá se mesmo assim insistirmos nesta estratégia, fechado através do CloseQuoteRequest estando a proposta num estado final:

The entity cannot be closed because it is not in the correct state.

Para colocarmos a Proposta em rascunho teremos que efetuar a seguinte chamada:

function SetStateRequest(state, status) {
     var recordId = Xrm.Page.data.entity.getId();

     var requestMain = ""
     requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
     requestMain += "  <s:Body>";
     requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
     requestMain += "      <request i:type=\"b:SetStateRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
     requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
     requestMain += "          <a:KeyValuePairOfstringanyType>";
     requestMain += "            <c:key>EntityMoniker</c:key>";
     requestMain += "            <c:value i:type=\"a:EntityReference\">";
     requestMain += "              <a:Id>" + recordId.replace('{', '').replace('}', '') + "</a:Id>";
     requestMain += "              <a:LogicalName>quote</a:LogicalName>";
     requestMain += "              <a:Name i:nil=\"true\" />";
     requestMain += "            </c:value>";
     requestMain += "          </a:KeyValuePairOfstringanyType>";
     requestMain += "          <a:KeyValuePairOfstringanyType>";
     requestMain += "            <c:key>State</c:key>";
     requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
     requestMain += "              <a:Value>" + state + "</a:Value>";
     requestMain += "            </c:value>";
     requestMain += "          </a:KeyValuePairOfstringanyType>";
     requestMain += "          <a:KeyValuePairOfstringanyType>";
     requestMain += "            <c:key>Status</c:key>";
     requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
     requestMain += "              <a:Value>" + status + "</a:Value>";
     requestMain += "            </c:value>";
     requestMain += "          </a:KeyValuePairOfstringanyType>";
     requestMain += "        </a:Parameters>";
     requestMain += "        <a:RequestId i:nil=\"true\" />";
     requestMain += "        <a:RequestName>SetState</a:RequestName>";
     requestMain += "      </request>";
     requestMain += "    </Execute>";
     requestMain += "  </s:Body>";
     requestMain += "</s:Envelope>";
     var req = new XMLHttpRequest();
     req.open("POST", _getServerUrl(), false)
     // Responses will return XML. It isn't possible to return JSON.
     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");
     var successCallback = null;
     var errorCallback = null;
     req.onreadystatechange = function () { ResponseOK(req); };
     req.send(requestMain);
 }

Para colocar em rascunho:

function putQuoteInDraft(){
     SetStateRequest(0, 1);
 }

Neste momento temos a Proposta em Rascunho, para verificarmos mais uma vez o fluxo de estados vamos tentar ativar a Proposta estando esta no estado final, mais uma vez recebemos uma mensagem de erro:

 The quote cannot be activated because it is not in draft state

Então temos que ativar a nossa Proposta com o seguinte código:

function putQuoteInActive(){
     SetStateRequest(1, 3);
 }

E com isto temos a nossa Proposta ativada e com a razão de estado em aberta. Agora sim podemos fechar a nossa Proposta e com a razão cancelado. Para isso efetuamos a seguinte chamada:

function CloseQuoteRequest(status, msg) {
     var recordId = Xrm.Page.data.entity.getId();

     var requestMain = ""
     requestMain += "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\">";
     requestMain += "  <s:Body>";
     requestMain += "    <Execute xmlns=\"http://schemas.microsoft.com/xrm/2011/Contracts/Services\" xmlns:i=\"http://www.w3.org/2001/XMLSchema-instance\">";
     requestMain += "      <request i:type=\"b:CloseQuoteRequest\" xmlns:a=\"http://schemas.microsoft.com/xrm/2011/Contracts\" xmlns:b=\"http://schemas.microsoft.com/crm/2011/Contracts\">";
     requestMain += "        <a:Parameters xmlns:c=\"http://schemas.datacontract.org/2004/07/System.Collections.Generic\">";
     requestMain += "          <a:KeyValuePairOfstringanyType>";
     requestMain += "            <c:key>QuoteClose</c:key>";
     requestMain += "            <c:value i:type=\"a:Entity\">";
     requestMain += "              <a:Attributes>";
     requestMain += "                <a:KeyValuePairOfstringanyType>";
     requestMain += "                  <c:key>quoteid</c:key>";
     requestMain += "                  <c:value i:type=\"a:EntityReference\">";
     requestMain += "                    <a:Id>" + recordId.replace('{', '').replace('}', '') + "</a:Id>";
     requestMain += "                    <a:LogicalName>quote</a:LogicalName>";
     requestMain += "                    <a:Name i:nil=\"true\" />";
     requestMain += "                  </c:value>";
     requestMain += "                </a:KeyValuePairOfstringanyType>";
     requestMain += "                <a:KeyValuePairOfstringanyType>";
     requestMain += "                  <c:key>subject</c:key>";
     requestMain += "                  <c:value i:type=\"d:string\" xmlns:d=\"http://www.w3.org/2001/XMLSchema\">" + msg + "</c:value>";
     requestMain += "                </a:KeyValuePairOfstringanyType>";
     requestMain += "              </a:Attributes>";
     requestMain += "              <a:EntityState i:nil=\"true\" />";
     requestMain += "              <a:FormattedValues />";
     requestMain += "              <a:Id>00000000-0000-0000-0000-000000000000</a:Id>";
     requestMain += "              <a:LogicalName>quoteclose</a:LogicalName>";
     requestMain += "              <a:RelatedEntities />";
     requestMain += "            </c:value>";
     requestMain += "          </a:KeyValuePairOfstringanyType>";
     requestMain += "          <a:KeyValuePairOfstringanyType>";
     requestMain += "            <c:key>Status</c:key>";
     requestMain += "            <c:value i:type=\"a:OptionSetValue\">";
     requestMain += "              <a:Value>" + status + "</a:Value>";
     requestMain += "            </c:value>";
     requestMain += "          </a:KeyValuePairOfstringanyType>";
     requestMain += "        </a:Parameters>";
     requestMain += "        <a:RequestId i:nil=\"true\" />";
     requestMain += "        <a:RequestName>CloseQuote</a:RequestName>";
     requestMain += "      </request>";
     requestMain += "    </Execute>";
     requestMain += "  </s:Body>";
     requestMain += "</s:Envelope>";
     var req = new XMLHttpRequest();
     req.open("POST", _getServerUrl(), false)
     // Responses will return XML. It isn't possible to return JSON.
     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");
     var successCallback = null;
     var errorCallback = null;
     req.onreadystatechange = function () { ResponseOK(req); };
     req.send(requestMain);
 }

A chamada desta função é a seguinte:

function putQuoteInCancel(){
     CloseQuoteRequest(6, "Cancelado porque a encomenda foi cancelada");
 }

Uma das razões que o SetStateRequest não funciona é que o fecho da Proposta necessita de um objeto QuoteClose onde guarda por exemplo a mensagem que estamos a passar.

Agora que temos a sequência de chamada estabelecida só nos falta criamos o botão, aqui mais uma vez e sem dúvida nenhum vamos recorrer ao RibbonWorkBench já muito referenciado aqui no blog. Por isso vou encurtar os screens desta parte. Resumidamente temos que:

  • Adicionar um botão a barra de comandos, neste caso a tab que diz Form;
  • Dentro deste botão, podemos colocar um novo texto e um icon;
  • Mas o mais importante no botão é associarmos um comando;

·         Este comando é onde está configurado quais os passos a executar e quais os critérios de Display e Enable:


 

Como podem reparar tenho as várias chamadas aos métodos que refiro acima e pela ordem correta.

Vou só colocar as funções auxiliares que usei:

function _getServerUrl() {
     var OrgServicePath = "/XRMServices/2011/Organization.svc/web";
     var serverUrl = "";
     if (typeof GetGlobalContext == "function") {
          var context = GetGlobalContext();
          serverUrl = context.getServerUrl();
     }
     else {
          if (typeof Xrm.Page.context == "object") {
               serverUrl = Xrm.Page.context.getServerUrl();
          }
          else{ 
               throw new Error("Unable to access the server URL"); }
          }
          if (serverUrl.match(/\/$/)) {
             serverUrl = serverUrl.substring(0, serverUrl.length - 1);
     } 
     return serverUrl + OrgServicePath;
}

 function ResponseOK(req) {
       if (req.readyState == 4) {
       if (req.status == 200) {
       }
       else {
           alert('erro');
       }
   }
}

function refreshForm(){
     var recordId = Xrm.Page.data.entity.getId();
     Xrm.Utility.openEntityForm("quote", recordId);
}

 

Se tiverem alguma dúvida coloquem nos comentários ou vão até ao fórum.

 

 

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: , , , ,

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