Results for tag "javascript"

CRM 2011 – Javascript – Attribute setzen (für alle Datentypen)

Hallo Leute,

nach längerer Zeit mal wieder ein feiner Alltags-Helper.
Um in CRM via Javascript einen Wert in ein Attribut schreiben zu können, bedarf es eigentlich je nach Typ (Lookup, Text, Integer, etc.) eine eigene Methode.
Ich habe mir jetzt einen Helper geschrieben, der selbst prüft um was für einen Typ es sich handelt und entsprechend handelt.

[sourcecode language=“javascript“]
this.SetAttributeValue = function (id, value, valueId, logicalName) {

var control = Xrm.Page.getControl(id);
var isDisabledField = Xrm.Page.getControl(id).getDisabled();

if (isDisabledField) {
Xrm.Page.getControl(id).setDisabled(false);
}

try {
if (control != null) {

if (value == null) {
FormUtils.SetStandardAttributeValue(id, value);
}
else {
var name = id;
var type = control.getAttribute().getAttributeType();

switch (type) {

case "string":
FormUtils.SetStandardAttributeValue(id, value);
break;

case "integer":
var intValue = parseInt(value);
FormUtils.SetStandardAttributeValue(id, intValue);
break;

case "optionset":
FormUtils.SetStandardAttributeValue(id, value);
break;

case "memo":
FormUtils.SetStandardAttributeValue(id, value);
break;

case "boolean":
FormUtils.SetStandardAttributeValue(id, (value == "true"));
break;

case "money":
var intValue = parseFloat(value);
FormUtils.SetStandardAttributeValue(id, intValue);
break;

case "datetime":
// not implemented yet
break;

case "lookup":
FormUtils.SetLookupAttributeValue(id, valueId, value, logicalName);
break;

case "decimal":
var intValue = parseFloat(value);
FormUtils.SetStandardAttributeValue(id, intValue);
break;
}
}

}
}
catch (ex) {

}

if (isDisabledField) {
Xrm.Page.getControl(id).setDisabled(true);
}

}

// Setzt den Wert eines "normalen" Attributes auf einer Entity-Page.
this.SetStandardAttributeValue = function (id, value) {

var isDisabledField = Xrm.Page.getControl(id).getDisabled();

if (isDisabledField) {
Xrm.Page.getControl(id).setDisabled(false);
}

try {
Xrm.Page.getAttribute(id).setValue(value);
Xrm.Page.getAttribute(id).setSubmitMode(‚always‘);
}
catch (err) {
alert(err);
}

if (isDisabledField) {
Xrm.Page.getControl(id).setDisabled(true);
}
}

// Setzt den Wert eines "normalen" Attributes auf einer Entity-Page.
this.SetLookupAttributeValue = function SetLookupAttributeValue(id, valueId, value, entityLogicalName) {

var isDisabledField = Xrm.Page.getControl(id).getDisabled();

if (isDisabledField) {
Xrm.Page.getControl(id).setDisabled(false);
}

try {
if (value == null) {
Xrm.Page.getAttribute(id).setValue(value);
}
else {
Xrm.Page.getAttribute(id).setValue([{ id: valueId, name: value, entityType: entityLogicalName}]);
}
Xrm.Page.getAttribute(id).setSubmitMode(‚always‘);
}
catch (err) {

}

if (isDisabledField) {
Xrm.Page.getControl(id).setDisabled(true);
}
}
[/sourcecode]

Beim setzen eines Optionsets muss der Integer-Wert gesetzt werden, nicht der Label. Zudem werden für das Setzen des Lookup-Attributes mehrere Parameter benötigt, die bei anderen Datentypen NULL sein können.

Am Error-Handling sollte man noch arbeiten, das ist dann aber euch selbst überlassen 😉

Have Fun!

Cheers,
Christian

CRM 2011 – OnCloseAlert – „Möchten Sie diese Seite wirklich verlassen?“ entfernen/unterdrücken

Hallo Leute,

einigen von euch ist es sicher schon passiert/aufgefallen. Ihr erzeugt eine neue Entität mittels Formular und wollt dieses Fenster dann, ohne es zu speichern, wieder verlassen. Was folgt ist der nachfolgende Dialog:

Das kann durchaus Sinn machen, möchte man z.B. den Benutzer warnen, dass Änderungen verloren gehen würden. Jedoch ist dieser Dialog durchaus problematisch, z.B. wenn man via Script die Seite neu laden/refreshen möchte und bewusst in Kauf nehmen möchte, dass Änderungen verloren gehen können.

Leider beschreibt die SDK keine Möglichkeit, wie man das deaktivieren kann. Sucht man mittels div. Suchmaschinen, bekommt man höchstens von Oberlehrern gebetsmühlenartig vorgekaut, dass man solche Dialoge tunlichst nicht verwenden soll…

Die Lösung ist letztlich recht einfach und besteht aus einer Zeile Code und kann im Javascript aufgerufen werden, nämlich:

[sourcecode language=“javascript“]
this.DetachCloseAlert = function () {
crmForm.detachCloseAlert();
}
[/sourcecode]

Happy Programming!

Cheers,
Chris

CRM 2011 – Ribbons via Javascript deaktivieren (disable Ribbons with javascript)

Jeder CRM Developer wird wohl schon vor folgendem Problem gestanden sein: Einen Ribbon Button deaktivieren, weil dieser im aktuellen Kontext keinen Sinn macht. Andere Buttons verwirren den Kunden, wiederrum andere sollen überhaupt verschwinden.

In einem meiner Projekte hatten wir nun die Situation, dass wir in einem bestimmten Prozess-Stati alle Ribbon Buttons von ca. 15-20 Entitäten sperren sollten. Inklusive der Original-Ribbon-Buttons (Datei, Entität, Hinzufügen, Listentools, etc.). Microsoft stellt dafür keinen vernünftigen Weg zur Verfügung, schlägt nur die Customizations.xml vor, was für ca. 400 Buttons ein enormer Zeitaufwand wäre, abgesehen von der extremen fehleranfälligkeit.

Ich habe daher, trotz aller Warnungn von div. CRM-Spezialisten („dieser Weg wird von MS nicht supportet“, etc.), einen Weg mittels Javascript gefunden, mittels welchem sich die Ribbon Buttons deaktivieren oder ganz ausblenden lassen, seht selbst:

[sourcecode language=“javascript“]

function HideRibbonButtons () {

var dateiButton = parent.document.getElementById("jewelcontainer"); // versteckt den "Datei"-Ribbon. Diesen zu deaktivieren habe ich nicht getestet!
dateiButton.style.display = ’none‘;

var ribbon = parent.document.getElementById("Mscrm.Ribbon");
var container = ribbon.childNodes[2].childNodes[0]; // container = Ein Tab, oberhalb der dargestellten Ribbons.

for (var i = 0; i < container.childNodes.length; i++) {
var group = container.childNodes[i];

if (group.id.indexOf(‚Isv‘) > 0) { // selbst hinzugefügte tabs enthalten bei mir den Hinsweis "Isv" und werden explizit ausgenommen.
// do nothing…
}
else {
DisableAllChilds(group);
}
}
}

// iteriert über alle child elements, bis das element mit der Rolle "button" gefunden wurde.
// An dieser Stelle kann dann die Deactivate oder Hide Funktionalität aufgerufen werden
function DisableAllChilds(element) {

for (var i = 0; i < element.childNodes.length; i++) {
var subElement = element.childNodes[i];

if (subElement.childNodes.length > 0)
DisableAllChilds(subElement);
}

if (element.role == "button") {

DeactivateRibbonButton(element);

}

}

// nachdem sich ein button nicht so ohne weiteres deaktivieren lässt (der dahinterliegende Event lässt sich nicht ausschalten),
// habe ich das element einfach geklont und das original-Element durch das erzeugte Element ersetzt.
function DeactivateRibbonButton(element) {
var link = document.createElement("a");
link.role = "button";
link.className = "ms-cui-ctl-large ms-cui-disabled";

try {
link.appendChild(element.childNodes[0]); // image/buttion
link.appendChild(element.childNodes[0]); // label
}
catch (exception) {
// do nothing…
}

element.parentNode.replaceChild(link, element);
}

// einen Button ausblenden ist hingegen recht easy und sollte keine negativen Auswirkungen haben.
function HideRibbonButton(element) {
element.style.display = ’none‘;
}

[/sourcecode]

Wichtig ist es noch zu wissen, dass das CRM nur den ersten Tab voll lädt, bedeutet, die Ribbons für alle weiteren Tabs werden erst bei erstmaligem Aufruf nachgeladen. Daher ist es notwendig, die Funktion „HideRibbonButtons()“ mehrmals aufzurufen, ich mache dieses mittels einem „setInterval“. Einen eleganteren Weg habe ich leider nicht finden können.

Abschließend nochmals explizit der Hinweis: Dies ist ein von Microsoft nicht unterstützter Weg! Bitte überlegt euch daher gut, ob ihr diesen gehen wollt!

Viel Spaß beim Nachmachen!

CRM 2011 – Custom View für Subgrid

In einem anderen Blog beschreibe ich, wie man einen Custom View zu einem Lookup hinzufügen kann. Heute habe ich die gleiche Funktionalität für ein Subgrid benötigt. Nachdem ich nicht gleich eine Lösung dafür gefunden habe, möchte ich euch meine nicht vorenthalten bzw. die Unterschiede zeigen:

[sourcecode language=“javascript“]
//Add your new view to the Lookup’s set of availabe views and make it the default view
$("#" + lookupFieldId).attr("disableViewPicker", "0");
Xrm.Page.getControl(lookupFieldId).addCustomView(viewId, entityName, viewDisplayName, fetchXml, layoutXml, viewIsDefault);
$("#" + lookupFieldId).attr("disableViewPicker", "1");
[/sourcecode]
So funktionierts für ein Lookup…

[sourcecode language=“javascript“]
var grd = document.getElementById(lookupFieldId);
if (grd.readyState != "complete") {
setTimeout("SetLookupForm(…params…);", 500);
}

grd.control.setParameter("fetchXml", fetchXml);
grd.control.refresh();
[/sourcecode]
… und so für ein Subgrid.

Achtet besonders auf die Abfrage auf „readystate“. Wenn Ihr die Funktionalität im OnLoad-Event verwendet, muss das Grid noch nicht geladen sein!! In diesem Fall rufe ich die Funktion einfach erneut auf…

Viel Spaß beim Nachmachen!

CRM 2011 – Custom View/Lookup aufbauen und filtern

Christian Wagnsonner 4 Comments

Wenn man etwas mit CRM arbeitet, stößt man zwangsläufig auf einige Limitierungen. Einfaches Beispiel: Man hat eine Verkaufschance und darunter eine Entität, z.B. Produkt (1:n). Dieses Produkt hat jedoch eine weitere Entität darunter, z.B. Produktpositionen (wieder 1:n). Auf der Form für Produktpositionen möchte ich jetzt als Lookup das Produkt auswählen. CRM listet in diesem Fall nun sämtliche Produkte auf und nicht nur jene, die unter der Verkaufschance hängen.

In so einem Fall kann man einen eigenen View (Stichwort CustomView) zur Laufzeit erstellen und dem Lookup zuordnen.
Hierfür habe ich mir eine Helper-Funktion geschrieben, welcher man u.a. die dynamisch die Spalten des Lookups erstellt, Filter/Ordering setzt und die Auswahl eines anderen Views im Lookup unterbindet.

[sourcecode language=“javascript“]

function SetViewForProjektProdukt() {
var lookupEntity = "lookupentity"; // Name der Entität, die im Lookup angezeigt werden soll
var lookupEntityPK = "lookupEntityPK "; // PrimaryKey der Entität, die im Lookup angezeigt werden soll
var lookupFieldId = "lookupFieldId "; // ID des Lookups im Formular, für das der CustomView eingerichtet werden soll
var lookupName = "lookupName"; // Bezeichnung des Lookups

var lookupFormColumns = new Array(); // Spalten welche im Lookup angezeigt werden sollen, müssen Teil der Entität "lookupentity" sein
lookupFormColumns[0] = "new_attr1";
lookupFormColumns[1] = "new_attr2";

var filterAttribute = "new_attr1";
var filterValue = ‚{GUID}‘;

var orderAttribute = "createdon"; // Feld, nach dem sortiert werden soll
var orderDescending = "true"; // asc/desc

SetLookupForm(lookupFieldId, lookupEntity, lookupEntityPK, lookupFormColumns, lookupName, filterAttribute, filterValue, orderAttribute, orderDescending);
}

function SetLookupForm(lookupFieldId, lookupEntity, lookupEntityPK, lookupFormColumns, lookupName, filterAttribute, filterValue, orderAttribute, orderDescending) {

if (Utils.StringIsNullOrEmpty(orderAttribute)) {
orderAttribute = "createdon";
}

if (Utils.StringIsNullOrEmpty(orderDescending)) {
orderDescending = "false";
}

//Note: in the form designer make sure the lookup field is set to "Show all views" in its "View Selector" property
//Set parameters values needed for the creation of a new lookup view…
var viewId = Xrm.Page.context.getUserId(); // Your new lookup views needs a unique id. It must be a GUID. Here I use the GUID of the current user id.
var entityName = lookupEntity; // The entity your new lookup view relates to
var viewDisplayName = lookupName; // A name for new lookup view
var viewIsDefault = true; // Whether your new lookup view should be the default view displayed in the lookup or not

//Define the Fetch XML query your new lookup view will use. You can create this via Advanced Find. You’ll need to massage the syntax a little though
var fetchXml =
"<fetch version=’1.0′ output-format=’xml-platform‘ mapping=’logical‘ distinct=’false‘>" +
"<entity name=’" + lookupEntity + "‘>";

for (var i = 0; i < lookupFormColumns.length; i++) {
fetchXml = fetchXml + "<attribute name=’" + lookupFormColumns[i] + "‘ />";
}

fetchXml = fetchXml +
"<filter type=’and‘>" +
"<condition attribute=’" + filterAttribute + "‘ operator=’eq‘ value=’" + filterValue + "‘ />" +
"</filter>" +
"<order attribute=’" + orderAttribute + "‘ decending=’" + orderDescending + "‘ />" +
"</entity>" +
"</fetch>";

//Define the appearance of your new lookup view
var layoutXml =
"<grid name=’resultset‘ object=’1′ jump=’name‘ select=’1′ icon=’1′ preview=’1′>" +
"<row name=’result‘ id=’" + lookupEntityPK + "‘>"; // id = the GUID field from your view that the lookup should return to the CRM form

for (var i = 0; i < lookupFormColumns.length; i++) {
layoutXml = layoutXml + "<cell name=’" + lookupFormColumns[i] + "‘ width=’100′ />";
}

layoutXml = layoutXml +
"</row>" +
"</grid>";

//Add your new view to the Lookup’s set of availabe views and make it the default view
$("#" + lookupFieldId).attr("disableViewPicker", "0");
Xrm.Page.getControl(lookupFieldId).addCustomView(viewId, entityName, viewDisplayName, fetchXml, layoutXml, viewIsDefault);
$("#" + lookupFieldId).attr("disableViewPicker", "1");

}
[/sourcecode]

Ich hoffe, ich kann euch damit ein paar Stunden eurer kostbaren Zeit sparen 😉

Cheers,
Chris

WCF/Soap Service aus Javascript konsumieren

In einem aktuellen Projekt habe ich die Aufgabe bekommen, ein WCF Service zu erstellen, welches dann mittels Javascript konsumiert werden können soll.
Doch ein SOAP-Service aus Javascript konsumieren ist eine recht aufwendige Geschichte und die gefundenen Tutorials im Internet oft alles andere als einfach verständlich.

Um das Service anzusprechen, verwende ich einen XMLHttpRequest, welchem man einen SoapBody/XML und eine SoapAction mitgeben muss. Um das SOAP-XML nicht selbst produzieren zu müssen, was ja auch gar nicht so einfach wäre, habe ich SOAP UI (http://www.soapui.org/) verwendet. Dort müsst Ihr nur das WSDL des Service eingeben und bekommt für jede Methode, die euer Service zur Verfügung stellt, einen Request (welcher das XML enthält) erzeugt:

Hier seht Ihr auch schon das XML, welches Ihr in eurer JS-Methode verwenden könnt, z.B.:

[sourcecode language=“xml“]
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tem="http://tempuri.org/">
<soapenv:Header/>
<soapenv:Body>
<tem:Login>
<!–Optional:–>
<tem:organization>?</tem:organization>
</tem:Login>
</soapenv:Body>
</soapenv:Envelope>
[/sourcecode]

Die SoapAction findet Ihr, wenn Ihr euch euer WSDL anseht.

In meinem Beispiel lautet diese: http://tempuri.org/IAuthentication/Login

Nun geht’s ans Javascript. Ich habe mir hierzu eine kleine Helper-Klasse geschrieben.

[sourcecode language=“javascript“]
var Utils = new function () {

var Request = new XMLHttpRequest();
var fbFunction = null;

this.CallWS = function CallWS(soapHeader, feedbackFunction) {

// Create CRM Request
fbFunction = feedbackFunction;
var url = ‚http://localhost/crm.service/Operation.svc‘;

Request.open("POST", url, true);
Request.setRequestHeader("SOAPAction", soapHeader.SoapAction);
Request.setRequestHeader("Accept", "application/xml");
Request.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
Request.onreadystatechange = WSFinished;

Request.send(soapHeader.SoapXml);
}

function WSFinished() {

if (Request.readyState == 4) {

if (Request.status == 200) {

// parse webservice result
Result.Xml = Request.responseXML;
Result.ParseResult();
}
else {

Result.StatusCode = -2;
Result.StatusMessage = Request.responseText;
}
fbFunction(Result);
}
}
}

var SoapUtils = new function () {

var SoapHeader = new function () {
this.SoapXml = null;
this.SoapAction = null;
}

this.GetSoapHeaderForLogin = function (org) {
SoapHeader.SoapXml = "<soapenv:Envelope xmlns:soapenv=’http://schemas.xmlsoap.org/soap/envelope/‘ xmlns:tem=’http://tempuri.org/‘><soapenv:Header/><soapenv:Body><tem:Login><tem:organization>" + org + "</tem:organization></tem:Login></soapenv:Body></soapenv:Envelope>";
SoapHeader.SoapAction = "http://tempuri.org/IAuthentication/Login";
return SoapHeader;
}

}
[/sourcecode]

Der Aufruf sieht dann wie folgt aus:

[sourcecode language=“javascript“]
var header = SoapUtils.GetSoapHeaderForLogin("xyz");
Utils.CallWS(header, LoginFinished);
[/sourcecode]

Nachdem ich diese Webservice-Methoden mehrmals in meinem Projekt aufrufen muss, habe ich das SoapXML und die SoapAction in eine eigene Methode gepackt (SoapUtils), welche ich mittels SoapUtils.GetSoapHeaderForLogin("xyz"); aufrufe. Die zurückgelieferte Informationen gebe ich dann der CallWS-Methode mit (Utils.CallWS(header, LoginFinished);) und gebe zudem als zweiten Parameter eine Methode bekannt, welche aufgerufen werden soll, sobald der Webservice-Call fertig ist.

Wichtig: Ein Webservice-Aufruf ist immer asynchron! Es gibt zwar Spielereien (ähnlich Thread.Sleep() in C#), diese sollten jedoch erfahrungsgemäß nicht verwendet werden.

Wenn Ihr euch den Source-Code genauer anseht, werdet Ihr feststellen, dass ich in der Methode „CallWS“ dem Request.onreadystatechange-Event subscribed habe. In der Methode WSFinished() prüfe ich nun State des Requests und Status des Requests und erzeuge ein Result-Objekt, welches ich der Feedback-Funktion (übergeben bei CallWS) retour liefere. Hier seid Ihr selbst gefordert, euer Webservice wird naturgemäß andere Resultate erzeugen als meines.

Ich hoffe, ich konnte euch ein wenig weiterhelfen 🙂

Cheers,
Chris