Showing posts with label SAP Portal. Show all posts
Showing posts with label SAP Portal. Show all posts

Monday, November 24, 2008

More Google Analytics in SAP Portal with jQuery

One of the challenges with SAP Portal and integrating Google Analytics is it's tendency to create a lot of links that pop content open in a new window. Since you don't have access to the code that creates these URLs it causes a wee bit of a headache when you look to determine what items are being clicked on in the KM, or where your users are linking out of the portal to certain other applications.

We can resolve some of this by using a javascript library to scrape the HTML page and insert some onclick events that will allow the items to be tracked.

How can we accomplish this?

There are two steps:

First, add access to your favorite javascript library inside the Google Analytics code. I've chosen jQuery, although you could easily use other libraries. You can do this through the ga-split-1.js file that was outlined earlier. Don't forget to change the name of the file if need be so it is not cached in users browsers.


var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
document.write(unescape("%3Cscript src='http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js' type='text/javascript'%3E%3C/script%3E"));


By pulling jQuery from Google, we're increasing our load time, since we won't have to wait on connections to the Portal. The risk is low, since it's Google. In addition, if jQuery isn't found, we just won't track certain types of links. We'd still get the key pack clicking information.

The second part is to actually use jQuery to track stuff. You might be able to use the jQuery GA plugin, but in my case, I decided to write my own javascript based upon the plugin to do the trick. I would keep this code in a separate file and load it after you've initialized the pageTracker within your PortalComponent:


function ga_decorateLink(u){
var trackingURL = '';
if(u.indexOf('://') == -1 && u.indexOf('mailto:') != 0){
// no protocol or mailto - internal link - check extension
var ext = u.split('.')[u.split('.').length - 1];
var exts = ['pdf','doc','xls','csv','jpg','gif', 'mp3','swf','txt','ppt','zip','gz','dmg','xml']
for(i = 0; i < exts.length; i++){
if(ext == exts[i]){
// Likely grabbing an item from KM, etc.
trackingURL = '/downloads/' + u;
break;
}
}
} else {
if(u.indexOf('mailto:') == 0){
// mailto link - decorate
trackingURL = '/mailto/' + u.substring(7);
} else {
// complete URL - check domain
var regex = /([^:\/]+)*(?::\/\/)*([^:\/]+)(:[0-9]+)*\/?/i;
var linkparts = regex.exec(u);
var urlparts = regex.exec(location.href);
if(linkparts[2] != urlparts[2]) trackingURL = '/external/' + u; /*leaving the portal*/
}
}
return trackingURL;
}

// Since you've initialized pageTracker in each Portal page, we're skipping that here.
// just wait until the entire page loads
$(document).ready(function(){
$('a').each(function(){
var u = $(this).attr('href');

if(typeof(u) != 'undefined'){
var newLink = decorateLink(u);
if(newLink.length){
$(this).click(function(){
$.pageTracker._trackPageview(newLink);
});
}
}
});
});


If you're using the defualt framework, be aware that you will not be able to track each and every link. Javascript cannot dive into iframes on the page. Since it can't do that, you'll be unable to track each and every link, unless you can use embedded for that particular iView which will eliminate the iframes.

Some fair warning here. This is from memory. I am no longer working with SAP Portal, so there is a good chance I've forgotten something here. However, it did work on my last day working with Portal...at least the version at that gig. If you run into problems, please fix them and share them. Don't hold onto it. Share it with the rest of the SAP community, post it on your blog, or submit it to SDN for inclusion in their hosted materials. At the very least, post a solution in the SDN forums so that others can use this. When you get it working, it's pretty darned cool!

Wednesday, October 08, 2008

SAP Portal Javascript/CSS Service Enhanced

The service was enhanced today.

What I discovered whilst doing more testing is that the service inserted the content very early in the lifecycle of the Portal.

So lets say you inserted your CSS which fixes a bunch of SAPisms you can't fix using the theme editor. Using the service you'd get output that looked like this:

<link href="/irj/portalapps/com.sap.portal.design.portaldesigndata/themes/portal/tillerTheme/glbl/glbl_nn7.css?7.0.15.0.25" rel="stylesheet"/>
<link href="/irj/portalapps/com.sap.portal.design.portaldesigndata/themes/portal/tillerTheme/prtl_std/prtl_std_nn7.css?7.0.15.0.25" rel="stylesheet"/>

<!-- EPCF: BOB Core -->
<meta content="text/javascript" http-equiv="Content-Script-Type"/>
<script src="/irj/portalapps/com.sap.portal.epcf.loader/script/optimize/js13_epcf.js?7.00001502"/>
<script>
<!--
// Snipped 30 lines of script
</script>
<!-- EPCF: EOB Core -->

<!-- HTML Business for Java, 645_VAL_REL, 477869, Tue Feb 26 13:23:36 EST 2008 -->
<!-- HTMLB: begin VARS -->
<script language="JavaScript">
ur_system = {doc : window.document , mimepath :"/irj/portalapps/com.sap.portal.design.urdesigndata/themes/portal/tillerTheme/common/", stylepath : "/irj/portalapps/com.sap.portal.design.urdesigndata/themes/portal/tillerTheme/ur/", emptyhoverurl : "/irj/portalapps/com.sap.portal.htmlb/jslib/emptyhover.html", is508 : false, dateformat : 1, domainrelaxing : "MINIMAL"};
</script>
<!-- HTMLB: end VARS -->
<link type="text/css" href="http://your/css.css" rel="stylesheet"/>
a bunch of scripts


Oops, you're not after the theme at all. You'd have to use !important all over the place. How does one resolve this?

You enhance the service. In this case I used an example found by decompiling the LAFService, which is the actual theme service. It provided examples on how to implement and use new IResource and IResourceInformation objects. Here's the ExternalResource IResource object:


import com.sapportals.portal.prt.resource.IResource;
import com.sapportals.portal.prt.resource.IResourceInformation;
import java.io.Serializable;

/**
* Describes a resource which resides outside of the Portal landscape
* Such resources could be external Javascript toolkits or CSS pages
*
*/
public class ExternalResource implements IResource, Serializable {
private IResourceInformation mm_resInfo;

public ExternalResource(){}

public IResourceInformation getResourceInformation(){
return mm_resInfo;
}

public void init(IResourceInformation resourceInformation) {
mm_resInfo = resourceInformation;
}

public boolean isAvailable() {
return mm_resInfo != null;
}

}

Doesn't do much does it. All of the work is in the init method and using the ExternalResourceInformation object which is an IResourceInformation:


import com.sapportals.portal.prt.component.IPortalComponentRequest;
import com.sapportals.portal.prt.resource.IResourceInformation;
import java.io.Serializable;

/**
*
* Describes the ResourceInformation required by a resource that lives
* outside of the Portal Landscape.
*/
public class ExternalResourceInformation implements IResourceInformation, Serializable {

private String mm_type;
private String mm_fileName;
private boolean mm_useFileName;
private String mm_URL;

/**
* @param resourceType - you should be using the static types defined in IResource.
* @param URL - The URL to the resource you're trying to add
*/
public ExternalResourceInformation(String resourceType, String URL){
mm_type = resourceType;
mm_URL = URL;
}
/* (non-Javadoc)
* @see com.sapportals.portal.prt.resource.IResourceInformation#getComponent()
*/
public String getComponent() {
return "theNameOfYourService";
}

/* (non-Javadoc)
* @see com.sapportals.portal.prt.resource.IResourceInformation#getType()
*/
public String getType() {
return mm_type;
}

/* (non-Javadoc)
* @see com.sapportals.portal.prt.resource.IResourceInformation#getSource()
*/
public String getSource() {
return "";
}

/* (non-Javadoc)
* @see com.sapportals.portal.prt.resource.IResourceInformation#getURL(com.sapportals.portal.prt.component.IPortalComponentRequest)
*/
public String getURL(IPortalComponentRequest arg0) {
return getURL();
}

public String getURL() {
return mm_URL;
}
}

All of the work here is done in the constructor. You just provide the URL and it will pass that to the IResource which will be included in the PortalResponse.

What's amazing about this is how easy this actually was. What's more amazing is how you need to decompile things to really understand how this works. Most IResources are BaseResource objects. Those objects are more complex since they need to ask the portal to build a URL to the resource you're attempting to include. Therefore, using this method must be faster and lighter on the portal itself as well as the browser.

One more thing to do. Enhance the service objects:

A new method signature in our interface:

public IResource getExternalCssResource(String cssURL);


New methods in our implementation:

public IResource getExternalCssResource(String cssURL) {
IResource ret = null;
ret = getResource(IResource.CSS, cssURL);
return ret;
}

private IResource getResource(String URL) {
IResource er = new ExternalResource();
IResourceInformation ri = new ExternalResourceInformation(resourceType, URL);
er.init(ri);
return er;
}

That's it.

Now, if you create a simple "footer" portal component which does nothing but insert your IResource, you can have your CSS at the bottom of your page.


IPortalComponentResponse componentResponse = (IPortalComponentResponse)pageContext.getAttribute(javax.servlet.jsp.PageContext.RESPONSE);
IHtmlHeadService htmlHeadService =
(IHtmlHeadService) PortalRuntime.getRuntimeResources().getService("com.scotts.tiller.portal.layouts.htmlheadservice.HtmlHeadService");
IResource res = htmlHeadService.getExternalScriptResource("http://your/css.css");
componentResponse.include(componentRequest, res);


Now your stylesheet will appear after your portal theme.

Monday, October 06, 2008

Hacking SAP Portal with a Javascript/CSS Service

One of the more, erm, "interesting features" of SAP Portal is the lack of ability to directly access the HTML HEAD tag and insert SCRIPT and LINK tags to your own CSS and Javascripts. Well it's not impossible to do, but SAP doesn't offer this out of the box straight away. Probably because they don't want you breaking things.

But lets say you want to use the Dojo Toolkit or YUI on a new hip AbstractPortalComponent. But you don't want to download and host the scripts locally. You wish to use AOLs CDN or Yahoo's CDN to load the javascript. It's faster and solid in terms of reliability. How can you accomplish this?

The answer is, you need to write a new service to access the HTML HEAD.

Create a service inside NWDS and call it, HtmlHeadService. NWDS will create an interface for the service and an implementation. Go to IHtmlHeadService and insert the following method signatures:

package com.portal.htmlheadservice;

import com.sapportals.portal.prt.service.IService;
import com.sapportals.portal.prt.component.IPortalComponentRequest;

public interface IHtmlHeadService extends IService {

public static final String KEY = "HtmlHeadService";

public void addScript(IPortalComponentRequest request, String scriptURL, String type);
public void addJS(IPortalComponentRequest request, String jsURL);
public void addLink(IPortalComponentRequest request, String linkURL, String type, String rel);
public void addCSSLink(IPortalComponentRequest request, String linkURL);
}

This is pretty simple so far. Next look at the HtmlHeadService object:

package com.portal.htmlheadservice;

import com.sapportals.portal.prt.service.IServiceContext;
import com.sapportals.portal.prt.logger.ILogger;
import com.sapportals.portal.prt.runtime.IPortalConstants;
import com.sapportals.portal.prt.component.IPortalComponentRequest;
import com.sapportals.portal.prt.pom.IPortalNode;
import com.sapportals.portal.prt.connection.PortalHtmlResponse;
import com.sapportals.portal.prt.connection.IPortalResponse;
import com.sapportals.portal.prt.util.html.HtmlDocument;
import com.sapportals.portal.prt.util.html.HtmlHead;
import com.sapportals.portal.prt.util.html.HtmlScript;
import com.sapportals.portal.prt.util.html.HtmlLink;

public class HtmlHeadService implements IHtmlHeadService{

private IServiceContext mm_serviceContext;
private ILogger mm_logger;

public void init(IServiceContext serviceContext) {
mm_serviceContext = serviceContext;
mm_logger = serviceContext.getLogger(IPortalConstants.SERVICE_LOGGER);
mm_logger.info(this, "Initialization of HtmlHeadAccessor");
}

public void afterInit() {
mm_logger.info(this, "After Initialization of HtmlHeadAccessor");
}

public void configure(com.sapportals.portal.prt.service.IServiceConfiguration configuration) {}

public void destroy() {}

public void release() {}

public IServiceContext getContext() {
return mm_serviceContext;
}

public String getKey() {
return KEY;
}

public void addLink(IPortalComponentRequest request, String linkURL, String type, String rel) {
HtmlHead docHead = getHtmlHead(request);
if (docHead != null) {
HtmlLink link = new HtmlLink(linkURL);
link.setType(type);
link.setRel(rel);
docHead.addElement(link);
} else {
mm_logger.severe("Could not get HtmlHead from PortalResponse");
}
}

public void addCSSLink(IPortalComponentRequest request, String linkURL) {
addLink(request, linkURL, "text/css", "stylesheet");
}

public void addScript(IPortalComponentRequest request, String scriptURL, String type) {
HtmlHead docHead = getHtmlHead(request);
if (docHead != null) {
HtmlScript script = new HtmlScript();
script.setSrc(scriptURL);
script.setType(type);
docHead.addElement(script);
} else {
mm_logger.severe("Could not get HtmlHead from PortalResponse");
}
}

public void addJS(IPortalComponentRequest request, String jsURL) {
addScript(request, jsURL, "text/javascript");
}

/* This contains the deprecated method getHtmlDocument(). If this fails, check
* the Web Page Composer based service cssService. It uses the exact same
* method. If this is failing, it should be failing.
*/
private HtmlHead getHtmlHead(IPortalComponentRequest request) {
HtmlHead docHead = null;
IPortalNode node = request.getNode().getPortalNode();
IPortalResponse resp = (IPortalResponse) node.getValue(IPortalResponse.class.getName());
try {
PortalHtmlResponse htmlResp = (PortalHtmlResponse) resp;
HtmlDocument doc = htmlResp.getHtmlDocument();
docHead = doc.getHead();
} catch (Exception cce) {
mm_logger.severe("Exception found: " + cce.getMessage());
cce.printStackTrace(System.err);
}
return docHead;
}
}


Here's the meat of the matter. What does this code do? It uses some undocumented objects to gain access to an HtmlDocument object. This object gives you full access to the entire web page. In this case we're just grabbing a head, you could do much more if you so choose.

So what about the deprecated method getHtmlDocument(), seems bad. Well, with the exception of the fact that SAP is using the exact same method in the recently released Web Page Composer tool, I wouldn't be worried. WPC uses this method to grab its style sheets and javascripts from the KM repositiory. The cool thing is, the code can be repurposed to place anything you like into the page.

How to finalize the service? It needs a ton of SharingReferences in the portalapp.xml file to make it go. This is probably more than it needs, but cssService was using this exact string:

"connection,usermanagement, knowledgemanagement, landscape, htmlb, exportalJCOclient, exportal"

With this service you can easily create a PortalComponent that accesses external stylesheets and javascripts to give your portal that custom look and feel that it's been lacking. Some folks have used this method to change the Portal Title and other features as well. Thanks to Darrell Merryweather at SAP for the inspiration.

Friday, August 29, 2008

XSLT in SAP Portal's Knowledge Management

One of the features of SAP's Portal application is a Knowledge Management library. Think of it as a JSR-170 application that's not JSR-170 compliant.

One of the challenges of working with this library is the lack of meaningful documentation. It's difficult to parse exactly how it works by just looking at the javadocs. There are some examples of what you can do, but they require strange configurations and occasionally bouncing the Portal. Considering a bounce can take 20-30 minutes rather than seconds, that's not an ideal situation.

Let's examine an idea that the UI/Usability designer has had on my current project. He wanted to simply drop XML into the Portal and use XSLT to give the look and feel he was looking for on individual pages.

Seems like a reasonable request. After much searching, I found this document on how to do that within SAP's KM. Go ahead, give it a read. Seems straight forward except for the bouncing of the server and the fact that it is focused more on XML documents rather than XML for the sake of having HTML look proper within the portal. If you've spend any time with Firebug looking at Portal output, you'll understand where I'm coming from.

Needless to say, this seemed highly difficult to actually implement. I don't want to have to bounce a server for each page we develop or each mistake we might make with the XSLT. Velocity of development would be far too slow.

Therefore I set off on a journey to figure out just how the KM APIs work. I ended up with the following code:


import com.sapportals.portal.prt.component.*;
import com.sapportals.wcm.repository.*;
import com.sapportals.wcm.util.uri.*;
import com.sapportals.wcm.util.usermanagement.*;


import org.jdom.*;
import org.jdom.input.*;
import org.jdom.output.*;
import org.jdom.transform.*;

import javax.xml.transform.stream.*;
import javax.xml.transform.*;

public class KmXmlTransformer extends AbstractPortalComponent {

public void doContent(IPortalComponentRequest request, IPortalComponentResponse response) {
IPortalComponentProfile profile = request.getComponentContext().getProfile();
String xmlDocument = profile.getProperty("XmlDocumentPath");
String xsltDocument = profile.getProperty("XsltDocumentPath");

try {
com.sap.security.api.IUser user=(com.sap.security.api.IUser)request.getUser();
com.sapportals.portal.security.usermanagement.IUser epUser = WPUMFactory.getUserFactory().getEP5User(user);
ResourceContext ctx= new ResourceContext(epUser);

RID xmlRid=RID.getRID(xmlDocument);
IResource xmlResource = (ResourceFactory.getInstance().getResource(xmlRid, ctx));

RID xslRid=RID.getRID(xsltDocument);
IResource xslResource = (ResourceFactory.getInstance().getResource(xslRid, ctx));

SAXBuilder builder = new SAXBuilder();

Document docXml = builder.build(xmlResource.getContent().getInputStream());
Document resultDoc = null;

TransformerFactory transformerFactory = TransformerFactory.newInstance();
Templates stylesheet =
transformerFactory.newTemplates(new StreamSource(xslResource.getContent().getInputStream()));
Transformer xslTransformer = stylesheet.newTransformer();

JDOMResult jdRes = new JDOMResult();
JDOMSource jdSrc = new JDOMSource(docXml);
xslTransformer.transform(jdSrc, jdRes);

resultDoc = jdRes.getDocument();

XMLOutputter outputter = new XMLOutputter(Format.getPrettyFormat());
outputter.output(resultDoc, response.getWriter());

} catch (Exception e) {
e.printStackTrace(System.err);
}
}
}
So what does this do? It uses JDOM and an XSLT engine to take an XML file in the repository and transform it with an XSLT file in the repository. It uses properties (XmlDocumentPath, XsltDocumentPath) to define where in the KM those files are. These are configurable so that you can simply reuse this object and just modify the properties to choose different files.

There are some issues with the code. Obviously, it's limited to a single transform in its current form. It also uses a deprecated API in the first three lines of the try block. com.sapportals.portal.security.usermanagement.IUser is a deprecated class. Unfortunately you can't create a ResourceContext without one. Nice professionalism by SAP to not offer an alternative.

Other than those limitations, it works pretty darn well. The only question left to analyze is how well this scales.

Saturday, July 26, 2008

Updating Javascript in SAP Portal

Quick reminder to those of you who've followed my posts on Google Analytics.

Be sure that if you're updating a Javascript file that you take the time to update the version of the file. SAP Portal does this using a query string like construct after the javascript file for its OOTB components.

You can do a similar construct for your files by simply embedding a version into the file name. Following the previous GA example, you can simply update the file name to be ga-split-2.0.1.js

Of course, the next step is to update the PortalComponent to pull in the correct version of the file.

So why do you need to do this? Depending upon how your Portal and Load Balancers are setup with caching and expires headers, you won't push the correct version of the javascript file to the browser unless you update the file name! Why? It's common practice to set Expires headers in the far future and set the browser to cache the javascript file. If your setup is doing that, then any changes you make the the original JS file will not be pulled in unless your users happened to clear their browser cache. Since the chance of your entire user community pulling that off is miniscule, the only way to force them to get the new version of the file is to update the file name!

Also, be sure you head over and look at what Spyvee did to inspire these posts over at NetweaverCentral.

Thursday, July 17, 2008

Enhanced Google Analytics in SAP Portal

If you happened to follow my post on integrating Google Analytics with SAP Portal, and attempted to implement it, you may have found some challenges with the reports. More specifically:
  • If you're using the Light Framework (or derivation), all of your URLs are unreadable. They don't describe what is going on in the page since the Portal uses GUIDs as a URL parameter to gather the appropriate page.
  • If you're using the Default Framework (or derivation), you only show hits on your entry point. Which is great for gathering browser information, but not so much for following user activity.
  • In order to resolve this problem, you decide to add the Analytics iView to other pages in your Portal. Now all of your URLs are really unreadable. In fact, you will find that you receive multiple URLs for the same page, where the only difference is the windowID in the query string. This makes the data flat out unusable.
So, what to do?

There is a single fix that resolves both issues. The fix involves asking Portal where in the Navigation Tree you are. First, add in some imports to your code:

import com.sapportals.portal.navigation.INavigationNode;
import com.sapportals.portal.navigation.NavigationEventsHelperService;
import com.sapportals.portal.prt.runtime.PortalRuntime;
import com.sapportals.portal.prt.pom.IEvent;


In order to use these, you'll need to get the following JARs and import them into your project:
  • com.sap.portal.navigation.api_service_api.jar
  • com.sap.portal.navigation.helperservice_api.jar
  • com.sap.portal.navigation.helperservice_core.jar
One of the methods you can override in an AbstractPortalComponent is doOnNodeReady(). This method is called once the PortalNode has been constructed. At this point, the node can ask the Portal for information. The method is implemented as follows:

    
protected void doOnNodeReady(IPortalComponentRequest request,IEvent arg1) {
// Get the service to access the Navigation information
NavigationEventsHelperService helperService =(NavigationEventsHelperService) PortalRuntime.getRuntimeResources().getService("com.sap.portal.navigation.helperservice.navigation_events_helper");
// Get your current location in the navigation tree
INavigationNode navTargetNode = helperService.getCurrentLaunchNavNode(request);
StringBuffer fullPath = new StringBuffer(navTargetNode.getTitle(Locale.ENGLISH));
// After stashing the title of the node, get the node's parent and loop
// until you've reached the top node. Stash each parent's name and build
// a navigation "path" for use later.
INavigationNode aParent = helperService.getParentNode(navTargetNode, request);
while (aParent != null && !aParent.getTitle(Locale.ENGLISH).equals("")) {
fullPath.insert(0, aParent.getTitle(Locale.ENGLISH) + "/");
aParent = helperService.getParentNode(aParent, request);
}
// store the path in a member variable that can be used inside doContent()
pageTitle = fullPath.toString();
}

Once you've created this path, you can then use it to track the page properly. Inside ga-split-2.js, you should remove the final line which calls pageTracker._trackPageview() Instead, you'll create a set of response.write() calls to use the pageTitle object and write a new snippet of code on each specific page.

The end of doContent will look as follows:


response.include(request, googleAnalyticsDataResource2);
response.write("<script type=\"text/javascript\">\n");
response.write("pageTracker._trackPageview(\""+ pageTitle +"\");\n");
response.write("</script>");

response.setContentType(PortalComponentContentType.HTML);


How to use the enhancements:

If you're in the light framework, it will just work. You can keep the code at the framework level and it will work on every page in the portal. If you're in the default framework, you'll need to add the code to each page that you want to track. You may want to remove the code from the framework and just track pages. The resulting reports will be far more readable and much better for your business users and portal sponsors who would likely be consuming the data (and pretty graphs) that Google Analytics provides.

Wednesday, June 18, 2008

SAP Portal and Google Analytics

Some folks over at Spyvee created a document on how to integrate Google Analytics with SAP Portal. It's a very good document but, I didn't care for the fact that the code would not be entered in a standard place for javascript.

Essentially SAP takes a lot of ownership over how objects are inserted into the portal. Ideally you'd want to place the Google Anayltics code right above the tag in your page. Portal doesn't quite let you do that. At least not with any ease.

The next best place for javascript code is at the bottom of the head....at least in SAP Portal. Why? Because you can easily place it there using an AbstractPortalComponent.

Here's some modified steps to Spyvee's document that will allow you to insert Google Analytics into your Portal Framework and track a whole lot of clicks.

Netweaver Portal Integration:

When you get to this part, create a PAR with an AbstractPortalComponent. Create something like this:

package com.corp.portal.tools;

import com.sapportals.portal.prt.component.*;
import com.sapportals.portal.prt.resource.IResource;

public class GoogleAnalytics extends AbstractPortalComponent {

public void doContent(IPortalComponentRequest request, IPortalComponentResponse response) {
IResource googleAnalyticsDataResource = request.getResource(IResource.SCRIPT, "scripts/ga-split-1.js");
response.include(request, googleAnalyticsDataResource);
IResource googleAnalyticsDataResource2 = request.getResource(IResource.SCRIPT, "scripts/ga-split-2.js");
response.include(request, googleAnalyticsDataResource2);
response.setContentType(PortalComponentContentType.HTML);
}
}


What this code will do is pull two scripts that you will create in the scripts directory of the portal application. These two scripts will be the two parts of the ga.js code you grabbed from Google. The code is split into two pieces surrounded by script tags. So creating the following script files will do the trick:

ga-split-1.js:

var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));


ga-split-2.js:
var pageTracker = _gat._getTracker("UA-XXXXXX-3");
pageTracker._initData();
pageTracker._trackPageview();


Obviously, don't copy this straight as you'll want your personalized tracking code instead of XXXXXXX :)

Once you've uploaded and created an iView, stash the iView at the bottom of your framework. I'm assuming you needed to customize it and aren't using the out of the box SAP framework. If you stash the iView at the bottom, and it's working, you'll see two script tags in the head to your two scripts, and you'll find a script between them calling the google-analytics.com/ga.js script.

Eventually Google will pick up that it's working and you'll begin to track your clicks. Just beware that if you're behind a firewall, you will probably get some strange results as to where your clicks are being routed depending upon your network topology. I've got requests in Ohio showing up as Chicago, which is where the google analytics call is being routed.

ShareThis