3.0 Component Standards

Document created by resplin Employee on Jun 6, 2015Last modified by alfresco-archivist on Aug 31, 2016
Version 2Show Document
  • View in full screen mode

Obsolete Pages{{Obsolete}}

The official documentation is at: http://docs.alfresco.com



3.03.1ExamplesDeveloper Guide


File Structure


Components consist of at least one WebScript (Freemarker template plus optional JavaScript logic), plus optional client-side stylesheet(s) and JavaScript files. They may also reference assets such as images, Adobe Flash movies and sounds.

30ComponentStandards_FileStructure.png


Component Authoring


WebScript Assets


Component WebScripts follow the same standard format as other V3.0 WebScripts with one notable exception: an optional head script which is injected into the <head> part of a template's page. The head script is generally used to include client-side JavaScript (.js) and StyleSheet (.css) assets, but can contain any other HTML mark-up normally reserved for the <head> block. Note that different browsers may treat duplicate tags unpredictably, e.g. if the final rendered page contains two <title> tags.

The html script should contain inline JavaScript initialization code, to instantiate any client-side JavaScript objects the component relies upon. Typically for Share, this involves passing the DOM ID generated by the framework, runtime options such as URL arguments and the i18n message bundle (.properties' script)' in JSON format. To preserve the validity of XHTML mark-up, client-side JavaScript should be wrapped in CDATA blocks:



<script type='text/javascript'>//</script>

It is very important for component authors to allow their components to be instantiated more than once per page. This means making sure no DOM IDs are used in the WebScript body, unless prefixed with the Framework-supplied htmlid, for example





...and also creating JavaScript objects with the new directive, rather than statically.


sample-component.get.desc.xml



<webscript>
  <shortname>Alfresco SampleComponent</shortname>
  <description>Alfresco Samples: Sample Component</description>
  <url>/components/alfresco/sample</url>
</webscript>

sample-component.get.head.ftl



<link rel='stylesheet' type='text/css' href='${url.context}/components/alfresco/sample.css' />
<script type='text/javascript' src='${url.context}/components/alfresco/sample.js'></script>

sample-component.get.html.ftl



<script type='text/javascript'>//</script>


sample-component.get.properties



message.welcome=The sample component loaded and is working as it should

Client-Side Assets


sample.js



/*
* Copyright (C) 2005-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.

* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ('FLOSS') applications as described in Alfresco's
* FLOSS exception.  You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing
*/

/*
* Alfresco.SampleComponent component logic.
* @namespace Alfresco
* @class Alfresco.SampleComponent
*/
(function()
{
   /**
    * YUI Library aliases
    */
   var Dom = YAHOO.util.Dom,
      Event = YAHOO.util.Event;

   /**
    * Alfresco Share aliases
    */
   var $html = Alfresco.util.encodeHTML;

   /**
    * Alfresco.SampleComponent constructor.
    * @return {Alfresco.SampleComponent} the new component instance
    * @constructor
    */
   Alfresco.SampleComponent = function SampleComponent_constructor(htmlId)
   {
      /* Standard component properties */
      this.name = 'Alfresco.SampleComponent';
      this.id = htmlId;

      /* Register with the ComponentManager */
      Alfresco.util.ComponentManager.register(this);

      /* Request YUI Components */
      Alfresco.util.YUILoaderHelper.require(['container'], this.onComponentsLoaded, this);

      return this;
   };

   Alfresco.SampleComponent.prototype =
   {
      /**
       * Default runtime options for this component
       * @property options
       * @type object
       */
      options:
      {
         /**
          * Sample test string
          *
          * @property testString
          * @type string
          * @default ''
          */
         testString: ''
      },

      /**
       * Allows multiple runtime options to be set simultaneously
       * @method setOptions
       * @param obj {object} options to override form defaults
       * @return {Alfresco.SampleComponent} returns 'this' for method chaining
       */
      setOptions: function SampleComponent_setOptions(obj)
      {
         /* Use a YUI helper function to override default options */
         this.options = YAHOO.lang.merge(this.options, obj);
         return this;
      },

      /**
       * Set messages for this component.
       *
       * @method setMessages
       * @param obj {object} Object literal specifying a set of messages
       * @return {Alfresco.SampleComponent} returns 'this' for method chaining
       */
      setMessages: function SampleComponent_setMessages(obj)
      {
         Alfresco.util.addMessages(obj, this.name);
         return this;
      },
     
      /**
       * Fired by YUILoaderHelper when required component script files have
       * been loaded into the browser.
       * @method  onComponentsLoaded
       */
      onComponentsLoaded: function SampleComponent_onComponentsLoaded()
      {
         /* Register the onReady callback when the component parent element has been loaded */
         Event.onContentReady(this.id, this.onReady, this, true);
      },

      /**
       * Fired by YUI when parent div has been constructed and ready for use.
       * @method  onReady
       */
      onReady: function SampleComponent_onReady()
      {
         /* TODO: Initialisation code, e.g. create YUI components. */
         var sampleDiv = Dom.get(this.id + '-sample');
         if (sampleDiv !== null)
         {
            sampleDiv.innerHTML = $html(this.options.testString);
         }

         Alfresco.util.PopupManager.displayMessage(
         {
            text: this._msg('message.welcome', this.name)
         });
      },

      /**
       * Gets a custom message
       *
       * @method _msg
       * @param messageId {string} The messageId to retrieve
       * @return {string} The custom message
       * @private
       */
      _msg: function SampleComponent__msg(messageId)
      {
         return Alfresco.util.message.call(this, messageId, 'Alfresco.SampleComponent', Array.prototype.slice.call(arguments).slice(1));
      }
   };
})();

Module Authoring


A Module is a variant of a Component. The benefit of creating a module instead of a component is that it decreases the page processing and page load on the user's browser and the processing on the server. A lot of pages in Share provides dialogs or popups that may never be used before the user browses to another page. To avoid unnecessary page loading only parts of the modules are loaded into the page in it's initial state. If the user decides to use the functionality from a module, the rest of the functionality for that module will be loaded into the current page and then presented to the user.

A good example of this is the Create Site dialog module in the Alfresco Share application. The Create Site functionality is accessible anywhere in the share application from the Header component's 'Sites'-menu. Since the Create Site dialog only will be used a fraction of the times it could be, it is written as a module instead of a component.

The following section describes how a module is used, let's continue to use the Create Site module and Header component as an example:


  • The Header component is responsible to create the Create Site module and tell it to show itself since it's the Header component that has the 'Create site' link in it's 'Sites'-menu. To be able to do this the Header component includes the Create Site modules javascript and css-file (create-site.js and create-site.css) in it's own head file (header.get.head.ftl). The Header component creates the Create Site module and tells it to show itselt calling the following code from it's javascript file (header.js):
Alfresco.module.getCreateSiteInstance().show();

  • The Create Site module now has to show itself and also be able to provide user feedback in the correct language if something goes wrong, in other words to be internationalised (i18n). To do this it needs it's HTML markup and i18n message, which hasn't been loaded  since it wasn't needed until now. To get the HTML markup and the i18n messages (which is injected through a javascript call in create-site.get.html.ftl) the Create Site module will make an ajax call to the server, take the response and insert it to the browser Dom and then add event listeners and YUI widget functionality to the create site dialog.

By using this approach neither the server or the user has to bother with loading the module's template and i18n messages for every page. The only thing that needs to be loaded is the javascript and the css file, which will be cached in the browser anyway and not cause any additional page load.


Differences between modules and components


Hopefully the basics of a module is quite clear at this point, to clarify a few things here are the main differences between a module and a component.


Component binding and template HTML


  • The Header component is bound into the page though a region tag:
    <script type='text/javascript'>new Alfresco.Header('${args.htmlid}')</script>

  • The Create Site module javascript instance is created by the Header components javascript instance from one of the 'Sites'-menu's event handlers in header.js. (Note! Create Site is a singleton but other modules could use regualar constructors)
Alfresco.module.getCreateSiteInstance()

i18n


  • The Header component's HTML template code gets its i18n from freemarker tags when it is executed on the server on page creation.
  • The Create site module's HTML template code gets its i18n from freemarker tags when it is requested and executed on the server after a user decides to use the funtionality in the Create Site dialog, in other words after page creation.
  • The Header component's javascript instance is created and gets its i18n from its HTML template, header.get.html.ftl:
<script type='text/javascript'>new Alfresco.Header('${args.htmlid}').setMessages(${messages});</script>

  • The Create Site component's javascript instance has already been created by the Header component when it's template is requested. Instead of trying to find its javascript instance the template adds the i18n messages, for the Create Site module, to the global Alfresco i18n object by invoking the following code in create-site.get.html.ftl:
<script type='text/javascript'>Alfresco.util.addMessages(${messages}, 'Alfresco.module.CreateSite');</script>



Note! When the Create Site javascript instance loads the template from the server it will pass in the following attribute to make sure the scripts inside the template will get run in all browser.


Alfresco.util.Ajax.request({
   url: Alfresco.constants.URL_SERVICECONTEXT + 'modules/create-site',
   ...
   execScripts: true
});



Note! Even thought the Create Site template doesn't do it, it is possible for it to find it's javascript instance by making a call to the Alfresco component registry.



   var createSiteInstance = Alfresco.util.ComponentManager.find({id: '${args.htmlid}'})[0];

WebScript Assets


sample-module.get.desc.xml



<webscript>
  <shortname>Alfresco SampleModule</shortname>
  <description>Alfresco Samples: Sample Module</description>
  <url>/modules/alfresco/sample</url>
</webscript>

sample-module.get.head.ftl?


A module has no head file, instead it's dependencies such as javascript and css files must be imported in the head file for the component that uses the module.


sample-module.get.html.ftl




  
${msg('header.welcome')}

  
${msg('label.about')}
  

<script type='text/javascript'>//</script>

sample-module.get.properties



header.welcome=Welcome to the Sample Module
label.about=This is a sample of a module to demonstrate how modules can be used to decrease the page load.
message.templateFailure=Could not load template for sample module

Client-Side Assets


sample-module.js



/**
* Copyright (C) 2005-2008 Alfresco Software Limited.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.

* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.

* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

* As a special exception to the terms and conditions of version 2.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* and Open Source Software ('FLOSS') applications as described in Alfresco's
* FLOSS exception.  You should have recieved a copy of the text describing
* the FLOSS exception, and it is also available here:
* http://www.alfresco.com/legal/licensing
*/

/**
* Sample module
*
* A sample module
*
* @namespace Alfresco.module
* @class Alfresco.module.SampleModule
*/
(function()
{
   /**
    * YUI Library aliases
    */
   var Dom = YAHOO.util.Dom,
      Event = YAHOO.util.Event;

   /**
    * Alfresco Share aliases
    */
   var $html = Alfresco.util.encodeHTML;
  
   /**
    * Sample module constructor.
    *
    * @param htmlId {string} A unique id for this module
    * @return {Alfresco.module.SmapleModule The new SampleModule instance
    * @constructor
    * @private
    */
   Alfresco.module.SampleModule = function(containerId)
   {
      this.name = 'Alfresco.module.SampleModule';
      this.id = containerId;
      this.widgets = {};

      var instance = Alfresco.util.ComponentManager.find(
      {
         id: this.id
      });
      if (instance !== undefined && instance.length > 0)
      {
         throw new Error('An instance of Alfresco.module.SampleModule already exists.');
      }
     
      // Register this component
      Alfresco.util.ComponentManager.register(this);

      // Load YUI Components
      Alfresco.util.YUILoaderHelper.require(['container'], this.onComponentsLoaded, this);

      return this;
   };

   Alfresco.module.SampleModule.prototype =
   {
      /**
       * Object container for storing YUI widget instances.
       *
       * @property widgets
       * @type object
       */
       widgets: null,

      /**
       * Fired by YUILoaderHelper when required component script files have
       * been loaded into the browser.
       *
       * @method onComponentsLoaded
       */
      onComponentsLoaded: function SM_onComponentsLoaded()
      {
          // Don't do anything since the template isn't loaded at this point
      },

      /**
       * Shows the SampleModule dialog to the user.
       *
       * @method show
       */
      show: function SM_show()
      {
         if (this.widgets.panel)
         {
            /**
             * The panel gui has been showed before and its gui has already
             * been loaded and created
             */                             
            this._showPanel();
         }
         else
         {
            /**
             * Load the gui from the server and let the templateLoaded() method
             * handle the rest.
             */
            Alfresco.util.Ajax.request(
            {
               url: Alfresco.constants.URL_SERVICECONTEXT + 'modules/sample-module',
               dataObj:
               {
                  htmlid: this.id
               },
               successCallback:
               {
                  fn: this.onTemplateLoaded,
                  scope: this
               },
               execScripts: true,
               failureMessage: Alfresco.util.message('message.templateFailure', this.name)
            });
         }
      },

      /**
       * Called when the SampleModule html template has been returned from the server.
       * Creates the YUI panel and shows it.
       *
       * @method onTemplateLoaded
       * @param response {object} a Alfresco.util.Ajax.request response object
       */
      onTemplateLoaded: function SM_onTemplateLoaded(response)
      {
         // Inject the template from the XHR request into a new DIV element
         var containerDiv = document.createElement('div');
         containerDiv.innerHTML = response.serverResponse.responseText;

         // The panel is created from the HTML returned in the XHR request, not the container
         var panelDiv = Dom.getFirstChild(containerDiv);

         this.widgets.panel = new YAHOO.widget.Panel(panelDiv,
         {
            modal: true,
            draggable: false,
            fixedcenter: true,
            close: false,
            visible: false
         });

         // Add the panel to the Dom
         this.widgets.panel.render(document.body);

         // Show the panel
         this._showPanel();
      },

      /**
       * Prepares the gui and shows the panel.
       *
       * @method _showPanel
       * @private
       */
      _showPanel: function CS__showPanel()
      {
         // Show the upload panel
         this.widgets.panel.show();
      }
   };
})();

Alfresco.module.getSampleModuleInstance = function()
{
   var instanceId = 'alfresco-sampleModule-instance',
      instance = Alfresco.util.ComponentManager.find(
      {
         id: instanceId
      });
   if (instance !== undefined && instance.length != 0)
   {
      instance = instance[0];
   }
   else
   {
      instance = new Alfresco.module.SampleModule(instanceId);
   }
   return instance;
};

Outcomes