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.

No comments:

ShareThis