Skip Headers
Oracle® Fusion Middleware Client-Side Developer's Guide for Oracle WebLogic Portal
10g Release 3 (10.3.4)

Part Number E14229-02
Go to Documentation Home
Home
Go to Table of Contents
Contents
Go to Feedback page
Contact Us

Go to previous page
Previous
Go to next page
Next
View PDF

6 Client-Side Development Best Practices

This chapter discusses tips and best practices for developing client-side portal code. The chapter includes these topics:

Note:

Opening the same portal desktop in multiple browser windows that share the same process (and, therefore, the same session) is not supported. For example, using the render:pageURL tag or the JavaScript window.open function to open a portal desktop in a new window is not supported and can result in unwanted side effects. For instance, if a portlet is deleted in the new window, and then the user tries to interact with that portlet in the main window, the interaction will fail.

6.1 Namespacing

If you are writing browser-based JavaScript code in a WLP environment, namespacing conflicts can easily arise if you are not careful. This section illustrates the kind of JavaScript namespace conflict that can occur when you place multiple portlets on a page.

6.1.1 A Simple Dynamic Table Portlet

The following example uses JavaScript to add rows to an HTML table dynamically. The Dojo Toolkit is used to provide the primary UI element (a button) and the event handling mechanism. As you will see, you will encounter problems if you intend to use code like this in a portlet/portal context.

Note:

The following code is designed according to the best, recommended practice of using a render dependencies file for including toolkit references, CSS references, and JavaScript functions in a portlet. For an introduction to this technique and step by step instructions on creating and referencing a render dependencies file, see Chapter 4, "Configuring JavaScript Libraries in a Portal Web Project"

The render dependencies file is shown in Example 6-1. A render dependencies file is XML that defines page-level events and resources such as external JavaScript and CSS that are needed by a portlet. The overall pattern of the file follows the pattern discussed in Chapter 4, "Configuring JavaScript Libraries in a Portal Web Project"

The JSP file is shown in Example 6-2. It defines a JavaScript function that adds a row to an HTML table and includes the Dojo button that fires the onClick event that calls the addRowsToTable() function.

Example 6-1 Render Dependencies File

<?xml version="1.0" encoding="UTF-8"?>
<window xmlns="http://www.bea.com/servers/portal/framework/laf/1.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.bea.com/servers/portal/framework/laf/1.0.0       laf-window-1_0_0.xsd">
    <render-dependencies>
        <html>
            <links>
                <search-path>
                    <path-element>../resources/js</path-element>
                </search-path>
                <link href="dijit/themes/tundra/tundra.css" type="text/css" 
rel="stylesheet"/>
            </links>        
            <scripts>
                <search-path>
                    <path-element>../resources/js</path-element>
                </search-path>
<script src="dojo/dojo.js" type="text/javascript"/>
                <script type="text/javascript">
var djConfig = {parseOnLoad:true, isDebug:true};
                </script> 
            </scripts>
        </html> 
    </render-dependencies>
</window>

Example 6-2 JSP File

<script type="text/javascript">
    // Load the Dojo Button widget
    dojo.require("dijit.form.Button");
</script>

<script type="text/javascript" >
    function addRowToTable() { 
    var tbl = document.getElementById("table_1"); 
    var lastRow = tbl.rows.length; 
    var row = tbl.insertRow(lastRow);

    //-- Update left cell 
    var cellLeft = row.insertCell(0); 
    var node1 = document.createTextNode(lastRow);
    cellLeft.appendChild(node1);

    //-- Update right cell 
    var cellRight = row.insertCell(1); 
    var node2 = document.createTextNode(lastRow);
    cellRight.appendChild(node2); }
    </script>

<button dojoType="dijit.form.Button" id="addRowButton">Add Row <script type="dojo/method" event="onClick">
        addRowToTable();
    </script></button>

<br>

<table border="1" id="table_1">
    <tr>
        <th colspan="3">Simple Dynamic Table</th>
    </tr>
    <tr>
        <td>Column 1</td>
        <td>Column 2</td>
    </tr>
</table>

Figure 6-1 shows the JSP portlet added to a portal page. Each time the Add Row button is clicked, a new row is added to the table, and the row number is displayed in the new table cells.

Figure 6-1 Simple Dynamic Table Portal

Description of Figure 6-1 follows
Description of "Figure 6-1 Simple Dynamic Table Portal"

The addRowToTable() JavaScript function retrieves the table element using the table's ID. While the portlet works perfectly well in this scenario, what happens if you add a second identical portlet to the same portal page?

6.1.2 Namespace Collisions

Suppose you add the dynamic table portlet to the same page multiple times. For example, consider the following portal (Figure 6-2), which contains three dynamic table portlets.

Figure 6-2 Multiple Dynamic Table Portlets on a Page

Description of Figure 6-2 follows
Description of "Figure 6-2 Multiple Dynamic Table Portlets on a Page"

The current configuration causes a JavaScript error to be thrown. After the event handler for the first button on the page is registered, subsequent attempts to register it fail. The error message is something like this:

Exception... "'Error: Tried to register widget with id==helloButton but that id is already registered' ...

As a result of this error, only the first button is registered, and it is the only button that works.

When you understand why multiple dynamic table portlets fail to work properly in a portal page, you can learn avoid many common browser-side programming problems encountered by portal developers.

The reason why the dynamic table fails to function properly when the portlet is duplicated is clear when you look at the HTML source code that is generated for the portal page. The portal framework on the server returns exactly the same HTML markup for each portlet. Both the JavaScript block and the HTML table code are duplicated verbatim three times in the same HTML page, as illustrated in Figure 6-3. The WebLogic Portal framework assigns the portlets themselves unique IDs; however, all of the JSP and HTML code is identical for each rendered portlet.

Figure 6-3 Source Code Repetition with Three Identical Portlets on a Page

Description of Figure 6-3 follows
Description of "Figure 6-3 Source Code Repetition with Three Identical Portlets on a Page"

6.1.3 The Mysterious Table Row

It is possible to assign the dijit button in Example 6-2 a unique name, such as by prefixing it with the portlet's unique ID (see techniques discussed in Section 6.2.1, "Using Ad Hoc Namespacing"). Such a technique will avoid the error condition; however, assigning a unique name to the button ID is insufficient to solve the namespacing problem.

Note that when a JavaScript function block is included multiple times, each time a duplicate variable is declared, it is overwritten by subsequent variables with the same name. In this example, the variable addRowButton is declared three times (because the script block is included three times in the page). Therefore, whichever button you click will only affect (add rows to) the first table, as illustrated in Figure 6-4.

Figure 6-4 Rows Mysteriously Added to the Wrong Table

Description of Figure 6-4 follows
Description of "Figure 6-4 Rows Mysteriously Added to the Wrong Table"

Techniques for avoiding these namespace problems are discussed in the next section, Section 6.2, "Avoiding Namespace Collisions".

6.2 Avoiding Namespace Collisions

This section discusses techniques and best practices for avoiding the kinds of namespace collisions illustrated in Section 6.1.2, "Namespace Collisions". The techniques include:

6.2.1 Using Ad Hoc Namespacing

One way to avoid namespace collisions in client-side browser code is to use ad hoc namespacing. Ad hoc namespacing means giving a unique name to:

  • Global functions

  • Global variables

  • DOM IDs (for example: <table id="someUniqueId">)

  • DOM names (apply only to anchor and form tags)

  • References to all of the above

For example, the JSP listed in Section 6.1.2, "Namespace Collisions" can be rewritten so that all of the global functions, variables, and IDs are unique and scoped to a specific portlet. One simple technique for achieving this in a portlet is to append the portlet's instance label to the appropriate global JavaScript identifiers, as shown in Example 6-3. Additions to this code sample are highlighted. As you can see, the example relies on obtaining the portlet instance label from the PortletPresentationContext object. This ID is unique for each portlet that appears in a portal. JSP expressions are then used to append the portlet label to appropriate global identifiers. In this case, we need to uniquely namespace the HTML table ID, the global function addRowToTable(), the Button widget ID, and the Button widget variable.

Tip:

JavaScript variables within functions are locally scoped, and therefore do not need to be namespaced for the global context.

Example 6-3 Dynamic Table JSP with Ad Hoc Namespacing

<%@ page import="com.bea.netuix.servlets.controls.portlet.PortletPresentationContext"%>
<%
// Tip: you can also use the JSP tag <render:encodeName name="someName" .../>
// to accomplish the same task as this scriptlet (obtaining the portlet 
// instance label.)
PortletPresentationContext portletCtx = (PortletPresentationContext) PortletPresentationContext.getPortletPresentationContext(request);
String portletId = portletCtx.getInstanceLabel();
pageContext.setAttribute("prefix", portletCtx.getInstanceLabel()); 
%>

<script type="text/javascript">
    // Load the Dojo Button widget
    dojo.require("dijit.form.Button");
</script>

<script type="text/javascript" >
    function ${prefix}_addRowToTable() {
 var tbl = document.getElementById("${prefix}_table_1");
 var lastRow = tbl.rows.length;
 var row = tbl.insertRow(lastRow);
  
 //-- Update left cell
 var cellLeft = row.insertCell(0);
 var node1 = document.createTextNode(lastRow);
 cellLeft.appendChild(node1);

 //-- Update right cell
 var cellRight = row.insertCell(1);
 var node2 = document.createTextNode(lastRow);
 cellRight.appendChild(node2);
 }
 </script>

<button dojoType="dijit.form.Button" id="${prefix}_helloButton"> Add Row
      <script type="dojo/method" event="onClick">
           ${prefix}_addRowToTable();
       </script>  
</button>

<br>

<table border="1" id="${prefix}_table_1">
<tr>
<th colspan="3">Simple Dynamic Table</th>
</tr>
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>

</table>

If you generate a portlet from this JSP and add the portlet multiple times to a portal, you will see that the portlets function independently and correctly, as shown in Figure 6-5.

Figure 6-5 Namespaced Portlet Code Functions Correctly

Description of Figure 6-5 follows
Description of "Figure 6-5 Namespaced Portlet Code Functions Correctly"

While ad hoc namespacing is an effective way to ensure that portlets with browser code function properly, other techniques and best practices are examined in the following sections.

6.2.2 Using Rewrite Tokens

A well known best practice among JavaScript developers is to externalize functions into .js files. An alternative approach to the ad hoc namespacing technique described in Section 6.2.1, "Using Ad Hoc Namespacing" is to externalize your JavaScript functions and use the WLP render dependencies feature and the rewrite token to provide unique scoping of variables. For additional information on the rewrite token, see the section "Portlet Dependencies" in Oracle Fusion Middleware Portlet Development Guide for Oracle WebLogic Portal.

The first step is to place the JavaScript script blocks that require namespacing into an external .js file. You then reference this file in the render dependencies file.

Tip:

The trick to including the .js file is to include it with the <script content-uri ...> tag rather than with the <script src ...> tag. Although the src tag is the most standard mechanism, if you use it, the rewrite tokens will not be expanded. For information on creating a dependency file and attaching a dependency file to a portlet, see "Portlet Dependencies" in Oracle Fusion Middleware Portlet Development Guide for Oracle WebLogic Portal.

Example 6-4 shows a sample render dependencies file. The included .js file is highlighted in bold text.

Example 6-4 Render Dependencies File

<?xml version="1.0" encoding="UTF-8"?>
<window xmlns="http://www.bea.com/servers/portal/framework/laf/1.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://www.bea.com/servers/portal/framework/laf/1.0.0       laf-window-1_0_0.xsd">
    <render-dependencies>
        <html>
            <links>
                <search-path>
                    <path-element>../resources/js</path-element>
                </search-path>
                <link href="dijit/themes/tundra/tundra.css" type="text/css"                   rel="stylesheet"/>
            </links>        
            <scripts>
                <search-path>
                    <path-element>../resources/js</path-element>
                </search-path>
                <script src="dojo/dojo.js" type="text/javascript"/>
                <script type="text/javascript">
var djConfig = {parseOnLoad:true, isDebug:true};
                </script> 
               <script type=
                 "text/javascript" content-uri="dojotest.js"/>
            </scripts>
        </html> 
    </render-dependencies>
</window>

The contents of the external .js file are shown in Example 6-5. Note that wlp_rewrite_ is appended to all of the function names and identifiers that require unique names within the page.

Example 6-5 Contents of External dojotest.js File with Rewrite Tokens

function wlp_rewrite_addRowToTable() {
var tbl = document.getElementById("wlp_rewrite_table_1");
var lastRow = tbl.rows.length;
var row = tbl.insertRow(lastRow);

//-- Update left cell
var cellLeft = row.insertCell(0);
var node1 = document.createTextNode(lastRow);
cellLeft.appendChild(node1);

//-- Update right cell
var cellRight = row.insertCell(1);
var node2 = document.createTextNode(lastRow);
cellRight.appendChild(node2);
 }

The portal framework takes care of replacing this token with the portlet's instance label, which is a unique identifier.

Example 6-6 shows the JSP file with the JavaScript function removed. Note that the .js file does not have to be imported into the JSP file if it is included in a render dependencies file. Namespacing of the dijit button id and the table id with the unique portlet id is still required in the JSP.

Example 6-6 JSP File

<%@ page import="com.bea.netuix.servlets.controls.portlet.PortletPresentationContext"%>
<%
PortletPresentationContext portletCtx = (PortletPresentationContext) PortletPresentationContext.getPortletPresentationContext(request);
String portletId = portletCtx.getInstanceLabel();
pageContext.setAttribute("prefix", portletCtx.getInstanceLabel()); 
%>

<script type="text/javascript">
    // Load the Dojo Button widget
    dojo.require("dijit.form.Button");
</script>

<button dojoType="dijit.form.Button" id="${prefix}_helloButton"> Add Row
      <script type="dojo/method" event="onClick">
           ${prefix}_addRowToTable();
       </script>  
</button>

<br>

<table border="1" id="${prefix}_table_1">
<tr>
<th colspan="3">Simple Dynamic Table</th>
</tr>
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>

</table>

6.2.3 Parameterizing Your JavaScript Functions

This section discusses another version of the dynamic table example in which parameterized JavaScript functions are factored out into a .js file. This external file is shown in Example 6-7. Note that both the addRow() and init() functions are parameterized. Note that it is a best practice to include external .js files in a portlet using a render dependencies file. See Section 4.3, "Creating a Render Dependencies File".

Example 6-7 External JavaScript File (.js)

function addRow(id) {

var table = document.getElementById(id+"_table");
var numrows = table.getElementsByTagName("tr").length;
var row = table.insertRow(numrows);

//-- Add text to the row cells.
var cellLeft = row.insertCell(0);
var textLeft = document.createTextNode(numrows-1);
cellLeft.appendChild(textLeft);

var cellRight = row.insertCell(1);
var textRight = document.createTextNode(numrows-1);
cellRight.appendChild(textRight);

}
function initialize(id) {
var addHandler = 
function() {
addRow(id);
};

    var addRowButton = dijit.byId(id+"_addRowButton");    
    dojo.connect(addRowButton, 'onClick', addHandler);
}

The JSP file from which the portlet is generated must be written so that the ID parameter is passed to the init() function. Note that the addOnLoad() function is called by passing in an anonymous function that returns the init() function. This technique is necessary, and allows the parameter passed to init() to persist after the addOnLoad() function returns.

Example 6-8 Modified JSP File

<%@ page import="com.bea.netuix.servlets.controls.portlet.PortletPresentationContext"%>
<%
PortletPresentationContext portletCtx = (PortletPresentationContext) PortletPresentationContext.getPortletPresentationContext(request);
pageContext.setAttribute("prefix", portletCtx.getInstanceLabel()); 
%>

<script type="text/javascript">
    // Load the Dojo Button widget
    dojo.require("dijit.form.Button");
</script>

<script type="text/javascript">
    dojo.addOnLoad( function() {
         initialize("${prefix}");
    });
</script>

 
<button dojoType="dijit.form.Button" id="${prefix}_addRowButton"> Add Row
</button>

<br>

<table border="1" id="${prefix}_table">
<tr>
<th colspan="3">Simple Dynamic Table</th>
</tr>
<tr>
<td>Column 1</td>
<td>Column 2</td>
</tr>

</table>

In summary, the original dynamic portlet described in Section 6.1.1, "A Simple Dynamic Table Portlet" has been redesigned to function in a portal/portlet environment. First, global variables and functions were addressed with ad hoc namespacing to prevent namespace collisions. Second, JavaScript code was externalized into a .js file. To accomplish this, we parameterized functions and used techniques of closure and anonymous functions. Of course, externalizing JavaScript code is always a best practice, as it allows for code reuse and reduces clutter in JSP or HTML files.

6.2.4 Using the Disc APIs

You can use the Disc APIs to retrieve unique portlet labels that can be used to scope JavaScript variables. The basic technique is similar to the one described in Section 6.2.1, "Using Ad Hoc Namespacing". With Disc, you can get the portlet label from the portlet's context object. For more information, see Section 7.5, "Using REST and Disc".