NexJ Logo

Structure: AFL modules, portlets, and cards

The structural components as discussed in Structure: Portals contain workspaces contain portlets, are used to arrange portlets. For more information about how the application organizes portlets, see Application pattern.

AFL portlets (also known as raw AFL portlets) arrange and bind controls, and may be used in portlet references and dialogs.

Learning objectives

  • Understand the internal structure of AFL portlets.
  • Create and use an AFL portlet.

Key concepts

Application Foundation Layers (AFL)  - a JavaScript framework for building enterprise applications, mainly used for UI development. See the AFL developer reference to learn more about the capabilities of the framework. AFL provides portal lifecycle management, data binding, context bus management, control development, internationalization and more.

AFL portlets are modules that return views that are written using the AFL core libraries.

Views

Most of what we work with in the UI are considered views, for example, portlets, buttons, charts, tables, and so on.

Portlet control - a container for other controls, created with `ui("Portlet", {children: [...]}).

  • The Card control is often used as the child of the portlet to provide a caption, and layout information and further grouping of child controls.
  • Alternatively, the portlet control may directly contain a list control or big button control for example. It depends on the design requirements.
  • The portlet interacts with the portal server to provide capabilities like the context bus. For example, currentEntity = myPortlet.context("EntityId");.

Again, with AFL portlets we are simply dealing with JavaScript, HTML markup, and CSS so have a great deal of flexibility in what we can do.

When creating a new portlet, we follow these common steps:

  1. Create a file called mod/app/<myportlet>.js
  2. In that file, define a portlet as follows:

    /* myPortlet */
    define("app/myportlet", ["nexj/sys", "nexj/ui", "nexj/i18n", "nexj/cvt", "nexj/ui/dom", "nexj/dom", "nexj/ui/obj", "nexj/ui/core"],
        function(sys, ui, i18n, cvt, dom, obj) {
    		...
    		return function() {
    			return ui("Portlet", {children: [ui("Card", {caption: "My Caption", children: [...})]});
    		}
    	}
    )
    

    where

    • app/myportlet is the name and matches the filename

    • ["nexj/sys", ... "nexj/ui/core"] are the dependencies. These modules listed in the dependencies are either providing a new object with an API, such as sys or as a plugin extending the capabilities of another library, such as nexj/ui/core extending the ui object from nexj/ui with more views. A list of modules can be found in the AFL Modules list.

    • function(sys, ui, i18n, cvt, dom, obj) { are the imports of the dependencies.

    • return function() {return ui("Portlet", {children: [...]}); is the definition of your portlet with the arrangement and behavior of all of it's child controls.
  3. Add the portlet to a workspace.

In preparation for the learning activities in this lesson, make sure that your files from the Structure: Portals contain workspaces contain portlets lesson contain the following source code. If not, copy the code into your files before continuing.

training:Contact.portal
<Portal caption="idsa.training.portal.Contact.caption">
   <Tool name="toolNotifications" caption="ids.notifications" icon="icon:notifications" event="setSidebar" parameter="NOTIFICATIONS"/>   
   <WorkspaceRef name="refSandbox" workspace="training:Sandbox"/>
   <WorkspaceRef name="refHome" workspace="mda:Home"/>
   <WorkspaceRef name="refContacts" workspace="mda:Contacts"/>
   <Drawer event="setSidebar" name="refNotifications" portlet="mda:NotificationDrawer"/>
</Portal>
training:Sandbox.workspace
<Workspace caption="idsa.training.workspace.Sandbox.caption" icon="icon:attach_money">
   <Page name="page">
      <Tab layout="cols:3 fluid:true" name="tabDetail" type="grid">
         <PortletRef name="mda_EntityNavigator_portlet" portlet="mda:EntityNavigator"/>
      </Tab>
   </Page>
</Workspace>
idsa.training.en.strings
idsa.training.portal.Contact.caption=My Training Application
idsa.training.workspace.Sandbox.caption=Sandbox

Creating your portlet

Let's create and enhance a simple portlet.

Learning activity

  1. Go to the Java perspective in NexJ Studio.
  2. Under training/mod/app, create a new file called myportlet.js.



  3. Open the file with a text editor, and paste the following JavaScript:

    training/mod/app/myportlet.js
    /** Example AFL Portlet */
    define("app/myportlet", ["nexj/sys", "nexj/ui"],
        function(sys, ui) {"use strict";
        return function() {
            return ui("Portlet", {children: [
            	ui("Card", {caption: "AFL Card", layout: {cols: 4}, children: [
    				ui("Label", {caption: new ui.Value("Label"), layout: {span: 4}}),
    	            ui("Text", {value: new ui.Value(), caption: "First Name", layout: {span: 2}}),
    	            ui("Text", {value: new ui.Value(), caption: "Last Name", layout: {span: 2}}),
    				ui("Button", {caption: "Display It", layout: {span: 2}}),
                ]})
            ]});
        }}
    )
  4. Save your work.

  5. Navigate to http://localhost:7080/training/ui/myportlet to see the portlet render. It should look similar to the following:



  6. In your Sandbox.workspace, change the contents to the following:

    training:Sandbox.workspace
    <Workspace caption="idsa.training.workspace.Sandbox.caption" icon="icon:attach_money">
       <Page name="page">
          <Tab layout="cols:3 fluid:true" name="tabDetail" type="grid">
             <PortletRef name="mda_EntityNavigator_portlet" portlet="mda:EntityNavigator"/>
             <PortletRef name="app_myportlet" portlet="app/myportlet"/>
          </Tab>
       </Page>
    </Workspace>
  7. Because you've made changes to the workspace, save your work, and re-seed.
    When you refresh your browser, your UI should look similar to the following:

Let's discuss what you just did.

The portlet

The portlet definition is an AFL module. You added a define statement with three parameters:

  • Module name (must be the same as the file name) - `define("app/myportlet"`
  • Dependencies - `, ["nexj/sys", "nexj/ui"],`. These dependencies are either modules that will be passed to the third parameter as function parameters, or mixins that enhance the behavior of a module. In this example, we could have mixed in nexj/ui/obj which is a module that enhances nexj/ui with business model object binding capabilities.
  • A function that takes the dependencies as parameters - `function(sys, ui) {"use strict";`.  For portlets, this function returns a portlet UI control.

The return function uses the ui library to create a portlet control with a child Card control, with a further child Text control. To create a UI control in AFL, use ui(<ControlName>, {<properties>}). Each control has it's own set of properties. Some of the more common ones are name, caption, children, and items.

The various controls have a property sets that dictate the behavior of the control. Controls often have a name property, if they will be referred to in script. The layout columns or offset properties (in combination with the layout property of the containing Card control for the number of columns), may be used to arrange controls into columns or limit their length.

ui("Label", {caption: new ui.Value("Label"), layout: {span: 4}}),
ui("Text", {value: new ui.Value(), caption: "First Name", layout: {span: 2}}),
ui("Text", {value: new ui.Value(), caption: "Last Name", layout: {span: 2}}),
ui("Button", {caption: "Display It", layout: {span: 2}}),

You created the portlet in the mod/app folder. For this to work, your project should have the Java nature added to it and a classpath entry for your mod folder.

Adding the portlet to a workspace

To use the portlet in a workspace (or anywhere else that uses portletRefs), set the PortletRef's portlet property to  app/<portletName>.

<PortletRef name="app_myportlet" portlet="app/myportlet"/>

Controls

For a look at some of the AFL Controls that are available to include in your portlet, navigate to http://localhost:7080/training/ui/demo and browse around. The code for these portlets can be found in your mod/app folder. The text example is in mod/app/text.js.

Events and showing a dialog

To show a dialog, we will simply add an onClick event to our button. Just to show that this is simply JavaScript, let's start with an alert. Replace the definition of your button control with the following code:

ui("Button", {caption: "Display It", layout: {span: 2}, onClick: function () {alert("The button was pressed."); console.log("Yes it was.");}}),

This will raise an alert, and print a message to the console if you have the developer tools open.

Hint

If you want to see what's going on, you can always look at the source that's been downloaded to your browser (usually with F12) and set breakpoints to debug.


Now let's raise a Flash message. Change the definition for your button control to the following code:

ui("Button", {caption: "Display It", layout: {span: 2}, onClick: function () {ui.Flash.open("The button was pressed at " + now())}}),

When you refresh and click the Display It button, you should see a flash message at the bottom of the screen.

To show a simple message dialog, replace the button definition with the following code:

ui("Button", {caption: "Display It", layout: {span: 2}, onClick: function () {ui.Dialog.info("The button was pressed.", "Dialog Title");}}),

And for a more complete dialog, try something like the following:

ui("Button", {
	caption: "Display It", 
	layout: {span: 2}, 
	onClick: function () {
		ui("Dialog", {
			caption: "The Dialog Caption", children: [ui("Text", {caption: "First Name"}), ui("Text", {caption: "Last Name"})]
		}).open();
}}),

And finally, let's make the dialog appear full screen and add a Card so the controls have something to sit on. The entire file should now look like the following.

training/mod/app/myportlet.js
/** Example AFL Portlet */
define("app/myportlet", ["nexj/sys", "nexj/ui"],
    function(sys, ui) {"use strict";
    return function() {
        return ui("Portlet", {children: [
        	ui("Card", {caption: "AFL Card", layout: {cols: 4}, children: [
				ui("Label", {caption: new ui.Value("Label"), layout: {span: 3}}),
	            ui("Text", {value: new ui.Value(), caption: "First Name", layout: {span: 2}}),
	            ui("Text", {value: new ui.Value(), caption: "Last Name", layout: {span: 2}}),
				ui("Button", {
					caption: "Display It", 
					layout: {span: 2}, 
					onClick: function () {
						ui("Dialog", {
							caption: "The Dialog Caption", 
							size: "x", 
							children: [
								ui("Card", {children: [
									ui("Text", {caption: "First Name"}), 
									ui("Text", {caption: "Last Name"})
								]})
							]
						}).open();
				}}),
            ]})
        ]});
    }}
)

Summary

You should now understand the basic structural elements of AFL portlets and dialogs.