Web Editor Framework

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.3


Introduction


The WebEditor framework (WEF) is a framework that consists of a a few core components and UI widgets. Together they allow plugins to be easily developed and added to the WebEditor.
When deployed, it loads all specified resources and creates a ribbon widget which plugins can add their functionality to. Typically, this means adding a toolbar and its buttons and perhaps a help link.
The plugin-able nature of the framework is supplied by the support of hooks around core methods which fire events before and after when each method is called. Other methods can also be specified so that before and after events are fired for those methods. See the #Events_and_Hooks section


Core WEF Components


A high level description of the core components are described below. All javascript files are fully documented with jsdoc and additional info may be found there.


WEF


The WEF object is responsible for bootstrap the WebEditor framework onto the page by loading all required modules and starting the application when all the resources are loaded. It has a config which looks likes this:



WEF.init(
{
   /**
    * Context path of application
    *
    * @type String
    */
   contextPath: '/awe',
  
   /**
    * Debug mode
    *
    * @type Boolean
    */
   debugMode: false,

   /**
    * constants
    * @type Object
    */
   constants: {},

   /**
    * Object literal of applications to render.
    *
    * @type Object
    */
   applications: {},
  
   /**
    * Configuration for loader
    * 
    */
   loaderConfig:
   {
      [See Loader for information about the loader config]
   }
});

Loader


The loader is responsible for setting up resources for modules (and any of their dependencies) to be loaded by the browser. A resource can be specified as one that should be loaded as follow:



   WEF.addResource({
      name: 'com.yahoo.bubbling',
      type: 'js',
      path: '\/awe/res/wef/bubbling.v2.1.js',
      requires: ['utilities'],
      varName: 'YAHOO.Bubbling'
   }
);

The loader can be configured and has the following options:



   {
      /**
       * Server port of awe app
       *
       * @type String
       */
      serverPort: window.location.protocol + '//' + window.location.host,
     
      /**
       * Context path of awe app
       *
       * @type String
       */
      urlContext: window.location.protocol + '//' + window.location.host + '/awe' + '/res',
     
      /**
       * Use sandbox to load files
       *
       * @type Boolean
       */
      useSandboxLoader: false,
     
      /**
       * Path to yuiloader. This is loaded via script tags so can be absolute or relative
       *
       * @type String
       */
      yuiloaderPath: '/yui/yuiloader/yuiloader-debug.js',
     
      /**
       * Base path to yui files. Use empty string to use YDN
       *
       * @type string
       */
      yuibase: '/awe/res/yui/',
     
      /**
       * A filter to apply to loader.
       * 3rd party plugins need to be available in -[filter].js versions too
       * Defaults to min if not supplied.
       *
       * @type String
       */
      filter: 'min',
     
      /**
       * Flag for yui loader to determine whether to load extra optional resources as well
       *
       * @type Boolean
       */
      loadOptional: true,
     
      /**
       * Skin overrides for YUI
       *
       * @type Object
       */
      skin:
      {
         base: '/assets/skins/',
         defaultSkin: 'sam'
      },
      /**
       * moduleInfo overrides for YUI loader. Useful if YUI loader module names differ across releases
       * eg selector-beta (using loader from yui v2.6) vs selector (yui v2.7+)
       *
       * @type Object
       */
       moduleInfoOverrides:
       {
          'selector': function WEF_selectorModuleInfoOverride(module, yuiVersion)
          {
            if (yuiVersion

The loader config provides a way to change a module metadata prior to loading. This can be useful, for instance, if filenames of the module source file change between releases. The above shows an example (moduleInfoOverrides) where the file path of the YUI's selector utility changes from selector-beta to selector after version 2.7.

In addition, the loader config also allows the specification of functions that run before and after a specified module is loaded. This can be useful if some pre or post initialization is needed.


ConfigRegistry


The ConfigRegistry object is responsible for maintaining a registry of configuration objects for a specified plugin. It has two methods


registerConfig()


This registers a config for a specified plugin using the id of the plugin.

  WEF.ConfigRegistry.registerConfig('org.springframework.extensions.webeditor.ui.ribbon',{ position: 'top' });

getConfig()


getConfig returns the config for the specified plugin

  WEF.ConfigRegistry.getConfig('org.springframework.extensions.webeditor.ui.ribbon');

PluginRegistry


The PluginRegistry maintains a registry of plugins and has the following methods:


getPlugin()


The getPlugin method returns a reference to the plugin class (not instance).

  WEF.PluginRegistry.getPlugin('my.plugin'));

registerInstance()


This is used to register an instance of a plugin.

  WEF.PluginRegistry.registerInstance(config.id, pluginInstance));
 

getInstance()


getInstance returns a instance of the specified plugin

  WEF.PluginRegistry.getInstance(pluginId)

Base


The WEF.Base is the core class that all wef components are based on. It provides init() and destroy() methods and also i18n functionality via setMessages() and getMessage(). 


Plugin


The Plugin component builds on Base and every plugin is expected to be one that extends Plugin or one of its subclasses. It provides activate() and deactivate() methods allowing plugins to be custom logic to be added when a plugin is (de)activated. In addition, it also adds a render() method, which is used to add elements to the page. Each method also fires before and after events that can be used to notify others that the plugin is being activated/deactivated/rendered.


Widget


A widget is a UI element that can be interacted with. A dialog is a good example of a widget. In addition to methods supplied by Plugin and Base, widgets adds show() and hide() methods. The show() and hide() methods add/remove the css class names of wef-show and wef-hide which show and hide the plugin's main element respectively.


App


An App is just a plugin and add no additional methods of its own.


Core WEF Widgets


The Core WEF widgets are components that the WEF use and plugin authors can either use directly or indirectly.


Ribbon


The Ribbon component is responsible for displaying the ribbon at the top of the page and also for managing and/or registered toolbars and plugins. It has the following methods available:


addToolbar()


This method adds a toolbar to the Ribbon. Normally this is the method used to add new tabs to the ribbon which in turn contain the toolbar(s).


addButtons()


This method adds the specified buttons to the specified toolbar.


getToolbar()


This returns a reference to the specified toolbar


Toolbar


The toolbar component is a simple component that manages a collection of buttons. It has the following methods available:


addButtons()


This method adds the specified buttons to the toolbar.


getButtonById()


This method returns a reference to the specified button.


Button Handling

A note of button handling. Any toolbar button when interacted with fires a namespaced event. That is, an event with a name that consists of the plugin id + button id + event type eg 'helloworldPlugin--hello-worldClick' would be an name for a click event for a button with an id of hello-world belonging to a plugin called helloworldPlugin. Any handler should listen to that event name in order to respond to it.


Tabbed Toolbar


The Tabbed Toolbar manages a collection of toolbars that are rendered in different tabs. This allows plugins to add their own functionality into either existing tabs or into their own tabs.


addToolbar()


This method adds a new tab with an Toolbar as the content.


addButtons()


This method adds the specified buttons to the specified toolbar.


getToolbar()


This returns a reference to the specified toolbar


Developing a plugin


This section describes how to add a sample plugin for the WebEditor. In most instances, this means that the plugin adds some functionality to the WebEditor ribbon which the user can then interact with. In this sample, a plugin adds a new tab to the ribbon and a button that when clicked shows an alert dialog displaying 'Hello World!'. In addition, it will also show how to change the help link (at the top of the ribbon) so it is context sensitive to the current plugin.


An Hello World plugin


A useful starting stub for your plugin is as follows:



(function()
{
   var Dom =YAHOO.util.Dom,
       Event = YAHOO.util.Event,
       KeyListener = YAHOO.util.KeyListener,
       Selector = YAHOO.util.Selector,
       Bubbling = YAHOO.Bubbling,
       Cookie = YAHOO.util.Cookie,
       WebEditor = YAHOO.org.springframework.extensions.webeditor;

   YAHOO.namespace('wef.pluginname');

   /**
    * Sample plugin constructor
    * @constructor
    * @class Sample
    * @namespace YAHOO.wef.pluginname
    * @extends WEF.App
    */
   YAHOO.wef.pluginname = function(config)
   {
      config.setUpCustomEvents = (['customMethod']).concat(config.setUpCustomEvents || []);
      YAHOO.wef.pluginname.superclass.constructor.apply(this, Array.prototype.slice.call(arguments));
   };

   YAHOO.extend(YAHOO.wef.pluginname, WEF.App,
   {
      //plugin implementation goes here
   });

})();

WEF.register('wef.pluginname', YAHOO.wef.pluginname, {version: '1.0.1', build: '1'});

The first name in the constructor is purely optional and highlights how we would add automatic firing of before and after events for a method called customMethod, by using the setUpCustomEvents property. This can be specified in the constructor, as here, or via the plugin config.

After that there are three methods that should be implemented. These are init(), render(), and onHelp(). Init() should contain any logic to initialise the plugin. For our example, which is a very simple one, we register an event handler for when a button is clicked. If the plugin was more complicated, it might be useful to use managed attributes. Managed attributes are attributes which fire events when the value of the attribute changes. If so, then the initAttributes method should be called.



      init: function SamplePlugin_init()
      {
         // call the superclass init function
         YAHOO.wef.plugin.sample.superclass.init.apply(this);
        
         // register click handler for the toolbar button
         Bubbling.on(this.config.name + WebEditor.SEPARATOR + 'hello-worldClick', this.helloWorldClick, this, true);

         // if using managed events
         //this.initAttributes(this.config)
         return this;
      },

Our sample plugin needs to add a toolbar and a button. We can do this in the render() method.



      render: function SamplePlugin_render()
      {
         // get the current context path
         var contextPath = WEF.get('contextPath');
        
         // add a toolbar just for this plugin
         var tb  = WebEditor.module.Ribbon.addToolbar('WEF-'+WebEditor.ui.Ribbon.PRIMARY_TOOLBAR+'-pluginSample',
         {
            id: 'WEF-'+WebEditor.ui.Ribbon.PRIMARY_TOOLBAR+'-pluginSample',
            name: 'WEF-'+WebEditor.ui.Ribbon.PRIMARY_TOOLBAR+'-pluginSample',
            label: '',
            title: this.getMessage('wef.plugin.sample.hello-world-tab'),
            content: ,
            active: true,
            pluginOwner:this
         }, WebEditor.ui.Toolbar);

         // add a button to the primary toolbar
         tb.addButtons(
         [
            {
               type: 'push',
               label: '',
               title: this.getMessage('wef.plugin.sample.hello-world-button'),
               value: this.config.namespace + WebEditor.SEPARATOR + 'hello-world',
               id: this.config.name + WebEditor.SEPARATOR + 'hello-world',
               icon: true
            }
         ]);
      },  

First, the addToolbar method adds a toolbar with an image for the tab and a title for a tooltip. The pluginOwner attribute is required so the framework can track which is the active plugin. (See #PluginActivation section). Next, a button is added to the newly created toolbar.

Finally, if the plugin has any help content somewhere then we add an onHelp handler which simply opens a new browser window to the help page.


   
   onHelp: function SamplePlugin_onHelp()
   {
      window.open('http://www.springsurf.org', 'samplehelp');
   }

If more complicated help logic is required, then this can be added here instead. For example, inline help within a dialog rather than an external link.


PluginActivation


If your plugin is more complicated than our example, it's possible that you need some custom logic that should be run everytime your plugin is activated or deactivated. A plugin is activated or deactivated everytime the active tab is changed (in the ribbon) by the user clicking on a non-active tab. Add your logic to the activate() and deactivate() method. It's also best practice to add a destroy() method which should cleanup after the plugin (unsubscribe event handlers etc). A plugin's destroy method is called when a page is unloaded (currently not implemented).


Developing a plugin for an existing plugin


The #Developing_a_plugin section described how to add a plugin to the ribbon. The section describes how to add an plugin to a plugin that has already been added.
In particular we'll change the hello world plugin so it's a plugin to the Alfresco WebEditor (AWE) but still renders it own toolbar.

First, we subscribe to the afterRender of the AWE plugin, see below:



      init: function SamplePlugin_init()
      {
         // call the superclass init function
         YAHOO.wef.plugin.sample.superclass.init.apply(this);
        
         // register click handler for the toolbar button
         Bubbling.on(this.config.name + WebEditor.SEPARATOR + 'hello-worldClick', this.helloWorldClick, this, true);
        
         // if using managed events
         //this.initAttributes(this.config)
        
         //render our plugin after the AWE is rendered
         Bubbling.on('awe'+WebEditor.SEPARATOR+'afterRender', this.render, this, true);
         return this;
      },

But as outlined in the #Bootstrap_process, every loaded plugin has its render() method called explicitly. This means that it's possible that our render method is called before the AWE plugin is rendered. So a small change is required to the render() method.



      render: function SamplePlugin_render()
      {
         if (arguments.length===0)
         {
            return;
         }
         // get the current context path
         var contextPath = WEF.get('contextPath');
        
         //add a new toolbar just for this plugin instead plugging into an existing one (as above).
         var tb  = WebEditor.module.Ribbon.addToolbar('WEF-'+WebEditor.ui.Ribbon.PRIMARY_TOOLBAR+'-pluginSample',
         {
            id: 'WEF-'+WebEditor.ui.Ribbon.PRIMARY_TOOLBAR+'-pluginSample',
            name: 'WEF-'+WebEditor.ui.Ribbon.PRIMARY_TOOLBAR+'-pluginSample',
            label: '',
            title: this.getMessage('wef.plugin.sample.hello-world-tab'),
            content: ,
            active: true,
            pluginOwner:this
         }, WebEditor.ui.Toolbar);

         // add a button to the primary toolbar
         tb.addButtons(
         [
            {
               type: 'push',
               label: '',
               title: this.getMessage('wef.plugin.sample.hello-world-button'),
               value: this.config.namespace + WebEditor.SEPARATOR + 'hello-world',
               id: this.config.name + WebEditor.SEPARATOR + 'hello-world',
               icon: true
            }
         ]);
      },  

We only want the render() method to be called in response to an event, so simple logic is added so nothing happens if there are no arguments passed in, since we know that an event is passed in but nothing is passed in when it is called directly by the framework.

Note: If the plugin doesn't warrant a toolbar of its own but instead needs to add buttons to an existing toolbar, then the plugin would first need to get a reference to that plugin's toolbar. Plugins can expose their toolbar via a managed attribute. The following shows this would be done in the render() method:



         //create toolbar
         .
         .
         .
         //set up toolbar as a managed attribute so it can be exposed to other plugins
         this.setAttributeConfig('toolbar',
         {
            value: tb
         });



Other plugins can then add buttons to that toolbar like so (where pluginId is the id of the plugin that owns the toolbar):



         var tb = WEF.PluginRegistry.getInstance({pluginId}).get('toolbar');
         tb.addButtons(...);

Events and Hooks


The web editor is an event driven framework. This means that in order for plugins to work, they need to listen to certain events and fire certain events. Every core method of a plugin fires a before and an after event for that method. For instance, a WEF.Base has an init and a destroy method. Each time the init method is called, for instance, a before event is fired, then the init method is executed and finally an after event is fired. (Note, the events are fired *automatically* for the core methods and any developer specified methods). So if you need to do something after a plugin is initialised, you can subscribe to the afterInit method of that plugin and when the init method has been executed, your event handler will then be run. Each component in the WEF tree has their own core methods and each of these also fire their own before and after events. WEF.Plugin has render(), activate() and deactivate() and WEF.Widget has show() and hide() methods. If you require before and after methods to be fired for one or more custom methods of your own plugin, then these can be specified in the plugin config. Add a property called setUpCustomEvents of Array type containing the names of the methods you wish to have before and after events fired for and it will be automatically set up. Note: the actual event names are namespaced.  This means that in order to subscribe to the events you have to know the name of the originating plugin (as specified in the plugin's config.name property). This is because the name of the event will be in this format config.name--eventName eg for a plugin with a name of simplePlugin the event name for its before init event will be 'samplePlugin--beforeInit'.


Bootstrap process


The section describes what happens when the framework bootstraps itself and the firing order of the events.

When WEF loads all specified resources, it fires a WEF--afterInit event. It does this after creating the Ribbon and running the main app. The main app subscribes a handler to the WEF-Ribbon--afterRender event and, when this event is fired, this handler creates an instance of every registered resource that is loaded and of a plugin type and calls its init method. When all plugins have been instantiated and initialised, only then are the plugins rendered. After the main app is run, WEF then calls its render method which ultimately fires a WEF--afterRender event. That event is subscribed to by WEF-Ribbon which renders its own elements and then fires off its own WEF-Ribbon-afterRender event. Remember, it is only now, after the ribbon has rendered, that the plugins and instantiated and initialised (as described above).

So, in summary  the actual order is

  WEF--afterInit event is fired
     WEF.render()
  WEF--afterRender event is fired
  Ribbon is subscribed to WEF--afterRender to run its render() method
     Ribbon.render()
  Ribbon fires WEF-Ribbon--afterRender event
  Any plugins which subscribe to WEF-Ribbon--afterRender are now instantiated and their init() method is called.
     plugin1()
     plugin1.init();
     plugin2()
     plugin2.init();
Finally each plugin has its render() method called
     plugin1.render()
     plugin2.render();

Perhaps it might be better described in an image:

500px


Configuring a Plugin


The developing a plugin section above covered how to implement a custom plugin, this section explains how the plugin is configured so it appears on the ribbon.

WEF automatically includes any Spring context files ending in '-context.xml' that reside in the 'org/springframework/extensions/webeditor/plugins' package. The context file used by the Hello World sample plugin is show in it's entirety below.



<beans>

   <bean id='wef.plugin.sample' parent='wefPluginBase' class='org.springframework.extensions.webeditor.WEFPluginImpl'>
      <property name='name' value='wef.plugin.sample' />
      <property name='description' value='Sample Plugin for Web Editor Framework' />
      <property name='path' value='/res/sample/plugin.js' />
      <property name='variableName' value='SamplePlugin' />
      <property name='dependencies'>
         <list>
            <ref bean='wefRibbon' />
            <ref bean='wef.plugin.sample.css' />
         </list>
      </property>
   </bean>
   <bean id='wef.plugin.sample.css' class='org.springframework.extensions.webeditor.WEFResourceImpl'>
      <property name='name' value='wef.plugin.sample.css' />
      <property name='description' value='Sample Plugin for Web Editor Framework' />
      <property name='path' value='/res/sample/plugin.css' />
      <property name='type' value='css' />
   </bean>

   <bean id='samplePluginResourceBundle' class='org.springframework.extensions.surf.util.ResourceBundleBootstrapComponent'>
      <property name='resourceBundles'>
         <list>
            <value>org.springframework.extensions.webeditor.plugins.sample-plugin</value>
         </list>
      </property>
   </bean>
           
</beans>

A plugin consists of one main JavaScript file and any number of dependent resources, these are all defined using standard Spring bean configuration. A plugin is represented by the 'org.springframework.extensions.webeditor.WEFPluginImp' class, whereas a resource is represented by the 'org.springframework.extensions.webeditor.WEFResourceImpl' class. A plugin is actually just a specialised resource, they therefore share several common properties, a unique name, description, path and dependencies.

The path property points to the location of the file, it should NOT contain the contextPath of the application, this will be added by the framework when appropriate. The leading '/res' in the path allows the files to be located within a JAR file.

The variableName property should be set to name of an object that is created by the referenced JavaScript. The YUILoader uses this to determine whether the file has been loaded.

Your plugin will more than likely have dependencies, at the very least it will depend on the ribbon component it's buttons will be added to. These are defined via the dependencies property, which is itself a list of other plugins or resources. The example above shows that the plugin is dependent on the 'wefRibbon' and 'wef.plugin.sample.css' resources. Resources can have other resources as dependencies.

Resources can either be CSS or JavaScript files, this is defined by the type property. The property accepts the values css and js.

Sometimes it's necessary to use IE specific CSS files to address IE's shortcomings, CSS files for this purpose can be flagged with the userAgent property as shown below. This instructs the framework to only load the resource if the browser being used is IE.



<property name='userAgent' value='IE' />

For all the built-in plugins and resources and their dependencies refer to the file named 'spring-webeditor-context.xml' in the 'spring-webeditor-1.0.0.CI-SNAPSHOT.jar' file, your plugins can depend on anything defined in this file, there is no need to redefine them.

The last part of the configuration shown above shows how to load a resource bundle for your plugin.

Attachments

Outcomes