Showing posts with label dojo. Show all posts
Showing posts with label dojo. Show all posts

On the fly JavaScript minification using Dojo ShrinkSafe

The Dojo ShrinkSafe is a tool that you can use to minify JavaScript, it can reduce size of JavaScript by close to 1/3 rd or more. You can call ShrinkSafe through commandline, or through Ant as part of your web applications build process. I wanted to see if i can call ShrinkSafe Java API to minify JavaScript.

I built ShrinkSafeFilter which will be part of my .war file and then i applied it to JavaScript URLs and what the ShrinkSafeFilter will do is it will collect the JavaScript file response into String minify it using ShrinkSafe API and return the minifed version to the browser, you can download the sample code from here

My Web application has index.html that includes three JavaScript files which are inside the /WebContent/js folder, this is the screen shot of how the test.js file looks like in the firebug




These are the steps that i had to follow to create ShrinkSafe Filter

  • First i had to create a ShrinkSafeFilter like this


    package com.webspherenotes.performance.minfy;

    import java.io.IOException;

    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.dojotoolkit.shrinksafe.Compressor;

    public class ShrinkSafeFilter implements Filter{


    public void destroy() {
    System.out.println("Entering ShrinkSafeFilter.destroy()");
    System.out.println("Exiting ShrinkSafeFilter.destroy()");
    }

    public void doFilter(ServletRequest request, ServletResponse response,
    FilterChain filterChain) throws IOException, ServletException {
    try {
    System.out.println("Entering ShrinkSafeFilter.doFilter()");

    System.out.println("Received request for " + ((HttpServletRequest)request).getPathInfo());
    SSHttpServletResponseWrapper responseWrapper = new SSHttpServletResponseWrapper((HttpServletResponse)response);
    filterChain.doFilter(request, responseWrapper);
    responseWrapper.finishResponse(response.getWriter());
    System.out.println("Exiting ShrinkSafeFilter.doFilter()");
    } catch (Throwable e) {
    e.printStackTrace(System.out);
    }
    }

    public void init(FilterConfig arg0) throws ServletException {
    System.out.println("Entering ShrinkSafeFilter.init()");
    System.out.println("Exiting ShrinkSafeFilter.init()");
    }

    }

    In the doFilter() method of ShrinkSafeFilter i am wrapping the HttpServletResponse object into the SSHttpServletResponseWrapper method, and then forwarding control to application server for letting it written the static JavaScript file

  • This is how my SSHttpServletResponseWrapper.java looks like
    package com.webspherenotes.performance.minfy;

    import java.io.IOException;
    import java.io.PrintWriter;

    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpServletResponseWrapper;

    import org.dojotoolkit.shrinksafe.Compressor;

    public class SSHttpServletResponseWrapper extends HttpServletResponseWrapper{

    StringOutputStream stringOutputStream;

    public SSHttpServletResponseWrapper(HttpServletResponse response) {
    super(response);
    }


    public ServletOutputStream getOutputStream() throws IOException {
    stringOutputStream = new StringOutputStream();
    return stringOutputStream;
    }

    public void finishResponse(PrintWriter writer){
    String inputJavaScript = stringOutputStream.getString();
    long beginTime = System.currentTimeMillis();
    String outputJavaScript = Compressor.compressScript(inputJavaScript, 0, 1, "all");
    System.out.println("Time took to compress " +( System.currentTimeMillis() -beginTime));
    writer.println(outputJavaScript);

    }

    }

    In the getOutputStream() method i am returning a custom ServletOutputStream object that will be used for collecting the actual response into String. Then inside the finishResponse() method i am taking the actual JavaScript output returned by application server into String and calling the Compressor.compressScript(inputJavaScript, 0, 1, "all") method of ShrinkSafe to minify it.

  • THis is how my StringOutputStream class looks like

    package com.webspherenotes.performance.minfy;

    import java.io.ByteArrayOutputStream;
    import java.io.IOException;

    import javax.servlet.ServletOutputStream;

    class StringOutputStream extends ServletOutputStream {

    ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();

    public void write(byte[] b, int offset, int length) throws IOException {
    byteArrayOutputStream.write(b, offset, length);
    }

    public void write(byte[] b) throws IOException {
    byteArrayOutputStream.write(b);
    }

    public void write(int b) throws IOException {
    byteArrayOutputStream.write(b);
    }

    public String getString() {

    return new String(byteArrayOutputStream.toByteArray());
    }

    public void close() throws IOException {
    byteArrayOutputStream.close();
    }

    public void flush() throws IOException {
    byteArrayOutputStream.flush();
    }

    }

    This is simple ServletOutputStream class that collects the response in string



This is how the firebug output looks like after i applied the ShrinkSafeFilter to it, did you notice the shinksafe has removed comments from the test.js and also the indentation.

Using Dojo and Dijit in the portlet

I made changes in the ValidationCacheSamplePortlet that i built in the last section to demonstrate how to use dojo and dijit in the portlet. As you can see i am including Dojo 1.3.2 from AOL CDN


<%@page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1" session="false"%>
<%@taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>

<style type="text/css">
@import src="http://o.aolcdn.com/dojo/1.3.2/dijit/themes/tundra/tundra.css";
@import src=""http://o.aolcdn.com/dojo/1.3.3/dojo/resources/dojo.css";
</style>
<script type="text/javascript" src="http://o.aolcdn.com/dojo/1.3.2
/dojo/dojo.xd.js" djConfig="parseOnLoad:true">
</script>
<portlet:defineObjects />
<script type="text/javascript">
dojo.require("dijit.form.Button");
dojo.require("dojo.parser");
</script>

<button dojoType="dijit.form.Button" id="btn">Resource
<script type="dojo/method" event="onClick">
dojo.xhrGet({
url: "<portlet:resourceURL/>",
load: function(data, ioargs){
dojo.byId("resourceResponse").innerHTML = data;
},
error: function(error,ioargs){
alert(error);
}
});
</script>
</button>

<div id="resourceResponse" />

Sample Resource Serving Portlet

This is a sample portlet that can be deployed in any Portlet Container and demonstrates how to make a Ajax Resource Call to the portlet without using any JavaScript framework like dojo. The JavaScript code is lengthy then if you use JavaScript framework. But sometimes you cant/dont want to use JavaScript framework for 1 call.

First i did build a JSR 286 compliant ValidationCacheSamplePortlet portlet like this.

public class ValidationCacheSamplePortlet extends GenericPortlet{
protected void doView(RenderRequest request, RenderResponse response)
throws PortletException, IOException {
System.out.println("Entering ValidationCacheSamplePortlet.doView()");
response.setContentType("text/html");
getPortletContext().getRequestDispatcher("/validation.jsp").include(request, response);
System.out.println("Exiting ValidationCacheSamplePortlet.doView()");
}
public void serveResource(ResourceRequest request, ResourceResponse response)
throws PortletException, IOException {
System.out.println("Entering ValidationCacheSamplePortlet.serveResource()");
response.setContentType("text/html");
response.getWriter().println("Hello from ValidationCacheSamplePortlet.serveResource");
System.out.println("Exiting ValidationCacheSamplePortlet.serveResource()");
}
}


The ValidationCacheSamplePortlet is very simple in the doView() method it is passing control to validate.jsp for generating response markup and in the serveResource() method is returning Hello from ValidationCacheSamplePortlet.serveResource String.

This is how the validation.jsp looks like, it has a button Resource button, when you click on that button it is making a HTTP GET call to <portlet:resourceURL/> and displaying the response using alert.


<%@page language="java" contentType="text/html; charset=ISO-8859-1"
pageEncoding="ISO-8859-1" session="false"%>
<%@taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet"%>
<portlet:defineObjects />
<script type="text/javascript">
function createXMLHttpRequestObject() {
var xmlHttp;
try {
xmlHttp = new XMLHttpRequest();
} catch (e) {
try {
xmlHttp = new ActiveXObject("Microsoft.XMLHttp");
} catch (e) {
}
}
if (!xmlHttp)
alert("Error creating the XMLHttpRequest object.");
else
return xmlHttp;
}
var xmlHttp = createXMLHttpRequestObject();
function makeResourceCall(){
if (xmlHttp){
try{
xmlHttp.open("GET", "<portlet:resourceURL/>", true);
xmlHttp.onreadystatechange = handleRequestStateChange;
xmlHttp.send(null);
}catch (e){
alert("Can't connect to server:\n" + e.toString());
}
}
}

function handleRequestStateChange(){
if (xmlHttp.readyState == 4){
if (xmlHttp.status == 200){
alert(xmlHttp.responseText);
}
}
}
</script>
<input type="button" onclick="JavaScript:makeResourceCall()" value="Resource Call" />
<div id="resourceResponse"></div>


Dojo and WebSphere portal 6.1.5

One of the most common questions about WebSphere Portal is, which version of Dojo does IBM WebSphere Portal use and what should i do if i want to use newer version of Dojo.

WebSphere Portal 6.1 is using Dojo Version 1.1.1 and replacing dojo version is not supported by IBM (i.e. if you change it then your own your own). The CSA theme depends heavily on the Dojo in fact almost all of the client side functionality is built using Dojo so even if your using Server side aggregation theme, features like client side wires or setting Preferneces, reading PUMA properties might break

Starting with WebSphere Portal 6.5, IBM is shipping two versions of Dojos

  • Dojo V 1.3.2. This is packaged in its own Web application named Dojo_Resources. You can manage it in the WebSphere Application Server administration console. By default it is deployed at the context root /portal_dojo . The path for the Dojo V 1.3.2 files is wp_profile_root/installedApps/node_name/Dojo_Resources.ear/dojo.war .

  • Dojo V 1.1.1. This is packaged in the directory wp_profile_root/installedApps/node_name/wps.ear/wps.war/themes/dojo/portal_dojo.



The Portal and PortalWeb2 themes use version 1.3.2 by default in WebSphere Portal Version 6.1.5. The Page Builder features and widgets are currently supported on Dojo 1.3.2 only.

IBM Support policy remains same that the version of dojo that is shipped with portal might be replaced entirely

Best practice for using dojo dijit in WebSphere Portal 6.1

WebSphere Portal ships with IBM's Dojo Toolkit. This version is based on Dojo 1.1.1 but has some additional fixes and features that are added by IBM. Also IBM can update or move to different version of Dojo framework in future fixes. You can find IBM Dojo Toolkit 1.1.1 in the theme



Important Note As per IBM support team, they wont support changing the version of Dojo that is shipped with WPS. It means that if you replace the Dojo version that is shipped with IBM's WebSPhere Portal and break the client side code then IBM wont support you.

One of the common requirements is that what if i want to use a Dijit Widget which was introduced in say Dojo 1.2.3 framework. THere are three options

  1. Replacing the Dojo Framework shipped with WPS with version 1.2.3: Biggest problem with this case is that IBM wont support you, Second issue is that WPS has close to 20000 lines of code, how would you test it with new version of dojo. And third issue is that IBM has built quite lot of logic for CSA theme as well as client side API(User Profile, Portlet preference access on client side) using Dojo framework modules. Replacing DOjo version might break this logic

  2. Including multiple versions of DOjo on page: As per Infocenter you cannot have multiple versions of Dojo on one page. I tried following Multiple Versions of Dojo on Page instructions but it did not work for me

  3. Back port your widgets: This is the preferred option, try implementing the Dojo widget that you want using IBM's Dojo Framwork. You should be able to copy the template file and .js file widget as it is

Using Dijit widgets in WebSphere Portal 6.1

Starting from Version 6.1 the Dojo framework is shipped with IBM WebSphere Portal Server. As a result you can assume that Dojo framework would be available on every page. If you want to use Dijit widgets in your portlet or theme code you will have to take some precautions. I did built a sample portlet to demonstrate how you can use Dijit widgets in your portlet.

This is how my sample portlet looks like. It has one dijit.form.Button button.



This is the dijitpage.jsp that i am using to generate markup for the view mode

<%@page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1" session="false"%>
<%@taglib uri="http://java.sun.com/portlet" prefix="portlet"%>
<%@taglib uri="http://www.ibm.com/xmlns/prod/websphere/portal/v6.1/portlet-client-model" prefix="portlet-client-model"%>
<%@taglib uri="http://java.sun.com/portlet" prefix="portletx"%>
<portlet-client-model:init>
<portlet-client-model:require module="ibm.portal.xml.*" />
<portlet-client-model:require module="ibm.portal.portlet.*" />
</portlet-client-model:init>
<portlet:defineObjects />
<script>
dojo.require("dojo.parser");
dojo.require("dijit.form.Button");
dojo.addOnLoad( function (){
console.log("Inside dojo.addOnLoad()");
dojo.parser.parse( "portletWidgetContainer");
});
function call_function(){
console.log("Inside the button event handler");
alert("Inside the button event handler")
}
</script>
<div id="portletWidgetContainer">
<button dojoType="dijit.form.Button" onclick="call_function">
Hi I am the One !!
</button>
</div>


You should follow these guidelines in your portlets

  • You should enclose the markup of your portlet or the markup that is using Dijit widget in div or someother enclosing element

  • Add dojo.addOnLoad() function inside your code and call the dojo.parser.parse(<enclosingelementid>) to parse the markup of the portelt for dijit widgets

  • Dojo framework would be included by theme on the page so dont include it explicitly. Trying to override dojo included by theme might break your theme



You can download the sample DijitPortlet from here