Microsoft Dynamics CRM 4.0 | Dashboarding einmal anders

 

Immer, wenn ich nach “Dashboard” im Zusammenhang mit CRM gesucht habe, bin ich auf Treffer gestoßen, die sich mit der Integration von Berichten (SQL Reporting Services) innerhalb des CRM als Dashboard beschäftigen – nicht jedoch mit den Möglichkeiten, mehrere Informationen gleichzeitig in einer Ansicht zu vereinen. Nun mag man sich lange über die Begrifflichkeit streiten, doch fakt bleibt, dass viele Anwender/Innen Informationen schneller/kompakter einsehen wollen. Und mit Hilfe von Übersichtsseiten versucht man diese Darstellung der Informationen zu ermöglichen.

Entwickler und Systemanpasser unter Euch wissen, dass Ihnen sehr häufig die Anforderung benannt wird: Ich möchte alle für mich relevanten Informationen auf der 1. Registerkarte sehen. Und im gleichen Atemzug wissen die Prozess-Analysten unter Euch, dass eine derartige Darstellungsform selten in Einklang zu bringen ist, mit den Unterschiedlichen Anforderungen von Prozessbeteiligten. Denn in Wahrheit wünscht sich (fast) jeder eine unterschiedliche Darstellungsform von Informationen.

Pragmatisch könnte man jedem Benutzer seine Sichtweise auf Daten individuell zusammenstellen lassen, doch ist dies wirklich die Lösung dieses Konflikts?

Eine Integration einer Übersichtsseite mit Hilfe von Berichten (SQL Reporting Services) kann hier einige Anforderungen erfüllen, doch es gibt auch noch eine weitere Methode für Übersichtsseiten zu sorgen. Uns hilft hierbei die Sitemap, mit der wir auch in der Lage sind, eigenständige ASPX- oder HTML-Seiten zu integrieren.

DashboardDie Idee eines “Dashboards” basiert in meinem Fall auf der Anforderung der Nutzer ohne weitere Mausklicks in der Übersichtsseite der Anfragen – in diesem Fall in Vorgänge umbenannt – die Aktivitäten (sowohl aktive, als auch abgeschlossene) schnell einsehen zu können (z.B. im Auskunftsfall [Telefonat])und eine weitere Bearbeitung aus dieser Übersicht heraus zu ermöglichen.

Das Resultat der Überlegungen der gebotenen Darstellungs-möglichkeiten innerhalb des CRM ist eine eigenständige .aspx Page, die aus dem ISV-Verzeichnis heraus auf Server-Seite aufgerufen wird und die unterschiedlichen Ansichten mit Hilfe von IFrames in einer Übersichtsseite darstellt.

Die Einbindung erfolgt über die Sitemap, in der der Aufruf der neuen URL eingerichtet wird.

Ergänzend zu den Standard-Funktionen in der Grid-Ansicht des CRM sind hier über ISV-Config weitere Buttons hinzugefügt. So seht Ihr z.B. die Möglichkeit der Anfrage unmittelbar weitere Aufgaben bzw. Telefonanrufe hinzuzufügen (rechts von Weitere Aktionen). Die Integration dieser Buttons habe ich bereits im Artikel beschrieben.

Darüber hinaus seht Ihr in der Darstellung der Aktivitäten einen Button, diese aus der Grid-Ansicht heraus abschließen zu können. Auch diese Funktion habe ich bereits im Artikel beschrieben.

Doch nun zu der eigentlichen Entwicklung einer Übersichts-.aspx-Seite. Es benötigt hierzu der Kenntnis der Erstellung von Ansichten, dem Ermitteln der Ansichts-ID und der Benutzung der IE-Entwicklertools zu Ermitteln von Security-Codes, TabSet-Bezeichnern, etc.

Der Aufbau der Seite ist schnell erklärt. Es wird eine Seite mit insgesamt 3 IFrames erzeugt, in die unterschiedliche Datensichten zur Laufzeit geladen werden.

<%@ Page Language="C#" %>

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Untitled Page</title>
</head>
<body scroll="no" style="margin:0px">
   <iframe id="casesGrid" 
   src="about:blank" 
   style="width:100%;height:33%;" 
   frameborder="0"
   resize="true"   
   scrolling="no">
 </iframe>
   <iframe id="activitiesGrid" 
   title="Aktivitäten"
   src="about:blank" 
   allowTransparency="true"
   style="width:100%;height:30%;background-color:#9EBADD;" 
   frameborder="0" 
   scrolling="no">
 </iframe>
   <iframe id="historyGrid" 
   title="Historie"
   src="about:blank" 
   allowTransparency="true"
   style="width:100%;height:37%;background-color:#9EBADD;" 
   frameborder="0" 
   scrolling="no">
 </iframe>

</body>
</html>

Damit jetzt noch ein wenig Action beim Aufruf der URL passiert, ist der Rest in Funktionen programmiert, die per Javascript in die Seite integriert sind.

Diese Funktionen möchte ich nachfolgend erläutern, damit Ihr Sie Euch für Eure Anforderungen hin anpassen könnt. Denn mit diesem “Konzept” könnt Ihr x-beliebige Übersichtsseiten erstellen. Zunächst erfolgen einige Globale Konfigurationen.

    <script language="javascript">
  attachEvent("onload",OnPageLoad);
  attachEvent("onresize",OnGridSlaveReady); 
  var Spacer = {Right : 1, Left : 2, Both : 3, None : 4}
  var bFired = false;
  // Security Code to open SystemView
  var seccode = '852023'
  //IFrame IDs
  var casesGrid;
  var activitiesGrid;
  var historyGrid;
  //IFrame document object
  var iframeDoc;
  var iframeDoc2;
  //Views picklist 
  var SavedQuerySelector;
  //The grid object
  var crmGrid;
  var actGrid;
  var DetailUrl = "/" + top.ORG_UNIQUE_NAME + "/cs/cases/areas.aspx";
  var DetailTabSet = "areaActivities"; //Area or relationship name for the deatil    
  var Detail_histTabSet = "areaActivityHistory"; //Area or relationship name for the detail 

Via “attachEvent” werden den jeweiligen Events Funktionsschritte hinzugefügt, auf die ich noch eingehe. ‘Spacer’ sind die | Abschnitte bei den ISV-Buttons, die ich ggfs. ausblenden möchte. ‘bFired’ ist eine Hilfsvariable um zu ermitteln, ob der User eine andere Ansicht ausgewählt hat und ich die einzelnen IFrames aktualisieren muss. In ‘seccode’ ist vor allem der Security Code zu ermitteln, mit dem es Usern möglich ist die System-Ansichten bei der Ansicht von Anfragen zu öffnen. Es folgen die einzelnen IFrame IDs, damit wir die IFrames gezielt ansprechen können.

Nun zu den Funktionen. Zunächst die Lade-Routine, die die Seitendarstellung übernimmt:

  /*
   Load the cases grid view,
   You may change the IFRAME url to suit your needs
  */
  function OnPageLoad()
  {
   casesGrid = document.all.casesGrid;
   activitiesGrid = document.all.activitiesGrid;
   historyGrid = document.all.historyGrid;
   var casesUrl  = "/" + top.ORG_UNIQUE_NAME + "/_root/homepage.aspx?etc=112&viewid={7C9C9F43-44DF-DE11-B3EA-000C29DFCCB9}"; 
   var activitiesUrl = "about:blank";
   var historyUrl = "about:blank";
   casesGrid.src = casesUrl;
   activitiesGrid.src = activitiesUrl;
   historyGrid.src = historyUrl;
   casesGrid.onreadystatechange = OnGridViewReady;
   activitiesGrid.onreadystatechange = OnGridSlaveReady;
   historyGrid.onreadystatechange = OnGridSlaveReady;
  }

Anschließend die Routine, wenn die Grid-Ansicht aktualisiert werden muss – eine anderen Ansicht ausgewählt wird, oder ein anderer Datensatz (Anfrage) selektiert wird:

  /*
   When the IFRAME is ready then:
   Attach to the grid refresh and selector change events.
   Since the grid is already loaded call it for the first time.
  */
  function OnGridViewReady()
  {
   if( casesGrid.readyState != "complete" ) 
    return;
   iframeDoc = casesGrid.contentWindow.document;
   
   //make sure the selector exists
   SavedQuerySelector = iframeDoc.all.SavedQuerySelector;
   if( SavedQuerySelector )
    iframeDoc.all.SavedQuerySelector.attachEvent( "onchange" , OnGridReadyChangeLayout );
   
   //make sure the grid exists
   crmGrid = iframeDoc.all.crmGrid;
   if( crmGrid )
   {
    iframeDoc.all.crmGrid.attachEvent( "onrefresh" , OnGridReadyChangeLayout );
    iframeDoc.all.crmGrid.InnerGrid.attachEvent("onselectionchange", GridClick); 
    //change the layout for the first time
    OnGridReadyChangeLayout();
   }
    //make sure the quick find exists
    var QuickFind = iframeDoc.all.findCriteria;
    if (QuickFind) {
    iframeDoc.all.findCriteriaButton.attachEvent("onclick", OnGridReadyChangeLayout);
    iframeDoc.all.findCriteria.attachEvent("onkeypress", function(keyPressed) { if (keyPressed.keyCode == 13) setTimeout(OnGridReadyChangeLayout, 10); });
    iframeDoc.all.clearCriteriaButton.attachEvent("onclick", OnGridReadyChangeLayout);
    }
   
  }

Da hier auch die Schnellsuche eine Aktualisierung der Übersichtsseite notwendig macht, ist auch dies im oberen Programmcode berücksichtigt.

Es folgt eine Routine, was passieren soll, wenn die untergeordneten IFrames (Ansichten der Aktivitäten bzw. historischen Aktivitäten) geladen sind:

 function OnGridSlaveReady()
  {
   if( activitiesGrid.readyState != "complete" ) 
    return;
   // HideButton-Routine to Hide System or ISV-Config-Buttons, which should not be used from Grid-View
   HideButtons (activitiesGrid,'Neu zu diesem Datensatz hinzufügen: Aktivität', Spacer.Right)
   HideButtons (activitiesGrid,'Vorh. Aktivität zu diesem Datensatz hinzufügen.', Spacer.Right)
   HideButtons (activitiesGrid,'Druckvorschau', Spacer.None)
   if( historyGrid.readyState != "complete" ) 
    return;
   HideButtons (historyGrid,'Neu zu diesem Datensatz hinzufügen: Aktivität', Spacer.Right)
   HideButtons (historyGrid,'Ausgewählte Aktivitäten abschließen', Spacer.Right)
   HideButtons (historyGrid,'Druckvorschau', Spacer.None)
 } 

In dieser Routine integriert ist die Ausblendung einiger ISV-Buttons, die in der Grid-Übersicht nicht dargestellt werden sollen. Die dahinter liegende Funktion sieht wie nachfolgend dargestellt aus:

 function HideButtons (oFrame, buttonTitles, spacer) {
   iframeDoc2 = oFrame.contentWindow.document;
   var liElements = iframeDoc2.all.mnuBar1.getElementsByTagName('li'); 
        for (var j = 0; j < buttonTitles.length; j++) { 
        for (var i = 0; i < liElements.length; i++) { 
            if (liElements[i].getAttribute('title') == buttonTitles) { 
                liElements[i].style.display = 'none'; 
                
                switch(spacer)
                {
                case Spacer.Right:
                  liElements[i].nextSibling.style.display = 'none';
                  break;
                case Spacer.Left:
                  liElements[i].previousSibling.style.display = 'none';
                  break;
                case Spacer.Both:
                  liElements[i].nextSibling.style.display = 'none';
                  liElements[i].previousSibling.style.display = 'none';
                  break;
                }
            }
        } 
        } 
  }

Und zu guter letzt folgen noch die Routinen, die angesprochen werden müssen, wenn jemand die Darstellungsform ändert (z.B. die Größe von Vollbild auf individuelle Größe umstellt, die Darstellung der Aktivitäten bzw. historischen Aktivitäten für den ausgewählten Anfrage-Datensatz und die Routine zur Zusammenstellung der URL:

  /*
   This function (callback) is called every time the 
   grid view refreshes either by the selector or refresh button.
   
   IF the Data( InnerGrid ) is not ready then the setTimeout is called.      
  */
  function OnGridReadyChangeLayout()
  {
   if( !crmGrid.InnerGrid ) 
    return setTimeout( OnGridReadyChangeLayout , 100 );
   bFired = false;
   iframeDoc.all.crmGrid.InnerGrid.attachEvent("onselectionchange", GridClick);
   GridClick();
  }
  
 function GridClick () { 
    //    check to see if this event has been fired already 
    if(bFired == false) { 
        //    get array of selected records 
        //var frameDoc = document.getElementById("IFRAME_myiframe").contentWindow.document; 
        //var a = frameDoc.all['crmGrid'].InnerGrid.SelectedRecords;
        var a = crmGrid.InnerGrid.SelectedRecords;        
        var selectedItems = new Array(a.length); 
        for (var i=0; i < a.length; i++) 
        { 
            selectedItems[i] = a[i][0]; 
        } 
        var masterId = crmGrid.InnerGrid.SelectedRecords[0][0];
        var masterTypeCode = crmGrid.InnerGrid.SelectedRecords[0][1];
        activitiesGrid.src = GetFrameSource(DetailUrl, DetailTabSet, masterId, masterTypeCode, seccode);   
        historyGrid.src = GetFrameSource(DetailUrl, Detail_histTabSet, masterId, masterTypeCode, seccode);   
        activitiesGrid.reload
        historyGrid.reload
        bFired = true; 
    } 
    else { 
        //    toggle our fired flag back 
        bFired = false; 
    } 
}

function GetFrameSource(url, tabSet, oId, oType, security) {
        var result = url + "?oId=" + oId + "&oType=" + oType + "&security=" + security + "&tabSet=" + tabSet;
        return result;
}

  
 </script>

Mit Hilfe dieser Übersichtsseite könnt Ihr die im obigen Schaubild dargestellte Übersichtsseite auch in Eurem System integrieren. Doch damit nicht genug. Mit diesem Ansatz lässt sich natürlich auch noch einiges mehr anstellen. Wie wäre es z.B. mit einem weiteren IFrame, welches Euch auch noch Informationen zum Kunden in die Übersicht einstellt. Hierzu bedarf es lediglich eines weiteren IFrames und der Ermittlung, ob es sich im Kunden-Fall um einen Kontakt oder eine Firma handelt, da hierfür unterschiedlich die URL zusammengestellt werden muss.

Diese Übersichtsseite habe ich wie auch das erste Beispiel (CustomGridView) auf meinem SkyDrive-Laufwerk zur Verfügung gestellt.

http://cid-97768ec3728c1ff3.skydrive.live.com/embedrowdetail.aspx/Public/Samples/

 

Ihr seht, der Ansatz ist sehr flexibel und kann von Euch weiter ausgebaut werden. Viel Spass wünsche ich in der Realisierung Eurer Übersichtsseiten.

 

Schreibe einen Kommentar

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s