Widgets Lifecycle

Widgets are small - non re sizable - pure javascripts components to be deployed in the eXo portal. They can be added in the left column (and sorted there) or inside the WebOS layout.

Init Phase

Widgets are bundled - like portlets - inside dedicated WARs and hence initialized thanks to a ServletListener. Hence each portlet is referenced inside the WebAppController object.

public class ApplicationRegister implements ServletContextListener {
  
  protected static Log log = ExoLogger.getLogger("widget:ApplicationRegister");
  
  /**
   * Each time a new widget application war is deployed then widgets are registered into the
   * WebAppController
   */
  public void contextInitialized(ServletContextEvent event) {
    try {
      String applications = event.getServletContext().getInitParameter("exo.application"); 
      String[] classes = applications.split(",") ;
      RootContainer root = RootContainer.getInstance() ;
      PortalContainer pcontainer =  root.getPortalContainer("portal") ;
      WebAppController controller = 
        (WebAppController)pcontainer.getComponentInstanceOfType(WebAppController.class) ;
      ClassLoader loader = Thread.currentThread().getContextClassLoader() ;
      for(String className : classes) {
        className = className.trim() ;
        log.info("Deploy Widget class name: " + className);
        Class type = loader.loadClass(className) ;
        Application application = (Application)type.newInstance() ;
        controller.addApplication(application) ;
      }
    } catch(Exception ex) {
      log.error("Error while deploying a widget", ex);
    }
  }

That way we register WidgetApplication (extends the Application class) directly inside the WebController. The widget application is a simple abstract class as shown now.

abstract public class WidgetApplication<T> extends Application {
  
  public String getApplicationType() { return "eXoWidget" ; }
  
  abstract public void processRender(T uiWidget, Writer w) throws Exception ;
  
  public ResourceBundle getOwnerResourceBundle(String username, Locale locale) throws Exception {
    throw new Exception("This method is not supported") ;
  }

  public ResourceBundle getResourceBundle(Locale locale) throws Exception {
    throw new Exception("This method is not supported") ;
  }
}

Render Phase

When the Portal is built, UIWidget components are bound to the UI component tree. The UIWidget class extends the UIComponent but it is a simple wrapper that does not contain any business logic as it is delegated to the UIWidgetLifecycle.

/**
   * The processRender method of the UIWidget component is implemented in its bound Lifecycle object.
   * 
   * The Lifecycle itself simply delegates the call to the WidgetApplication stored in the IoC WebAppController
   * component
   */
  @SuppressWarnings("unchecked")
  public void processRender(UIComponent uicomponent , WebuiRequestContext context) throws Exception {
    UIWidget uiWidget = (UIWidget)  uicomponent ;
    PortalContainer container = PortalContainer.getInstance() ;
    WebAppController controller = 
      (WebAppController)container.getComponentInstanceOfType(WebAppController.class) ;
    WidgetApplication application =
      (WidgetApplication) controller.getApplication(uiWidget.getApplicationId()) ;
    if(application != null) application.processRender(uiWidget, context.getWriter()) ;
  }

Here is the sample code of a simple widget that just shows information on it:

public class InfoWidget extends WidgetApplication<UIWidget> {
  
  public String getApplicationId() { return "eXoWidgetWeb/InfoWidget"; }

  public String getApplicationName() { return "InfoWidget"; }

  public String getApplicationGroup() { return "eXoWidgetWeb"; }
  
  public void processRender(UIWidget uiWidget, Writer w) throws Exception {
    PortalRequestContext pContext = Util.getPortalRequestContext();
    MVCRequestContext appReqContext = new MVCRequestContext(this, pContext) ;
    
    int instanceId = uiWidget.getApplicationInstanceId().hashCode() ;
    
    int posX = uiWidget.getProperties().getIntValue("locationX") ;
    int posY = uiWidget.getProperties().getIntValue("locationY") ;
    int zIndex = uiWidget.getProperties().getIntValue("zIndex") ;
    
    w.write("<div id = 'UIInfoWidget' applicationId = '"+instanceId+"' posX = '"+posX+"' posY = '"+posY+"' zIndex = '"+zIndex+"'><span></span></div>") ;
    
    String script = 
      "eXo.portal.UIPortal.createJSApplication('eXo.widget.web.info.UIInfoWidget','UIInfoWidget','"+instanceId+"','/eXoWidgetWeb/javascript/');";
    appReqContext.getJavascriptManager().addCustomizedOnLoadScript(script) ;
  }
}

The last two lines are the most important as our widgets are fill javascript components that - after being launched the first time - only communicate with the server in a REST way exchanging JSON structures to refresh their own DOM tree dynamically.

The last line register the script inside the JavascriptManager object which is bound with the MVCRequestContext. In that context the JavascriptManager is the one from the parent context which means the PortalRequestContext.

Hence for each widgets and several portlets some javascript code can be dynamically appended using the addCustomizedOnLoadScript() method of the JavascriptManager class.

All those scripts are either added at the end of the full HTML code (the main part is taken from UIPortalApplication.gtmpl) or within the AJAX code fragment and in both case the script will be dynamically loaded on the client.

Here is the end of the UIPortalApplication.gtmpl file which is used in the case of the widgets to add the script:

<script type="text/javascript">
      <&#37;=rcontext.getJavascriptManager().getJavascript()&#37;>
        eXo.core.Browser.onLoad();
      <&#37;=rcontext.getJavascriptManager().getCustomizedOnLoadScript();&#37;>
    </script>

Which generates the following HTML code where wthe widget js class is referenced, the instance also as well as the root path on the server to get the associated jstmpl file to use to dynamically modify the DOM.

eXo.core.Browser.onLoad();    
  eXo.portal.UIPortal.createJSApplication('eXo.widget.web.welcome.UIWelcomeWidget','UIWelcomeWidget','1292191470','/eXoWidgetWeb/javascript/');

Javascript Phase

Once the HTML is returned to the client and hence the server phase is over, the browser still needs to load the javascript scripts on the client. Some of them are dynamically loaded and cached

The UIPortal.js script contains the createJSApplication() application as shown in the following js code fragment

/*
* This method will start the creation of a new javascript application such as a widget
*
* - The application parameter is the full javascript class for the application (for example "eXo.widget.web.info.UIInfoWidget")
* - The application id is an id shared among all the application instance
* - The instance id is unique among all the javascript application deployed in the layout
* - The appLocation is the root path location of the jstmpl file on the server (for example /eXoWidgetWeb/javascript/)
*
*  If the application class name is not null, the associated .js file on the server is loaded using the eXo.require() method
*
*  Once loaded the initApplication() method is called; in other words, the application is lazy instantiated and initialized
*  on the client browser
*/
UIPortal.prototype.createJSApplication = function(application, applicationId, instanceId, appLocation) {
	if(application) {
	  eXo.require(application, appLocation);
	  var createApplication = application + '.initApplication(\''+applicationId+'\',\''+instanceId+'\');' ;
	  eval(createApplication);
	}
} ;

The eXo.js file contains the eXo.require() function:

/*
* This method will : 
*   1) dynamically load a javascript module from the server (if no root location is set 
*      then use '/eXoResources/javascript/', aka files
*      located in the eXoResources WAR in the application server). 
*      The method used underneath is a XMLHttpRequest
*   2) Evaluate the returned script
*   3) Cache the script on the client
*
*/
eXo.require = function(module, jsLocation) {
  try {
    if(eval(module + ' != null'))  return ;
  } catch(err) {
    //alert(err + " : " + module);
  }
  window.status = "Loading Javascript Module " + module ;
  if(jsLocation == null) jsLocation = '/eXoResources/javascript/' ;
  var path = jsLocation  + module.replace(/\./g, '/')  + '.js' ;
  var request = eXo.core.Browser.createHttpRequest() ;
  request.open('GET', path, false) ;
  request.setRequestHeader("Cache-Control", "max-age=86400") ;
  request.send(null) ;
  try {
    eval(request.responseText) ;
  } catch(err) {
    alert(err + " : " + request.responseText) ;
  }
} ;

In the case of our sample widget it would mean that it will try to load the UIInfoWidget.js located at the path (URL here) /eXoWidgetWeb/javascript/eXo/widget/web/info/UIInfoWidget.js

The code of that widget is then shown here:

eXo.require('eXo.widget.UIExoWidget');

UIInfoWidget.prototype = eXo.widget.UIExoWidget;
UIInfoWidget.prototype.constructor = UIInfoWidget;

function UIInfoWidget() {
	this.init("UIInfoWidget", "info");
}

if(eXo.widget.web == null) eXo.widget.web = {} ;
if(eXo.widget.web.info == null) eXo.widget.web.info = {};
eXo.widget.web.info.UIInfoWidget = new UIInfoWidget()  ;

As you can see the script is small as everything is extending from the UIEXoWidget.js and when the UIInfoWidget object is instanciated the init() method of the UIExoWidget is called:

/*
* This method is used by any user widget to init itself
*
* - The widget name is is extracted from the full class name
* - A widget can have an icon to be then used in several locations like the application registry
* - A widget must have a width of maximum 220px to fit in the left column of eXo Portal
* - A widget can provide different skins but only a default is mandatory
* 
*/
UIExoWidget.prototype.init = function(appName, appFolder, attrsWidget) {
  this.appCategory = "eXoWidgetWeb" ;
	this.appName = appName ;
	this.attrsWidget = attrsWidget;
	this.appFolder = appFolder;
	var nameWidget = eXo.widget.UIExoWidget.getNameWidget(appName);
	this.appIcon = "/eXoResources/skin/DefaultSkin/portal/webui/component/view/UIPageDesktop/icons/80x80/"+nameWidget+".png" ;
	this.skin = {
	  Default: "/eXoWidgetWeb/skin/"+appFolder+"/DefaultStylesheet.css",
	  Mac:     "/eXoWidgetWeb/skin/"+appFolder+"/MacStylesheet.css",
	  Vista:   "/eXoWidgetWeb/skin/"+appFolder+"/VistaStylesheet.css"
	} ;
	this.width = "220px" ;
	this.height = "auto" ;
};

Then, once the application is loaded and the init() method called, the createJSApplication() calls the initApplication() method of the widget, aka the initApplication() of the UIExoWidget object located in the UIExoWidget.js file


Creator: Benjamin Mestrallet on 2007/08/20 00:49
Copyright (c) 2000-2009. Allright reserved - eXo platform SAS
1.6.13286