Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Repair Jira Macros

...

Content Distribution Networks (CDNs)

Info

Hold off on using CDN URLs until

Jira Legacy
serverSystem JIRA
serverIdd8d429a7-dc92-3696-85f0-1de5d4e9bcf6
keyRESSERV-82
is implemented. Otherwise it becomes a problem when you are attempting to work on uPortal or do demos in offline mode.

Some popular libraries are hosted on CDNs.  To decrease load times (browser accesses a different domain) and potentially improve performance (a different application may have downloaded the JS library from the CDN and have it in the browser's cache), a CDN can be used instead of obtaining the library from the Resource Server.  The CDN must support HTTPS to access the library so user's don't get security warnings.

...

Preferred way to access resources from a CDN is to put them in a skin.xml file and use the rs:aggregatedResources tag so you get the non-minified versions when desired.   The below example assumes 

Code Block
titlePreferred way to access CDN content
skin.xml:
 
<resources xmlns="http://www.jasig.org/uportal/web/skin"  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://www.jasig.org/uportal/web/skin https://source.jasig.org/schemas/resource-server/skin-configuration/skin-configuration-v1.2.xsd">


    <js included="plain">//code.jquery.com/jquery-1.10.2.js</js>
    <js included="aggregated">//code.jquery.com/jquery-1.10.2.min.js</js>
    <js included="plain">//code.jquery.com/ui/1.10.3/jquery-ui.js</js>
    <js included="aggregated">//code.jquery.com/ui/1.10.3/jquery-ui.min.js</js>
</resources>
 
JSP file:
 
<rs:aggregatedResources path="skin.xml"/>

The alternative is to load them from the CDNs directly as you would in any webapp.

Code Block
titlenon-preferred way to access CDN content
<script src="//code.jquery.com/jquery-1.10.2.min.js" type="text/javascript"></script>

Loading Javascript

Below is an example of loading jQuery 1.10.2, jQuery TreeView 1.4.0 and assigning them under the myPortletName var with the portlet's namespace key. The myPortletName var with the portlet namespace property is used as a namespace for this portlet's JavaScript, allowing the portlet to declare its own functions without placing them in the global namespace.

...

<!-- Use a resource URL for common javascript libraries that multiple portlets may share and is present in the Resource Serving Webapp. -->
<script src="//code.jquery.com/jquery-1.10.2.min.js" type="text/javascript"></script>
<script src="<rs:resourceURL value='/rs/jquery-treeview/1.4.0/jquery.treeview.js'/>" type="text/javascript" ></script>
<script src="${pageContext.request.contextPath}/javascript/myPortlet.js" type="text/javascript" ></script>
<!-- alternate style <script src="<c:url value='/javascript/myPortlet.js'/>" type="text/javascript" ></script> -->

<script type="text/javascript">
/*
 * Don't overwrite an existing myPortletName variable, just add to it
 */
var myPortletName = myPortletName || {};
myPortletName["${n}"] = myPortletName["${n}"] || {};
/*
 * Switch jQuery to extreme noConflict mode, keeping a reference to it in the myPortletName["${n}"] namespace
 */
myPortletName["${n}"].jQuery = jQuery.noConflict(true);

/**
 * Use an anonymous function to initialize all JavaScript for this portlet.
 */
(function($) {
    // treeview init
    $('#${n}community ul.listing').treeview({
        animated: {duration:300, easing:"swing"},
        collapsed: true,
        unique: false
    });
    $('#${n}community ul.treeview ul').siblings('a').click(function(e) {
        e.preventDefault();
        $(this).siblings('div.hitarea').trigger('click');
        $(this).blur();
    });
})(myPortletName["${n}"].jQuery);
</script>

Using this approach the portlet is ensured it will not be affected by other versions of jQuery loaded on the same page or other JavaScript frameworks that affect $. The approach is described on jQuery's Using jQuery with Other Libraries page.

 

If you've got your javascript in a .js file -- one that can't resolve the server-side expression '#${n}container' -- you can launch something in the .js file from within the .jsp, passing a reference to the ${n}container div.

Code Block
// In a JSP file:
<script>

myPortletName.myObj["${n}"] = customJsLib.myCustomConstructor($('#${n}container'));
myPortletName.myObj["${n}"].init();

</script>

jQuery Selectors

If you need to target an element in your markup, you must also make sure that those elements are named appropriately. In most cases your portlet is just one small part of the content on the page and it is easy to end up with id and class conflicts. The portlet API provides a <portlet:namespace/> tag that generates a unique, JavaScript/CSS safe, string for your instance of the portlet. A common pattern for using this value is to set it as a JSP variable and use the variable in the markup and JavaScript to end up with unique IDs in the output.

Code Block
languagehtml/xml
<!-- Store the portlet namespace string in the JSP page variable "n" -->
<c:set var="n"><portlet:namespace/></c:set>
 
<!-- Create a message block using the namespace variable to give it a unique ID -->
<div id="${n}loadingMessage" class="portlet-msg-info" role="status">
  <h3><spring:message code="loading.information"/></h3>
  <p><spring:message code="please.wait.while.the.system.finishes.loading.the.requested.data"/></p>
</div>


 
<script type="text/javascript">
communities.jQuery(function() {
 var $ = communities.jQuery;


 //Use the namespace variable to select the div and perform a slideDown transition
 $("#${n}loadingMessage").slideDown(500);
});
</script>

This approach will allow you to have multiple instances of a portlet on one page and each will target the correct element within the portlet.  Alternately a recommended approach is to use a namespaced div to surround all portlet content as discussed on CSS Best Practices so you can target elements using the following approach without specifying id attributes on portlet page content:

Code Block


<%-- This example assumes the portlet.xml defines a portlet preference includeJsLibs=false that the installer can override to set to true if needed.
     The portletPreferencesValues variable comes from the defineObjects tag library. --%>
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0" %>
<portlet:defineObjects/>
<c:if test="${portletPreferencesValues['includeJsLibs'][0] != 'false'}">
    <rs:aggregatedResources path="/resources.xml"/>
</c:if>
 
<script type="text/javascript"><rs:compressJs>
    var ${n} = {};   <%-- This is one way to namespace this portlet's version of jQuery --%>
<c:choose>
    <c:when test="${portletPreferencesValues['includeJsLibs'][0] != 'false'}">
        ${n}.jQuery = jQuery.noConflict(true)
    </c:when>
    <c:otherwise>
        ${n}.jQuery = up.jQuery;
    </c:otherwise>
</c:choose>
    ${n}.jQuery(function(){
        ...
    }
</rs:compressJs></script>

The alternative is to load them from the CDNs directly as you would in any webapp.

Code Block
titlenon-preferred way to access CDN content
<script src="//code.jquery.com/jquery-1.10.2.min.js" type="text/javascript"></script>

Loading Javascript

Below is an example of loading jQuery 1.10.2, jQuery TreeView 1.4.0 and assigning them under the myPortletName var with the portlet's namespace key. The myPortletName var with the portlet namespace property is used as a namespace for this portlet's JavaScript, allowing the portlet to declare its own functions without placing them in the global namespace.

Code Block
html
html
<!-- Store the portlet namespace string in the JSP page variable "n" -->
<c:set var="n"><portlet:namespace/></c:set>
 
<!-- Use a resource URL for common javascript libraries that multiple portlets may share and is present in the Resource Serving Webapp. -->
<script src="//code.jquery.com/jquery-1.10.2.min.js" type="text/javascript"></script>
<script src="<rs:resourceURL value='/rs/jquery-treeview/1.4.0/jquery.treeview.js'/>" type="text/javascript" ></script>
<script src="${pageContext.request.contextPath}/javascript/myPortlet.js" type="text/javascript" ></script>
<!-- alternate style <script src="<c:url value='/javascript/myPortlet.js'/>" type="text/javascript" ></script> -->

<script type="text/javascript">
/*
 * Don't overwrite an existing myPortletName variable, just add to it
 */
var myPortletName = myPortletName || {};
myPortletName["${n}"] = myPortletName["${n}"] || {};
/*
 * Switch jQuery to extreme noConflict mode, keeping a reference to it in the myPortletName["${n}"] namespace
 */
myPortletName["${n}"].jQuery = jQuery.noConflict(true);

/**
 * Use an anonymous function to initialize all JavaScript for this portlet.
 */
(function($) {
    // treeview init
    $('#${n}community ul.listing').treeview({
        animated: {duration:300, easing:"swing"},
        collapsed: true,
        unique: false
    });
    $('#${n}community ul.treeview ul').siblings('a').click(function(e) {
        e.preventDefault();
        $(this).siblings('div.hitarea').trigger('click');
        $(this).blur();
    });
})(myPortletName["${n}"].jQuery);
</script>

Using this approach the portlet is ensured it will not be affected by other versions of jQuery loaded on the same page or other JavaScript frameworks that affect $. The approach is described on jQuery's Using jQuery with Other Libraries page.

 

If you've got your javascript in a .js file -- one that can't resolve the server-side expression '#${n}container' -- you can launch something in the .js file from within the .jsp, passing a reference to the ${n}container div.

Code Block
// In a JSP file:
<script>

myPortletName.myObj["${n}"] = customJsLib.myCustomConstructor($('#${n}container'));
myPortletName.myObj["${n}"].init();

</script>

jQuery Selectors

If you need to target an element in your markup, you must also make sure that those elements are named appropriately. In most cases your portlet is just one small part of the content on the page and it is easy to end up with id and class conflicts. The portlet API provides a <portlet:namespace/> tag that generates a unique, JavaScript/CSS safe, string for your instance of the portlet. A common pattern for using this value is to set it as a JSP variable and use the variable in the markup and JavaScript to end up with unique IDs in the output.

Code Block
languagehtml/xml
<!-- Store the portlet namespace string in the JSP page variable "n" -->
<c:set var="n"><portlet:namespace/></c:set>
 

<link rel="stylesheet" href="/MyPortletWebapp/css/my_portlet.css" type="text/css"/>
<div class="MyPortletWebapp" <!-- Create a message block using the namespace variable to give it a unique ID -->
<div id="${n}containerloadingMessage">
  class="portlet-msg-info" role="status">
  <!-- portlet content here, such as -->

	<div class="email-message">
        <!-- content -->
    <h3><spring:message code="loading.information"/></h3>
  <p><spring:message code="please.wait.while.the.system.finishes.loading.the.requested.data"/></p>
</div>

<div>
 
<script type="text/javascript">
communities.jQuery(function() {
  var $ = communities.jQuery;


 //Use the namespace variable to select the div and perform a slideDown transition

 $("#${n}container .email-messageloadingMessage").slideDown(500);
});
</script>

JavaScript Caching

The other important approach the above example takes is to fully qualify the paths to the JavaScript. The full library name, version number and packaging should be included on the URL that the script is loaded from. This allows the portlet developer to set aggressive browser caching headers for the JavaScript to reduce subsequent page load times. If a JavaScript framework is upgraded the change in the version number in the URL will ensure the new version is loaded.

Bootstrap in portlets

Portlets desiring to use Bootstrap JavaScript have an added complexity in that the Bootstrap JavaScript cannot be loaded multiple times on the page.  Each portlet needs to cooperate in using either the portal's Bootstrap Javascript (if present) or a single Bootstrap JavaScript shared by all portlets.  The portlet should use the following approach.  This is not needed if the portlet is just using Bootstrap CSS.

Code Block
languagejs
<script src="//code.jquery.com/jquery-1.10.2.min.js"></script>

<script type="text/javascript">
    // Bootstrap javascript fails if included multiple times on a page.
    // uPortal Bootstrap best practice: include bootstrap if and only if it is not present and save it to
    // portlets object. Bootstrap functions could be manually invoked via portlets.bootstrapjQuery variable.
    // All portlets using Bootstrap Javascript must use this approach.  Portlet's jQuery should be included
    // prior to this code block.
    var portlets = portlets || {};
    // If bootstrap is not present at uPortal jQuery nor a community bootstrap, dynamically load it.
    up.jQuery().carousel || portlets.bootstrapjQuery || document.write('<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"><\/script>');
</script>

<script type="text/javascript">
    // Must be in separate script tag to insure bootstrap was dynamically loaded.
    portlets["${n}"] = {};
    portlets["${n}"].jQuery = jQuery.noConflict(true);
    // If bootstrap JS global variable was not defined, set it to the jQuery that has bootstrap attached to.
    portlets.bootstrapjQuery = portlets.bootstrapjQuery || (up.jQuery().carousel ? up.jQuery : portlets["${n}"].jQuery);
</script>

 

Development Tips

...

titleJavascript aggregation tip

On your local and dev instance, you can permanently disable JavaScript and CSS aggregation by adding to your .bash_profile:

export JAVA_OPTS="$JAVA_OPTS -Dorg.jasig.resourceserver.utils.aggr.aggregated_theme=false”

...

This approach will allow you to have multiple instances of a portlet on one page and each will target the correct element within the portlet.  Alternately a recommended approach is to use a namespaced div to surround all portlet content as discussed on CSS Best Practices so you can target elements using the following approach without specifying id attributes on portlet page content:

Code Block
<!-- Store the portlet namespace string in the JSP page variable "n" -->
<c:set var="n"><portlet:namespace/></c:set>
 
<link rel="stylesheet" href="/MyPortletWebapp/css/my_portlet.css" type="text/css"/>
<div class="MyPortletWebapp" id="${n}container">
    <!-- portlet content here, such as -->

	<div class="email-message">
        <!-- content -->
    </div>

<div>

<script type="text/javascript">
communities.jQuery(function() {
  var $ = communities.jQuery;

  //Use the namespace variable to select the div and perform a slideDown transition
  $("#${n}container .email-message").slideDown(500);
});
</script>

JavaScript Caching

The other important approach the above example takes is to fully qualify the paths to the JavaScript. The full library name, version number and packaging should be included on the URL that the script is loaded from. This allows the portlet developer to set aggressive browser caching headers for the JavaScript to reduce subsequent page load times. If a JavaScript framework is upgraded the change in the version number in the URL will ensure the new version is loaded.

Bootstrap in portlets

Portlets desiring to use Bootstrap JavaScript have an added complexity in that the Bootstrap JavaScript cannot be loaded multiple times on the page.  Each portlet needs to cooperate in using either the portal's Bootstrap Javascript (if present) or a single Bootstrap JavaScript shared by all portlets.  The portlet should use the following approach.  This is not needed if the portlet is just using Bootstrap CSS.

Code Block
languagejs
<script src="//code.jquery.com/jquery-1.10.2.min.js"></script>

<script type="text/javascript">
    // Bootstrap javascript fails if included multiple times on a page.
    // uPortal Bootstrap best practice: include bootstrap if and only if it is not present and save it to
    // portlets object. Bootstrap functions could be manually invoked via portlets.bootstrapjQuery variable.
    // All portlets using Bootstrap Javascript must use this approach.  Portlet's jQuery should be included
    // prior to this code block.
    var portlets = portlets || {};
    // If bootstrap is not present at uPortal jQuery nor a community bootstrap, dynamically load it.
    up.jQuery().carousel || portlets.bootstrapjQuery || document.write('<script src="//netdna.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"><\/script>');
</script>

<script type="text/javascript">
    // Must be in separate script tag to insure bootstrap was dynamically loaded.
    portlets["${n}"] = {};
    portlets["${n}"].jQuery = jQuery.noConflict(true);
    // If bootstrap JS global variable was not defined, set it to the jQuery that has bootstrap attached to.
    portlets.bootstrapjQuery = portlets.bootstrapjQuery || (up.jQuery().carousel ? up.jQuery : portlets["${n}"].jQuery);
</script>

 

Development Tips

Tip
titleJavascript aggregation tip

On your local and dev instance, you can permanently disable JavaScript and CSS aggregation by adding to your .bash_profile:

export JAVA_OPTS="$JAVA_OPTS -Dorg.jasig.resourceserver.utils.aggr.aggregated_theme=false”
Also see UE Development Tips
Tip

To use JSTL expressions like ${portletPreferencesValues['includeJsLibs'][0] != 'false', make sure you have the portlet 2.0 version of the portlet tag, not the portlet 1.0 version; e.g.

 

<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet_2_0" %>
<portlet:defineObjects/>

not
 
<%@ taglib prefix="portlet" uri="http://java.sun.com/portlet" %>
<portlet:defineObjects/>
Info
iconfalse
titleAdditional References

CSS Best Practices

JSP Best Practices