Customizing Your Site¶
When working with eXo Platform, it is important not to modify the source code. This will ensure compatibility with future upgrades, and support will be simplified. To customize your site, you need to create an extension project by providing your own artifacts as a set of wars/jars/ears.
This chapter will show you how to customize your site using the extension mechanism provided by eXo Platform.
Defining structure of a site Required knowledge of a site’s structure, including
portal.xml
,pages.xml
andnavigation.xml
.Creating a new site Steps to create a new site, how to define a site as default and to create custom templates for it.
Managing features of a new site How to manage various features of a new site, including enabling/disabling the drive creation, adding JavaScript, localizing with resources, adding/removing a language, declaring a servlet/filter, and creating a custom look and feel. All these features are managed through your extension project.
Managing eXo Platform look and feel All aspects and detailed steps to create a custom look and feel for a site.
Upload Component How to confugure the upload service.
Defining structure of a site¶
The structure of a site is mainly defined into 3 files: portal.xml
,
pages.xml
and navigation.xml
. You can create multiple pages
within a single site.
Understanding the “Intranet” site of eXo Platform is a good approach to the
site structure. The configuration of the “Intranet” site can be found in
the $PLATFORM_TOMCAT_HOME/webapps/acme-intranet.war
directory.
portal.xml
The portal.xml
file describes the layout and portlets that will be
shown on all pages:
Properties which define site name, default locale, access and edit permissions, and some basic configurations (session alive, show portlet information) of the “Intranet” site.
<portal-name>intranet</portal-name> <locale>en</locale> <access-permissions>*:/platform/users</access-permissions> <edit-permission>*:/platform/administrators</edit-permission> <properties> <entry key="sessionAlive">onDemand</entry> <entry key="showPortletInfo">0</entry> </properties>
Layout which defines layout container, banner, footer, menu and breadcrumbs portlets displayed in all pages. Also,
<page-body>
is a flag which specifies the position of pages’ portlets.<portal-layout> <move-apps-permissions>*:/platform/administrators</move-apps-permissions> <move-containers-permissions>*:/platform/administrators</move-containers-permissions> <container id="NavigationBody" template="system:/groovy/portal/webui/container/UITableColumnContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <container id="LeftNavigation" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>*:/platform/users</access-permissions> <container id="LeftBreadCrumbNavigationPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>*:/platform/users</access-permissions> <portlet-application> <portlet> <application-ref>platformNavigation</application-ref> <portlet-ref>UIBreadCrumbsNavigationPortlet</portlet-ref> </portlet> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> </portlet-application> </container> <container id="LeftNavigationPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>*:/platform/users</access-permissions> <portlet-application> <portlet> <application-ref>platformNavigation</application-ref> <portlet-ref>UICompanyNavigationPortlet</portlet-ref> </portlet> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> </portlet-application> </container> <container id="GroupsNavigationPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>*:/platform/users</access-permissions> <portlet-application> <portlet> <application-ref>platformNavigation</application-ref> <portlet-ref>UIGroupsNavigationPortlet</portlet-ref> </portlet> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> </portlet-application> </container> <container id="SpaceNavigationPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>*:/platform/users</access-permissions> <portlet-application> <portlet> <application-ref>platformNavigation</application-ref> <portlet-ref>UISpaceNavigationPortlet</portlet-ref> </portlet> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> </portlet-application> </container> </container> <container id="RightBody" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <page-body> </page-body> </container> </container> </portal-layout>
Note
The move-apps-permissions
and move-containers-permissions
tags define which users have permissions to move applications and
containers on this layout. If these permissions are not set
explicitly, they would default to Everyone.
navigation.xml
The navigation.xml
file defines all navigation nodes of a site. The
syntax is simply using the nested node tags. Each node refers to a page
defined in the pages.xml
file that will be explained later.
Node that defines URI, name, label and page reference of the “Intranet” homepage.
<node> <uri>home</uri> <name>home</name> <label>#{portal.intranet.home}</label> <page-reference>portal::intranet::homepage</page-reference> </node>
A node navigation might contain sub-nodes.
<node> <uri>connections</uri> <name>connections</name> <label>#{portal.intranet.connections}</label> <visibility>SYSTEM</visibility> <node> <uri>connections/all-people</uri> <name>all-people</name> <label>#{portal.intranet.yours}</label> <page-reference>portal::intranet::all-people</page-reference> </node> <node> <uri>connections/network</uri> <name>network</name> <label>#{portal.intranet.yours}</label> <page-reference>portal::intranet::network</page-reference> </node> ... </node>
Note
For the top nodes, the URI and the navigation node name must have the same value.
For sub-nodes, the URI is composed like <uri>connexions/all-people</uri> where ‘connections’ is the name of the parent node, and ‘all-people’ is the name of node (<name>all-people</name>).
When you configure the
navigation.xml
file, sometimes you need to set the node visibility. To configure the node visibility, simply put <visibility>type_of_visibility</visibility> as a child of the <node> tag.eXo Platform supports 4 types of node visibility, including:
DISPLAYED: The node will be displayed.
HIDDEN: The node is not visible in the navigation but can be accessed directly with its URL.
SYSTEM: Same as HIDDEN node, except that this node is not deletable by UI.
TEMPORAL: The node is displayed in related time range. When the visibility of TEMPORAL node is configured, the start and end dates can be specified by using <startpublicationdate> and <endpublicationdate>.
pages.xml
The pages.xml
defines the layout and portlets of specific pages. A
page defined in pages.xml
will declare name, title, access and edit
permissions, move applications and containers (optional). Also, the
<portlet-application>
is used to declare portlets in a page.
<page>
<name>activities</name>
<title>Activities</title>
<access-permissions>*:/platform/users</access-permissions>
<edit-permission>manager:/platform/administrators</edit-permission>
<move-apps-permissions>manager:/platform/administrators</move-apps-permissions>
<move-containers-permissions>manager:/platform/administrators</move-containers-permissions>
<container id="UIUserNavigationPortlet" template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<access-permissions>*:/platform/users</access-permissions>
<move-apps-permissions>manager:/platform/administrators</move-apps-permissions>
<move-containers-permissions>manager:/platform/administrators</move-containers-permissions>
<portlet-application>
<portlet>
<application-ref>platformNavigation</application-ref>
<portlet-ref>UIUserNavigationPortlet</portlet-ref>
</portlet>
<title>User Navigation Portlet</title>
<access-permissions>*:/platform/users</access-permissions>
<show-info-bar>false</show-info-bar>
<show-application-state>true</show-application-state>
</portlet-application>
</container>
<container id="ProfileActivity" template="system:/groovy/portal/webui/container/UIContainer.gtmpl">
<access-permissions>*:/platform/users</access-permissions>
<move-apps-permissions>manager:/platform/administrators</move-apps-permissions>
<move-containers-permissions>manager:/platform/administrators</move-containers-permissions>
<portlet-application>
<portlet>
<application-ref>social-portlet</application-ref>
<portlet-ref>UserActivityStreamPortlet</portlet-ref>
</portlet>
<title>User Activity Stream</title>
<access-permissions>*:/platform/users</access-permissions>
<show-info-bar>false</show-info-bar>
<show-application-state>true</show-application-state>
</portlet-application>
</container>
</page>
A group site has the same structure as portal sites. You can define :
- group.xml
: defines group site layout
- pages.xml
: defines pages layouts
- navigation.xml
: defines pages navigation structure
Creating a new site¶
In this tutorial you create your own site using a custom-extension.
The custom extension requires a jar and a war, so make sure you follow the link above to create and deploy the jar. Below are steps to create the war. Notice the webapp name is site-extension to configure it properly in the jar.
Create the webapp
site-extension.war
:The
portal
folder name indicates that the site is of portal type.The
site1
folder name will attend in the URL of the created site. If you change it here, you need to change it everywhere in the later configuration.
Edit
web.xml
:<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" metadata-complete="true" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> <display-name>site-extension</display-name> <listener> <listener-class>org.exoplatform.container.web.PortalContainerConfigOwner</listener-class> </listener> </web-app>
Edit
configuration.xml
to import site definition configuration:<?xml version="1.0" encoding="ISO-8859-1"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <import>war:/conf/sites-definition.xml</import> </configuration>
Edit
sites-definition.xml
to declare your site(s) to the portal:<?xml version="1.0" encoding="ISO-8859-1"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <external-component-plugins> <target-component>org.exoplatform.portal.config.UserPortalConfigService</target-component> <component-plugin> <name>new.portal.config.user.listener</name> <set-method>initListener</set-method> <type>org.exoplatform.portal.config.NewPortalConfigListener</type> <description></description> <init-params> <value-param> <name>override</name> <description></description> <value>true</value> </value-param> <object-param> <name>portal.configuration</name> <description></description> <object type="org.exoplatform.portal.config.NewPortalConfig"> <field name="predefinedOwner"> <collection type="java.util.HashSet"> <!-- You can declare many sites here --> <value><string>site1</string></value> <!--<value><string>site2</string></value>--> </collection> </field> <field name="ownerType"> <string>portal</string> </field> <field name="templateLocation"> <string>war:/conf/sites</string> </field> <field name="importMode"> <string>merge</string> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
Edit
site1/portal.xml
:<portal-config> <portal-name>site1</portal-name> <locale>en</locale> <access-permissions>*:/platform/users</access-permissions> <edit-permission>*:/platform/administrators</edit-permission> <properties> <entry key="sessionAlive">never</entry> <entry key="showPortletInfo">1</entry> </properties> <portal-layout> <move-apps-permissions>manager:/platform/administrators</move-apps-permissions> <move-containers-permissions>manager:/platform/administrators</move-containers-permissions> <page-body> </page-body> </portal-layout> </portal-config> - ``<locale>``: Defines the default language of your site. - ``<access-permissions>``: Specifies which membership(s) can access your site. Use comma to separate values. - ``<edit-permission>``: Specifies which membership can edit your site. Single value only. - ``<move-apps-permissions>``: Specifies which membership(s) can move applications on your site. Use comma to separate values. - ``<move-containers-permissions>``: Specifies which membership(s) can move containers on your site. Use comma to separate values. - ``<properties>``: See `Keep session alive <#KeepSessionAlive>`__ and `Show info bar by default <#ShowInfoBarByDefault>`__ for details. - ``<portal-layout>``: This is the simplest layout that contains only the <page-body>. You will write more complete layout later.
Edit
site1/navigation.xml
. In this example, only the homepage is defined:<node-navigation xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_objects_1_2 http://www.gatein.org/xml/ns/gatein_objects_1_2" xmlns="http://www.gatein.org/xml/ns/gatein_objects_1_2"> <priority>1</priority> <page-nodes> <node> <uri>home</uri> <name>home</name> <label>Home Page</label> <page-reference>portal::site1::homepage</page-reference> </node> </page-nodes> </node-navigation> - ``<page-reference>``: Assigns the node to a page. The format is *{site-type}::{site-name}::{page-name}*.
Edit
site1/pages.xml
. In this example, only one page (homepage) is created. The page contains GettingStartedPortlet that is a built-in portlet.<page-set xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_objects_1_2 http://www.gatein.org/xml/ns/gatein_objects_1_2" xmlns="http://www.gatein.org/xml/ns/gatein_objects_1_2"> <page> <name>homepage</name> <title>Home Page</title> <access-permissions>*:/platform/users</access-permissions> <edit-permission>*:/platform/administrators</edit-permission> <move-apps-permissions>manager:/platform/administrators</move-apps-permissions> <move-containers-permissions>manager:/platform/administrators</move-containers-permissions> <portlet-application> <portlet> <application-ref>homepage-portlets</application-ref> <portlet-ref>GettingStartedPortlet</portlet-ref> </portlet> <title>Getting Started</title> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> <show-application-state>false</show-application-state> <show-application-mode>false</show-application-mode> </portlet-application> </page> </page-set>
Deploy your extension, then test your site at http://mycompany.com:8080/portal/site1/.
Note
Note that the override
value-param should be set to true. This
will be explained in the next section.
You can as well, add a group site definition in the same extension:
Edit
sites-definition.xml
to declare your site(s) to the portal:<?xml version="1.0" encoding="ISO-8859-1"?> <configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <external-component-plugins> <target-component>org.exoplatform.portal.config.UserPortalConfigService</target-component> <component-plugin> <name>new.portal.config.user.listener</name> <set-method>initListener</set-method> <type>org.exoplatform.portal.config.NewPortalConfigListener</type> <description></description> <init-params> <value-param> <name>override</name> <description></description> <value>true</value> </value-param> <object-param> <name>group.configuration</name> <description></description> <object type="org.exoplatform.portal.config.NewPortalConfig"> <field name="predefinedOwner"> <collection type="java.util.HashSet"> <!-- You can declare many group sites here (example of group site name: /platform/administrators) --> <value><string>/GROUP_ID</string></value> </collection> </field> <field name="ownerType"> <string>group</string> </field> <field name="templateLocation"> <string>war:/conf/sites</string> </field> <field name="importMode"> <string>merge</string> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
Edit
WEB-INF/conf/sites/GROUP_ID/group.xml
:<portal-config> <portal-name>GROUP_ID</portal-name> <locale>en</locale> <access-permissions>*:GROUP_ID</access-permissions> <edit-permission>*:/platform/administrators</edit-permission> <properties> <entry key="sessionAlive">never</entry> <entry key="showPortletInfo">1</entry> </properties> <portal-layout> <page-body> </page-body> </portal-layout> </portal-config>
This file will define an empty layout for this group site. Instead of doing this, you can avoid adding this file and let Dynamic layout used instead for Group site.
In fact, a group which is using a dynamic site layout
will
inherit the layout defined in Portal Site. This will lead to have
a unique structure of portal definition centralized in portal.xml
of parent site to make UI coherent when navigating from a portal to a space
or a group page.
Add
navigation.xml
andpages.xml
insideWEB-INF/conf/sites/GROUP_ID/
as defined above.Restart the server and access the group pages.
Redeploying your site extension¶
The sites are created during portal initialization, that is, when the
server starts for the first time. So if you deploy your extension after
that, your extension should trigger a re-initialization process, by
setting the override
value-param to true as said before:
<value-param>
<name>override</name>
<description></description>
<value>true</value>
</value-param>
If this parameter is omitted, it is defaulted to false.
After your site is created, its preferences persists in database. When
you modify your extension and redeploy it, you need to set
importMode
to either merge or overwrite to make your extension
update/override the persistent data; otherwise, the modification will
not take effect.
The valid
options
of importMode
:
importMode |
Description |
---|---|
conserve |
Imports data when it does not exist, otherwise do nothing. |
insert |
Imports data when it does not exist, otherwise performs a strategy that adds new data only. |
merge |
Imports data when it does not exist, and updates data when it exists. |
overwrite |
Overwrites whatever data. |
Setting your site as default site¶
When entering this URL in your browser: http://mycompany.com:8080/portal, you will be directed to a default site.
To configure your site as default, add the following content to
sites-definition.xml
, inside <init-params>:
<value-param>
<name>default.portal</name>
<description></description>
<value>mysite</value>
</value-param>
Note
Note that the <value-param> tags should be added right after the <init-params> opening tag.
Enabling/Disabling a drive creation¶
During the site creation, a drive with the same name as the site is also automatically created. However, you can decide if such a drive is automatically created or not by using two parameters named autoCreatedDrive, and targetDrives in the external component plugin named CreateLivePortalEventListener.
<external-component-plugins>
<target-component>org.exoplatform.services.listener.ListenerService</target-component>
<component-plugin>
<name>org.exoplatform.portal.config.DataStorage.portalConfigCreated</name>
<set-method>addListener</set-method>
<type>org.exoplatform.services.wcm.portal.listener.CreateLivePortalEventListener</type>
<description>this listener creates a new live portal content storage.</description>
<init-params>
<value-param>
<name>autoCreatedDrive</name>
<description>A drive will be automatically created during the portal creation.</description>
<value>false</value>
</value-param>
<values-param>
<name>targetDrives</name>
<description>The list of drives which are automatically created during the portal creation with
"autoCreatedDrive=false".
</description>
<value>acme</value>
</values-param>
</init-params>
</component-plugin>
</external-component-plugins>
If autoCreatedDrive=true, a drive will be automatically created during the portal creation regardless of targetDrives. In case autoCreatedDrive is not specified, then its default value is true.
If autoCreatedDrive=false, only drives listed in targetDrives are created. In case targetDrives is not specified, no drives are created.
Creating page container template¶
Page container templates are Groovy ones that are used for (but not limited to) page decoration. You can re-use the built-in templates or write your own templates and package them in the site-extension.
By declaring a template in pages.xml, you decorate the individual pages. By declaring a template in portal.xml, you make the site layout that impacts all the pages of the site.
You can learn some templates in
portal.war!/groovy/portal/webui/container/
. In this tutorial the
templates are not much more than this code:
<%uicomponent.renderChildren();%>
Add the templates to your site extension so that it contains:
Edit
portal.xml
to make the site layout:<portal-layout> <container id="MySiteLayoutContainer" template="war:/groovy/container/MySiteLayoutContainer.gtmpl"> <page-body> </page-body> </container> </portal-layout>
Edit
pages.xml
to place the Getting Started portlet into a container:<page> ... <container id="MyPageContainer" template="war:/groovy/container/MyPageContainer.gtmpl"> <move-apps-permissions>*:/platform/administrators</move-apps-permissions> <move-containers-permissions>*:/platform/administrators</move-containers-permissions> <portlet-application> <portlet> <application-ref>homepage-portlets</application-ref> <portlet-ref>GettingStartedPortlet</portlet-ref> </portlet> <title>Getting Started</title> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> <show-application-state>false</show-application-state> <show-application-mode>false</show-application-mode> </portlet-application> </container> ... </page>
Edit
MySiteLayoutContainer.gtmpl
:<div style="background-color: navy; font-size: 18px; padding: 15px; color: white"> <span>ACME Intranet</span> </div> <div> <%uicomponent.renderChildren();%> </div> <div style="text-align: center; background-color: wheat; border-bottom-left-radius: 7px; border-bottom-right-radius: 7px"> <span style="font-style: italic">Powered by eXo Platform</span> </div>
Edit
MyPageContainer.gtmpl
:<div style="border: 3px solid deepskyblue"> <%uicomponent.renderChildren();%> </div>
After redeploying the site extension, test the decoration of the page (navy and wheat blocks for site layout, and the blue border for the page container):
Managing features of a new site¶
After creating a new site, you need to manage some common features, including:
Adding JavaScript to your site How to add JavaScript to your site, details about JavaScript modules and specific usecases.
Localizing with resources How to localize with resources in your custom extension.
Adding/Removing a language How to add or remove a language through the locale configuration file.
Declaring servlet/filter How to declare the servlet/filter extension to get the current portal.
Creating a page layout template How to create a page layout template that is used in Page Creation Wizard.
Adding JavaScript to your site¶
eXo Platform comes with some very powerful JavaScript management capabilities. You can easily control how your own JavaScript files are included in your site’s pages and manage their dependencies.
Most of these capabilities can be done with some declarations in the
gatein-resources.xml
file of your extension.
Adding JavaScript to all pages of a site
This usecase is used when you want to add JavaScript to all pages of a site named “my-site”:
<portal>
<name>my-site</name>
<module>
<script>
<path>/bar.js</path>
</script>
</module>
</portal>
Adding JavaScript to a page when its portlet is displayed
This usecase is used when you want to add JavaScript to a portlet named “my-portlet”.
<portlet>
<name>my-portlet</name>
<module>
<script>
<path>/bar.js</path>
</script>
</module>
</portlet>
JavaScript modules
The eXo Platform JavaScript improvements are built on top of the notion of JavaScript module. JavaScript does not provide a natural way for namespacing and the notion of module was designed to solve this problem. Namespacing can be perceived as a natural lack, however this lack should be seen as an advantage as modules provide more and more namespacing. Indeed, the module pattern allows creating and resolving dependencies between modules at runtime on demand and loading JavaScript resources in parallel.
The notion of module can be viewed as:
An identifier or name.
A list of dependencies on the modules required to work properly.
The code packaged is usually expressed as a self-executing function. The product, which is an object produced by the module, is usually consumed by other modules.
At runtime, the dependency system defines a graph of function to execute that makes the product of each module be injected in the other modules. It can be seen as a simple dependency injection system which can load modules in an asynchronous and parallel fashion providing parallel loading, namespacing and dependency management.
Note
See JavaScript Development for further information about JavaScript.
When adding JavaScript to your site, you need to consider the following specific usecases:
Declaring an eXo Platform module
Declaring an AMD module
Using eXo Platform jQuery module
Using a custom jQuery version
Configuring jQuery plugins
Exposing version of jQuery globally
Implementing a global jQuery plugin
Using CommonJS modules
Using Mustache.js module
Using Text.js module
Overriding the dependency of a native AMD module
Accessing a module from a script
Disabling minification
Declaring an eXo Platform module¶
This part takes the
Highlight.js library
as an example to show you how to declare an eXo Platform module. This library
is actually a jQuery
plugin which follows the self-invoking pattern
that consumes the jquery
dependency as $. Here is an overview of the
Highlight.js
source:
(function($) {
...
}(jQuery)
Assume that you have added it to the javascript
folder in your
extension, and now declare this module using the XML declaration in
/WEB-INF/gatein-resources.xml
as follows:
<module>
<name>highlight</name>
<script>
<path>/javascript/highlight/highlight.js</path>
</script>
<depends>
<module>jquery</module>
<as>jQuery</as>
</depends>
</module>
The module is named highlight
and uses the
/javascript/highlight/highlight.js
source code bundled in the
war
file.
The depends
tag creates a dependency on the jquery
module. The
dependency is aliased as jQuery using the as
tag to match the $
argument of the Highlight.js
self-executing function. Refer
here
to check which jQuery versions are provided in eXo Platform.
Declaring an AMD module¶
eXo Platform is capable of integrating native AMD (Asynchronous Module Definition) modules, and eXo Platform modules are currently translated into AMD modules. To further understand the AMD declaration, see the RequireJS documentation.
AMD modules follow the pattern as below:
define("module", ["dependency1",...,"dependencyN"],
function(dep1,...,depN) {
});
eXo Platform can use such a module out of the box, however some parts will be
overridden by the declaration in gatein-resources.xml
:
The “
module
” name will be ignored and replaced with the declared module name.The module dependencies from “
dependency1
” to “dependencyN
” have to be declared with the same name ingatein-resources.xml
.Assuming that the dependencies from
dependency1
todependencyN
have been declared in XML, such module definition can be declared with the following XML:<module> <name>MyModule</name> ... <depends> <module>dependency1</module> </depends> ... <depends> <module>dependencyN</module> </depends> </module>
Using eXo Platform jQuery module¶
eXo Platform provides the jQuery library 3.2.1 as a jquery module, the
configuration of this module can be found in the eXoResources.war
file. To reuse this jQuery version, just declare a dependency over it:
<portlet>
<name>RequireJSPortlet</name>
<module>
<depends>
<module>jquery</module>
</depends>
</portlet>
The default jquery
module alias is $, so if you are using it, it
should be named $ in the self-executing function:
If your library uses a different name, such as jQuery, the XML as
tag should be used:
<portlet>
<name>RequireJSPortlet</name>
<module>
<depends>
<module>jquery</module>
<as>jQuery</as>
</depends>
</portlet>
With the following self-executing function:
(function($) {
...
}(jQuery)
Using a custom jQuery version¶
If you are not satisfied with the jQuery version provided by eXo Platform, you can integrate your desired version. It is common that products built over eXo Platform depend on the third party JavaScript frameworks depending on other versions of jQuery libraries, so deploying other jQuery libraries is unavoidable at some points. Multiple jQuery instances within a web page conflict with global variables, however the module system allows you to use such a library with no hassles.
The following example is about a jQueryPortlet using jQuery version 1.6.4, which is configured properly:
<module>
<name>jquery-1.6.4</name>
<script>
<adapter>
(function() {
<include>/javascript/jquery-1.6.4.js</include>
return jQuery.noConflict(true);
})();
</adapter>
</script>
</module>
<portlet>
<name>jQueryPortlet</name>
<module>
<script>
<path>/javascript/MyJSFile.js</path>
...
</script>
<depends>
<module>jquery-1.6.4</module>
<as>$</as>
</depends>
</module>
</portlet>
Note
Return to the beginning part of the Adding JavaScript to your site section to learn about use of JavaScript in eXo Platform.
Configuring jQuery plugins¶
This section shows you how to configure a jQuery plugin and how to use
it in the jQueryPluginPortlet
portlet.
Use the jQuery plugin as a minimal one:
(function($) { $.fn.doesPluginWork = function() { alert('YES, it works!'); }; })(jQuery);
Declare it as a module:
<module> <name>jquery-plugin</name> <as>jqPlugin</as> <script> <path>/jqueryPlugin/jquery-plugin.js</path> </script> <depends> <module>jquery</module> <as>jQuery</as> </depends> </module>
Use this plugin in your portlet:
<portlet> <name>jQueryPluginPortlet</name> <module> <script> <path>/jqueryPlugin/jqueryPluginPortlet.js</path> </script> <depends> <module>jquery</module> <as>$</as> </depends> <depends> <module>jquery-plugin</module> </depends> </module> </portlet>
Note
Your portlet module should depend on the jquery and you need to declare:
The dependency on
jquery
that allows using the jQuery object.The dependency on
jquery-plugin
that ensures the plugin to be loaded in thejquery
dependency before it is injected in the portlet module.
Exposing version of jQuery globally¶
The built-in jQuery is currently declared as an AMD module. By default, jQuery will not be available in the window object of the browser. This section shows how to make jQuery available so you can write a code like in a plain script.
The following script will make jQuery available by mounting the jQuery object in the window object:
require( ["SHARED/jquery"], function($) {
// the '$' in window.$ is alias, you can make the other for yourself.
window.$ = $;
});
This script must be integrated as a shared script:
<scripts>
<name>imediatejs</name>
<script>
<path>/myfolder/imediateJScript.js</path>
</script>
</scripts>
A portlet can then provide its own script on which it depends:
<portlet>
<name>foo</name>
<script>
<name>portletjs</name>
<path>/myfolder/portlet.js</path>
</script>
<depends>
<scripts>imediatejs</scripts>
</depends>
</scripts>
With the following JavaScript:
$("#foo").html("<h1>hello global jQuery</h1>");
Implementing a global jQuery plugin¶
There are a few ways to implement the usage of a global jQuery plugin. However, make sure that the global jQuery is available before the global jQuery plugin is loaded.
As you have seen before how you can scope a module to a portlet, the
module will be loaded when the portlet is on a page using the
PORTLET
scope. Accordingly, use the PORTAL
scrope instead of
PORTLET
. The main difference is that the loading of your plugin will
be triggered on a specific site instead of a specific portlet.
Create the
jQuery
plugin as a script namedmyPlugin.js
and integrate it to your plugin:require(["SHARED/jquery"], function($) { $.fn.myPluginFunction = function() { // Your work here; }; });
Bind the script in the site and reuse the
immediatejs
script seen before:<portal> <name>classic</name> <scripts> <script> <name>myPlugin</name> <path>/myfolder/myPlugin.js</path> </script> <script> <name>imediatejs</name> <path>/myfolder/imediateJScript.js</path> </script> </scripts> </portal>
Now, your plugin is globally available and you can use it:
<script type="text/javascript">
$('#foo').myPluginFunction();
</script>
Using CommonJS modules¶
CommonJS defines its own module format, although it is not supported by eXo Platform. The adapter format can be used to adapt CommonJS modules to work well in eXo Platform.
Here are two simple CommonJS modules:
math.js
exports.add = function() { var sum = 0, i = 0, args = arguments, l = args.length; while (i < l) { sum += args[i++]; } return sum; };
increment.js
var add = require('math').add; exports.inc = function(val) { return add(val, 1); };
CommonJS modules use their required function which conflicts with the
RequireJS same function. So, to make it work in the AMD enabled
environment, these modules need to be wrapped and injected predefined
modules: require
, exports
and module
provided by Requirejs
(See the details here).
eXo Platform will wrap the code basing on the configuration using the adapter
format:
<module>
<name>math</name>
<script>
<adapter>
define(["require", "exports"], function(require, exports) {
<include>/commonjs/math.js</include>
});
</adapter>
</script>
<depends>
<module>require</module>
</depends>
<depends>
<module>exports</module>
</depends>
</module>
<module>
<name>increment</name>
<script>
<adapter>
define(["require", "exports", "math"], function(require, exports) {
<include>/commonjs/increment.js</include>
});
</adapter>
</script>
<depends>
<module>require</module>
</depends>
<depends>
<module>exports</module>
</depends>
<depends>
<module>math</module>
</depends>
</module>
Using Mustache.js module¶
Mustache.js is a popular
JavaScript template engine. Mustache is written to be executed in
several kinds of environment as a global object, a CommonJS module, or
as a native AMD module. If the “module
“, “exports
”
dependencies are available, Mustache will register it as a CommonJS
module. It can be adapted to eXo Platform thanks to the adapter format:
<module>
<name>mustache</name>
<script>
<adapter>
define(["require", "exports", "module"], function(require, exports, module) {
<include>/requirejs/js/plugins/mustache.js</include>
});
</adapter>
</script>
<depends>
<module>require</module>
</depends>
<depends>
<module>exports</module>
</depends>
<depends>
<module>module</module>
</depends>
</module>
Use the adapter
tag here and declare the require
, exports
and module
dependencies of the CommonJS
module. Now any module
can have Mustache instance injected just by declaring it in its
dependencies list:
<module>
<name>foo</name>
...
<depends>
<module>mustache</module>
</depends>
</module>
(function(mustache){
//code that use Mustache
mustache.render(template);
})(mustache);
Using Text.js module¶
RequireJS
supports the loader plugin which enables a module to be a
plugin and uses the AMD system to load web resources in an efficient
manner.
When there are many templates or the template has a large size,
embedding template in the page is not a good choice for front-end
performance reason. It would be better to use Text.js
to load the
separate template files and inject them as dependencies.
Text.js
which is a native AMD module also depends on the module
dependency predefined by the AMD loader. Thanks to the native AMD
support of eXo Platform, it is straightforward to declare and use Text.js
in eXo Platform:
<module>
<name>text</name>
<script>
<path>/requirejs/js/plugins/text.js</path>
</script>
<depends>
<module>module</module>
</depends>
</module>
Now you can use the mustache and text modules to load templates and render them in your own module:
<portlet>
<name>foo</name>
<module>
...
<depends>
<module>mustache</module>
</depends>
<depends>
<module>text</module>
<as>tmpl</as>
<resource>/path/to/template.html</resource>
</depends>
</module>
</portlet>
You have the text module in the dependency list with a <resource>
tag, Text.js
will load that resource template and inject it with the
tmpl
name. Here is the JavaScript of the portlet:
function(mustache, tmpl) {
var html = mustache.render(tmpl);
//append rendered html to DOM
})(mustache, tmpl);
Overriding the dependency of a native AMD module¶
While declaring a native AMD module, the module dependency names must
match with the AMD dependencies declared in the define
function
arguments. When there is a mismatch between a module declared in the
native module and the module system of eXo Platform, the as
tag can be
used to rename the dependencies.
There is a foo.js
file defining an AMD module named foo
with two
dependencies ["dep1", "dep2"]
as follows:
define("foo", ["dep1", "dep2"], function(a1, a2) {
// The module
});
Supposing that the dependencies are declared as module1
and
module2
in eXo Platform and the names do not match. To override them, use
the as
tag to rename the dependencies:
<module>
<name>foo</name>
<script>
<path>/path/to/foo.js</path>
</script>
<depends>
<module>module1</module>
<as>dep1</as>
</depends>
<depends>
<module>module2</module>
<as>dep2</as>
</depends>
</module>
Accessing a module from a script¶
Sometimes it is required to access a module from a script, RequireJS
provides such capability by using the require
function to execute a
function in the managed context:
require(["SHARED/ModuleA"], function(a) {
// Codes of interacting with module A
a.doSomething();
});
In such a situation, you need to use the AMD module name of the module
on which you need to depend, this case uses PORTLET/ModuleA
. The
prefix in uppercase is the module scope among SHARED
, PORTLET
and PORTAL
.
Disabling minification¶
In eXo Platform, Javascript scripts declared as modules are minified by default in order to reduce their size and therefore the data volume when downloaded in web page.
This minification may lead to conflicts and errors when the script is incompatible with eXo Platform minifier (Google Closure Compiler).
Starting from eXo Platform 5.0, this minification can be disabled with the new module attribute ‘minify’ :
<minify>false</minify>
This option could be set in gatein-resources.xml
in script tag:
<module>
<name>myModule</name>
<script>
<path>/javascript/myScript.js</path>
<minify>false</minify>
</script>
</module>
Note
We highly recommend to always enable scripts minification. If you have conflicts, take care to not deactivate minification in the whole platform.
To use this new option, the new XSD 1.4 should be used.
Localizing with resources¶
In your custom-extension.war
, if you want to add your own resource
files to support localization, you can do as follows:
Add some folders and files to your custom extension to have:
WEB-INF |__ classes | |__ locale | |__ portal | |__ sample_en.properties | |__ sample_fr.properties |__ conf | |__ configuration.xml | |__ locale-configuration.xml |__ web.xml
In this example there are 2 resources for English (_en) and French (_fr). The resource files can be .properties or .xml.
It is necessary that the resources are located in WEB-INF/classes
.
Edit
locale-configuration.xml
to configure ResourceBundleService:<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <external-component-plugins> <target-component>org.exoplatform.services.resources.ResourceBundleService</target-component> <component-plugin> <name>Sample ResourceBundle Plugin</name> <set-method>addResourceBundle</set-method> <type>org.exoplatform.services.resources.impl.BaseResourceBundlePlugin</type> <init-params> <values-param> <name>init.resources</name> <value>locale.portal.sample</value> </values-param> <values-param> <name>portal.resource.names</name> <value>locale.portal.sample</value> </values-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
Pay attention to the value locale.portal.sample
. It is like a
translation of the path of your resources (locale/portal/sample
-
with the language code and file extension name is eliminated).
Edit
configuration.xml
to import thelocale-configuration.xml
:<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <import>war:/conf/locale-configuration.xml</import> </configuration>
Adding/Removing a language¶
Developers can define new or remove a defined language through the locale configuration file. The resource is managed by org.exoplatform.services.resources.LocaleConfigService as follows:
<component>
<key>org.exoplatform.services.resources.LocaleConfigService</key>
<type>org.exoplatform.services.resources.impl.LocaleConfigServiceImpl</type>
<init-params>
<value-param>
<name>locale.config.file</name>
<value>war:/conf/common/locales-config.xml</value>
</value-param>
</init-params>
</component>
All languages defined in the locale-config.xml
file are listed in
the Interface Language Settings
window. The resource bundle is managed by
org.exoplatform.services.resources.ResourceBundleService
as follows:
<external-component-plugins>
<!-- The full qualified name of the ResourceBundleService -->
<target-component>org.exoplatform.services.resources.ResourceBundleService</target-component>
<component-plugin>
<!-- The name of the plugin -->
<name>Sample ResourceBundle Plugin</name>
<!-- The name of the method to call on the ResourceBundleService in order to register the ResourceBundles -->
<set-method>addResourceBundle</set-method>
<!-- The full qualified name of the BaseResourceBundlePlugin -->
<type>org.exoplatform.services.resources.impl.BaseResourceBundlePlugin</type>
<init-params>
<!--values-param>
<name>classpath.resources</name>
<description>The resources that start with the following package name should be load from file system</description>
<value>locale.portlet</value>
</values-param-->
<values-param>
<name>init.resources</name>
<description>Store the following resources into the db for the first launch</description>
<value>locale.portal.sample</value>
</values-param>
<values-param>
<name>portal.resource.names</name>
<description>The properties files of the portal , those files will be merged
into one ResourceBundle properties
</description>
<value>locale.portal.sample</value>
</values-param>
</init-params>
</component-plugin>
</external-component-plugins>
Adding a new language
To add a new language, you need to copy the default
locale-config.xml
file from
platform-extension/WEB-INF/conf/common/locales-config.xml
to your
custom-extension.war
(with the same path) and add the corresponding
language entry.
For example, to add Italian, do as follows:
Add the following code to the
locale-config.xml
file under your extension project.<locale-config> <locale>it</locale> <output-encoding>UTF-8</output-encoding> <input-encoding>UTF-8</input-encoding> <description>Default configuration for Italian locale</description> </locale-config>
Create a new resource bundle as
sample_it.properties
in thecustom-extension.war!/WEB-INF/classes/locale/portal
folder or in thesrc/main/resources/locale/portal
folder of the source code if you are using Maven.
Note
This step is necessary because the Resource Bundle Service of the portal will find keys and values in the resource bundle of each corresponding language.
Restart the server.
To check if the added language takes effect, click your username on the top navigation bar and click Change Language. In the Interface Language Settings window that appears, you will see the Italian is listed as below:
Removing a language
To remove an existing language, you need to delete the relevant language
code in the locale-config.xml
file and all files containing the
suffix name as the key of language.
For example, to remove French, do as follows:
Find and remove the following code from the
locale-config.xml
file under your extension project.<locale-config> <locale>fr</locale> <output-encoding>UTF-8</output-encoding> <input-encoding>UTF-8</input-encoding> <description>Default configuration for france locale</description> </locale-config>
Continue removing all resource bundle files containing the suffix name as fr in all folders.
Note
It is recommended this step be done to delete unnecessary data in the application.
Restart the server.
To check if French is removed, hover your cursor over your username on the top navigation bar, then click Change Language.
In the Interface Language Settings window that appears, French is no longer listed.
Declaring servlet/filter¶
If you ship servlets or filters as part of your portal extension, and these servlets/filters need to access specific resources of a portal during the process of the servlets or filters request, make sure that these servlets/filters are associated with the current portal container. The proper way to do that is making your servlet or filter extend the org.exoplatform.container.web.AbstractHttpServlet or org.exoplatform.container.web.AbstractFilter class respectively. Both AbstractHttpServlet and AbstractFilter have the method named getContainer(), which returns the current PortalContainer.
Declaring servlet/filter extension to get the current portal
Add the dependency to
pom.xml
of your extension project.<dependency> <groupId>org.exoplatform.kernel</groupId> <artifactId>exo.kernel.container</artifactId> <scope>provided</scope> </dependency>
Implement the servlet/filter.
Servlet
package org.sample.servlet; ... public class SampleServlet extends AbstractHttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { System.out.println("[SampleServlet]: Current portal " + getContainer()); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); } }
Filter
package org.sample.filter; ... public class SampleFilter extends AbstractFilter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { System.out.println("[SampleFilter]: Current portal " + getContainer()); chain.doFilter(request, response); } @Override public void destroy() { } }
Register the servlet/filter to
web.xml
of your extension.Servlet
<servlet> <servlet-name>SampleServlet</servlet-name> <servlet-class>org.sample.servlet.SampleServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>SampleServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping>
Note
- The servlet will not only properly initialize the current
PortalContainer, but also set the current thread’s context ClassLoader to servlets or filters. The ClassLoader looks for resources in associated web applications in the order specified by the dependencies configuration.
Filter
<filter> <filter-name>SampleFilter</filter-name> <filter-class>org.sample.filter.SampleFilter</filter-class> </filter> <filter-mapping> <filter-name>SampleFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Creating a page layout template¶
In eXo Platform, a page is a set of portlets which could be arranged in columns, rows, and tabs. A page layout template is a layout sample that is used in Page Creation Wizard.
In this tutorial, you will:
Create your own page layout template.
Localize the label with your language resources.
Customize the preview icon of your template.
The following picture points out the category, the preview icon and labels:
Create your custom extension as follows:
Edit the page template in
page.xml
:<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_objects_1_7 http://www.gatein.org/xml/ns/gatein_objects_1_7" xmlns="http://www.gatein.org/xml/ns/gatein_objects_1_7"> <name></name> <container template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> </container> <container template="system:/groovy/portal/webui/container/UITableColumnContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <container template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> </container> <container template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> </container> </container> <container template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> </container> </page>
In which:
UIContainer
(built-in inportal.war
) is the smallest container that should not contain other containers. They will be aligned in row if you do not put them in any parent container.To align them in column, put them in a
UITableColumnContainer
. To add a tab to the page, useUITabContainer
. See more samples inportal.war!/WEB-INF/conf/portal/template/pages
.
Declare your custom page template by adding a new component plugin (to import in one of configuration files)
<external-component-plugins> <target-component>org.exoplatform.portal.page.PageTemplateService</target-component> <component-plugin> <name>analyticsPageConfigs</name> <set-method>addPageTemplate</set-method> <type>org.exoplatform.portal.page.PageTemplatePlugin</type> <init-params> <object-param> <name>category</name> <object type="org.exoplatform.webui.core.model.SelectItemCategory"> <field name="name"> <string>customPageConfigs</string> </field> <field name="options"> <collection type="java.util.ArrayList" item-type="org.exoplatform.webui.core.model.SelectItemOption"> <value> <object type="org.exoplatform.webui.core.model.SelectItemOption"> <field name="label"> <string>customPage.CustomLayout</string> </field> <field name="value"> <string>custom</string> </field> <field name="icon"> <string>CustomLayout</string> </field> </object> </value> </collection> </field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins>
This code adds a category named “customPageConfigs” and a template named “CustomLayout”. The arguments of the SelectItemOption object are:
the key of the localized name of the template
the unique name of the template. This name must be the folder name containing the page.xml file (WEB-INF/conf/portal/template/pages/<name>/page.xml)
the CSS class name for the template icon
Add the file
WEB-INF/classes/locale/portal/webui_en.properties
inside your extension WAR:UIDropDownPageTemp.item.customPageConfigs=Custom Page Configs UIWizardPageSelectLayoutForm.label.customPage.CustomLayout=Custom Layout
Note
The localization is explained in Localizing with resources.
The
locale.portal.webui
resource name is configured inportal.war
and you must not change its path.
Add the preview icon as follows:
i. Edit your stylesheet in
myStylesheet.css
:.CustomLayout { width: 270px; height: 170px; margin: auto; background: url('images/ItemSelector.gif') no-repeat left -680px; }
ii. For simplification, you can copy the image named
ItemSelector.gif
fromeXoResources.war!/skin/DefaultSkin/webui/component/UISelector/UIItemSelector/background
.iii. Edit the
gatein-resources.xml
file:<gatein-resources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_resources_1_3 http://www.gatein.org/xml/ns/gatein_resources_1_3" xmlns="http://www.gatein.org/xml/ns/gatein_resources_1_3"> <portal-skin> <skin-name>Enterprise</skin-name> <skin-module>myStylesheet</skin-module> <css-path>/skin/myStylesheet.css</css-path> </portal-skin> </gatein-resources>
Tip
Read more about the portal skinning technique in GateIn Reference.
Deploy your custom extension and test:
Managing look and feel¶
This section will help you to learn how to customize the look and feel of eXo Platform.
A skin is a set of CSS and images files. eXo Platform comes with a default skin called Enterprise.
You may want to only make some changes to an existing skin by adding or overriding style for custom portlets, native portlets, or the whole portal. This can be done without creating a new skin.
Tip
The main difference between creating a new skin and customizing an existing one is that when creating a new skin, the Enterprise skin (and optionally other skins) is still available and can be used. Therefore a site could use the new skin and another site could still use the Enterprise skin.
So if you need to provide the capability to select multiple skins, go for creating a new skin, otherwise customizing an existing skin should be sufficient.
In this section, we will treat these elements:
Skin elements Introduction to skin elements and concepts.
Stylesheets loading priority Description of the loading strategy of stylesheets in web pages.
Customizing existing skins How to customize existing skins.
Creating a new skin How to create a new skin, to select a default skin.
Skin best practices A list of best practices in skin development.
Customizing layout All details to customize layout of a site, a page and the shared layout.
By following this section, you are able to customize the eXo Platform look and feel effectively.
Skin elements¶
eXo Platform provides support for skinning the entire User Interface (UI) of a site, including your own portlets. Skins are designed to help you pack and reuse common graphic resources.
The complete skinning of eXo Platform can be decomposed into three main parts: site skin, portlet windows and portlet skin.
Portal skin
The portal skin contains styles for the HTML tags (for example, div, th, td) and the portal UI (including the toolbar). This should include all UI components, except for window decorators and portlet specific styles.
Portlet skin
The portlet skins affect how portlets are rendered on the page. The portlet skins can be affected in two main ways described in the following sections.
Portlet Specification CSS Classes: The portlet specification defines a set of CSS classes that should be available to portlets. eXo Platform provides these classes as a part of the portal skin. This enables each portal skin to define its own look and feel for these default values.
eXo Platform provides a means for portlet CSS files to be loaded that is based on the current portal skin. This enables a portlet to provide different CSS styles to better match the current site’s look and feel.
Portlet windows
The CSS styles are associated with the portlet window decorators. The window decorators contain control buttons and borders surrounding each portlet. Individual portlets can have their own window decorators selected, or be rendered without one.
Note
The window decorators and the default portlet specification CSS classes should be considered as separate types of skinning
components, but they need to be included as a part of the overall portal skin. The portal skin must include CSS classes of these components or they will not be displayed correctly. A portlet skin does not need to be included as a part of the portal skin and can be included within the portlets web application. See Skins in page markups for more information.
Stylesheets loading priority¶
When a portal page is loaded, the CSS stylesheets are loaded in the following order:
Stylesheets of the Portal skin
ECMS site stylesheets (see here for more details)
Stylesheets of the portlets contained in the page
Stylesheets of the Portal skin elements with a module name starting with customModule (see here for more details)
A style of a stylesheet can override the style of all the stylesheets loaded before. Therefore if a portlet body skin defines the following CSS class:
my-portlet-style-class {
background-color: green;
}
It can be customized by adding the following class in a custom module of the portal skin:
my-portlet-style-class {
background-color: red;
font-size: 1.2em;
}
This new style definition changes the background color to red and defines the font size to 1.2em.
Tip
Inside each category, stylesheets are loaded following an order defined by the attribute css-priority for Portal and Portlet skin elements and the field Priority for ECMS site stylesheets. The stylesheets with lower priority value are loaded first.
Customizing existing skins¶
Existing skins can be customized by adding new CSS files or by changing the favicon.
Adding custom style¶
eXo Platform allows to inject new CSS files by configuration, by
declaring them in a file WEB-INF/gatein-resources.xml
in an
extension webapp.
Portal skin
New CSS files can be added to a portal skin, meaning they are loaded in
all the pages of the sites using this skin. For example, in order to add
a new CSS file to override some styles of the Enterprise skin, the
following configuration must be defined in the file
WEB-INF/gatein-resources.xml
:
<gatein-resources>
<portal-skin>
<skin-name>Enterprise</skin-name>
<css-path>/skin/my-style.css</css-path>
<skin-module>MyStyle</skin-module>
<css-priority>0</css-priority>
<overwrite>false</overwrite>
</portal-skin>
</gatein-resources>
In which:
<portal-skin>
is the root element that declares the portal skin element, including:<skin-name>
is the identifier of the skin.<skin-module>
is the identifier of the skin element. It must be an unique name.<css-path>
is the path to the CSS file in the webapp which defines the style of the site.<css-priority>
is the loading priority amongst portal stylesheets. Lower values are loaded first. This property is optional.<overwrite>
is the property that defines if the portlet skin overwrites or not another skin which has the same name, if it exists. This property is optional. Defaults to false.
Once deployed, the CSS file /skin/my-style.css
will be loaded in all
the pages of the sites which use the Enterprise skin. CSS files will be
loaded as link tags in the head section of the web page,
following a defined Loading strategy,
so before the portlets CSS files.
Tip
A sample Maven project is available here as a working example to customize a skin.
Portal skin - Custom Module
Since the Portal skin stylesheets are loaded before the Portlets skin
stylesheets, portlets style overrides the portal style. While this is
very useful to control the style of a portlet no matter what the portal
style is, it can be a problem when a portal style must be applied on all
portlets no matter what the portlets style is. eXo Platform provides the
capability to define portal CSS files which can be loaded after portlet
CSS files. They must be declared in the file
WEB-INF/gatein-resources.xml
like portal CSS files. The only
difference is that they must have a skin-module tag starting with
customModule:
<gatein-resources>
<portal-skin>
<skin-name>Enterprise</skin-name>
<css-path>/skin/my-custom-module-style.css</css-path>
<skin-module>customModuleMyStyle</skin-module>
<overwrite>false</overwrite>
</portal-skin>
</gatein-resources>
Portlet skin
Portlets often require additional styles that may not be defined by the
portal skin. eXo Platform allows to define additional stylesheets for each
portlet and appends the corresponding link tags to the head. Here is an
example that can be added in the file WEB-INF/gatein-resources.xml
to define a new CSS file to be included whenever the portlet
WhoIsOnLinPortlet is available on a portal page:
<gatein-resources>
<portlet-skin>
<application-name>homepage-portlets</application-name>
<portlet-name>WhoIsOnLinPortlet</portlet-name>
<skin-name>Enterprise</skin-name>
<css-path>/skin/WhoIsOnLinPortlet/my-portlet-style.css</css-path>
<css-priority>1</css-priority>
<overwrite>false</overwrite>
</portlet-skin>
</gatein-resources>
In which:
<portlet-skin>
is the root element that declares the portlet skin element, including:<application-name>
is the context name of web application that contains the portlet declared in<portlet-name>
.<portlet-name>
is the identifier of the portlet.<skin-name>
is the identifier of the skin. The portlet skin element is only loaded when the given skin is selected for the site.<css-path>
is the path to the CSS file in the webapp which defines the stylesheets of the portlet.<css-priority>
is loading priority amongst portlets stylesheets. Lower values are loaded first. This property is optional.<overwrite>
is the property that defines if the portlet skin overwrites or not another skin which has the same name, if it exists. This property is optional. Defaults to false.
This example tells eXo Platform to load the CSS file
/skin/WhoIsOnLinPortlet/my-portlet-style.css
when the portlet
WhoIsOnLinPortlet of the webapp homepage-portlets is displayed
on the page. As for sites, CSS files will be loaded as link tags in
the head section of the web page. The ID attribute of <link> element
uses the pattern portletAppName_PortletName
. In the above example,
the ID of the link is “homepage-portlets_WhoIsOnLinPortlet”.
The portlet can be any native eXo Platform portlet or any custom portlet.
Note
The full schema for gatein-resources.xml files can be found at https://www.exoplatform.com/xml/ns/gatein_resources_1_4.xsd.
Customizing the Enterprise Skin¶
The Enterprise Skin (available only in Enterprise Edition) has been done as a new skin, using the customStyle.less file. It means that its CSS style is loaded as a portal skin custom module, so after all the others type of stylesheets, as described in the CSS priority chapter.
In order to customizing styles defined in the Enterprise Skin, a portal skin custom module must be used, with a higher priority.
Changing portlet icons¶
Each portlet can be represented by a unique icon that you can see in the
portlet registry or page editor. This icon can be changed by adding an
image to the directory of the portlet webapplication:
skin/DefaultSkin/portletIcons/
.
To use the icon correctly, it must be named after the portlet. For
example, the icon for the Hello portlet named HelloPortlet is
located at: skin/DefaultSkin/portletIcons/HelloPortlet.png
.
Note
You must use skin/DefaultSkin/portletIcons/
for the directory to store the portlet icon regardless of which skin is going to be used.
Changing the favicon¶
The eXo Platform Favicon is packaged in platform-extension
webapp:
platform-extension.war!/favicon.ico
.
You can override it by packaging it at the root of your extension,
for example custom-extension.war!/favicon.ico
.
Note
Note that the icon should be 16x16 pixels to be well displayed.
For a quick test you can download an icon somewhere, like the Google
favicon available at http://google.co.uk/favicon.ico. Resize it
to 16x16 and pack it in the extension.
After deploying your extension, you should remove images cache in the browser and refresh eXo Platform page. Here is the illustration with Google favicon replacing eXo Platform favicon:
Creating a new skin¶
This section covers the following topics:
Creating a new skin¶
eXo Platform allows to create new skins. New skins are based on the Enterprise skin. The difference between creating a new skin compared to customizing an existing skin is that the new skin is available besides the Enterprise skin, so a site can use the new skin while another site still use the Enterprise skin.
In order to create a new skin, process as follows:
Create a Maven project from the available sample.
This sample contains a Maven module for the skin extension webapp (sample-skin-webapp) and a Maven module for packaging it as an addon (packaging). It has the following structure:
where the important files are:
sample-skin-webapp/src/main/webapp/META-INF/exo-conf/configuration.xml
, the extension activation file. The extension name must be adapted.sample-skin-webapp/src/main/webapp/WEB-INF/web.xml
. The extension name (display-name) must be adapted.sample-skin-webapp/src/main/webapp/skin/less/customVariables.less
, the file to override style variables (see below).sample-skin-webapp/src/main/webapp/skin/less/customStyle.less
, the file to define new style (see below).sample-skin-webapp/pom.xml
, the build file. The groupId, artifactId, version, exo.skin.name and exo.skin.display.name must be adapted.
eXo Platform defines a set of style variables which allow to change the default style globally. The complete list is available here. If you want to modify style variables, change the value of any available variable in the file
src/main/webapp/skin/less/customVariables.less
.For example:
@baseColor: #ffffff; // text color , border color, and other UI elements.. @baseBackground: #333333; // default background , background for content display.... @primaryColor: #0ab5f5; // primary button and link color... @primaryBackground: #222222; // main background such as header popup background, tab items... @colorIconDefault : #fff; // icon color @images-path: "/{YOUR-SKIN-ADDON}/skin/images/themes/default"; @contentBackground: url("@{images-path}/ShareImages/Background/texture-gray.jpg") repeat left top;
If you want to go further by defining new CSS classes or by overriding default CSS classes, add them in the file
src/main/webapp/skin/less/customStyle.less
. For example:body { background-color: green; }
This is a Less file, so you can use all the power of Less syntax.
Testing the new skin
Deploy the addon locally.
Apply the new skin to your site.
i. Start eXo Platform package, and log in with the administrator account.
ii. Go to Administration Portal Sites, then click Edit Site Configuration next to one site, for example, Intranet site.
iii. In the Skin drop-down list, select the new skin and click Save.
Refresh the page and enjoy the new skin.
The current homepage:
The homepage with your applied skin add-on:
Style variables and icons/images¶
This section describes all the variables that can be changed in
src/main/webapp/skin/less/customVariables.less
when creating a new
skin.
Basic variables
There are 4 main color variables. These are used to define color for majority of component.
Variable |
Value |
Description |
---|---|---|
@baseColor |
#000000 |
Color of text, border, and other UI elements. |
@baseBackground |
#ffffff |
Color of background displaying content. |
@primaryColor |
#2f5e92 |
Color of primary buttons and links. |
@primaryBackground |
#f0f0f0 |
Color of main background, for example, of header popup and tab items. |
The other colors are dependant on the 4 main colors above and can automatically adapt to the value of the @baseColor variable (dark or light). In particular:
Variable |
Inheritance/Value if base color (@baseColor) is dark (lightness(@baseCo lor) < 50%) |
Inheritance/Value if base color (@baseColor) is light (lightness(@baseCo lor) >= 50%) |
Description |
---|---|---|---|
@baseColorDark |
lighten(@baseColor , 20%) (#333333) |
lighten(@baseColor , 60%); |
Default color of title, text, text input, and label. |
@baseColorMedium |
lighten(@baseColor , 50%) (#808080) |
lighten(@baseColor , 50%) |
Text color of navigation links when being selected. |
@baseColorLight |
lighten(@baseColor , 60%) (#999999) |
darken(@baseColor, 20%) |
Default color of small text, subtitle, and text field explanation. |
@borderColor |
lighten(@baseColor , 80%) (#cccccc) |
darken(@baseColor, 20%) |
Default border color of box, container, and text input. |
@primaryBackground Hover |
darken(@primaryBac kground, 4%) |
darken(@primaryBac kground, 3%) |
Background color of table row, disabled input field, ready-only form, gray tab in a page when being hovered. |
@primaryBackground Light |
lighten(@primaryBa ckground, 3%) |
lighten(@primaryBa ckground, 4%) |
Background color of table accent, hr line, well, and active pagination. |
@revealBackgroundH over |
lighten(@baseColor , 98%) |
darken(@baseColor, 98%) |
Background color of reveal component hover status. |
@revealBackgroundS elected |
lighten(spin(@prim aryColor, -10%), 52%) |
darken(spin(@prima ryColor, -10%), 52%) |
Background color of reveal component selected status. |
@primaryDarkColor |
darken(@primaryCol or, 10.5%) (#224469) |
darken(@primaryCol or, 18.5%) (#578dc9) |
Border color of primary button, and left navigation. |
@primaryLightColor |
lighten(@primaryCo lor, 18.5%) (#578dc9) |
lighten(@primaryCo lor, 10.5%) (#224469) |
Background color of primary button, and dropdown when being hovered. |
@btnBackground |
@baseBackground |
@primaryBackground |
Background color of default button. |
@btnBackgroundHigh light |
@primaryBackground |
@baseBackground |
Background color of highlight button. |
@dropdownArrowRigh t |
darken(@baseBackgr ound, 20%) (#aac5e3) |
lighten(@baseBackg round, 90%) |
Background color of arrow on the right of dropdown. |
@quotePrimaryBackg round |
lighten(@quotePrim aryBorder, 18%) (#aac5e3) |
darken(@primaryCol or, 10%) (#aac5e3) |
Background color of primary quote. |
Advanced variables
For each component, some own variables are defined. They may be new values, or be inherited from base variables. To modify these components, change value of these ones, or change the variables inherited (be careful if it impacts the others).
Icon Fonts
Variable
Inheritance/Value
Description
@colorIconDefault
#999999
Default color of action icons and action lists having dark grey labels.
@colorIconExtra
#626262
Default color of action icons having light or medium grey labels.
@colorIconPrimary
@primaryColor
Color of icons for hovered/pressed/selected effect on light or dark grey icons.
@colorIconSecondary
#ffffff
Hover icon in dropdown menu, icons in primary button or black/grey bar.
@sizeIcon24x24
22px
Size of medium icons.
@sizeIcon12x12
10px
Size of mini icons.
@sizeIcon64x64
60px
Size of extra-large icons.
@sizeIcon32x32
30px
Size of large icons.
@sizeIconDefault
14px
Size of default icons.
@font-path
“/eXoSkin/skin/fonts”
Path to the icon fonts folder.
Left Navigation
Variable
Inheritance/Value
Description
@plfNavigationTitleColor
@primaryColor
Color of left navigation’s heading.
@plfNavigationBgBorderLe ft
@primaryLightColor
Color of left border of the selected item in the left navigation.
@plfNavigationBorderLeft
@primaryDarkColor
Border color of left border of the selected item in the left navigation.
@plfNavigationColor
@baseColorMedium
Text color of items in the left navigation.
@plfNavigationBgSelected
@baseBackground
Background color of the selected item of the left navigation.
@plfNavigationBorderSele cted
@borderColor
Color of top and bottom borders of the selected item in the left navigation.
@plfNavigationColorSelec ted
@baseColorMedium
Text color of the selected item in the left navigation.
@plfNavigationColorHover
@linkColorHover
Text color of the hovered item in the left navigation.
Breadcrumb
Variable
Inheritance
Description
@breadcrumbColor
@baseColorDark
Color of breadcrumb text.
@breadcrumbHoverColor
@primaryColor
Color of breadcrumb text when being hovered.
@breadcrumbActiveColor
@textLightColor
Color of breadcrumb text when being selected.
Default button
Variable
Inheritance/Value
Description
@btnColor
@baseColorDark
Text color of default button.
Gradient (@btnBackground, @btnBackgroundHighligh t)
Gradient (@baseBackground,@primaryBa ckground)
Background color of default button (gradient background).
@btnBorder
@borderColor
Border color of default button.
Primary button
Variable
Inheritance/Value
Description
Gradient (@btnPrimaryBackground, @btnPrimaryBackgroundHigh light)
Gradient (@primaryLightColor, @primaryColor)
Background color of primary button (gradient background).
@btnBorder
@btnPrimaryBorder
Border color of primary button.
@btnColor
@btnPrimaryColor
Text color of primary button.
Calendar picker
Variable
Inheritance
Description
@calComponentDayTodayBackgrou nd
@primaryBackgrou nd
Background color of today.
@calComponentCurrentWeekBorde r
@borderColor
Border color of current week.
@calComponentDaySelectedColor
@baseBackground
Text color of selected day.
@calComponentTimeInputBoxBack ground
@primaryBackgrou nd
Background color of input value.
@calComponentDaySelectedBackg round
@primaryColor
Background color of selected day.
Dropdown
Variable
Inheritance
Description
@dropdownBackground
@baseBackground
Background color of dropdown.
@dropdownLinkBackgroundHov er
@primaryLightColor
Background color of hovered item.
@dropdownBorder
@borderColor
Border color of dropdown.
Form: input field, textarea, select box
Normal state
Variable
Inheritance
Description
@inputBackground
@baseBackground
Background color of input field, text area and select box.
@inputBorder
@borderColor
Border color of input field, text area and select box.
@inputColor
@baseColorDark
Text color of input field and text area.
Focus state
Variable
Inheritance/Value
Description
@inputFocusBorder
lighten(@infoColor, 30%)
Border color of hovered item.
@inputFocusColor
@inputColor
Text color of hovered item.
Disable state
Variable
Inheritance
Description
@inputDisabledBackgroun d
@primaryBackgroundHov er
Background color of disabled item.
@inputDisableColor
@textLightColor
Text color of disabled item.
Read-only state
Variable
Inheritance
Description
@formReadOnyBackground
@primaryBackgroundHov er
Background color of read-only item.
@formReadOnyColor
@baseColorDark
Text color of read-only item.
Notifications
Variable
Inheritance
Description
@warningBackground
@errorBackground
@successBackground
@infoBackground
@warningColorLight
@errorColorLight
@successColorLight
@infoColorLight
Background color of notifications.
@warningBorder
@errorBorder
@successBorder
@infoBorder
@warningColor
@errorColor
@successColor
@infoColor
Border color of notifications.
@warningText
@errorText
@successText
@infoText
@textColor
@textColor
@textColor
@textColor
Text color of notifications.
Pagination
Variable
Inheritance
Description
@paginationBackground
@baseBackground
Background color of paginator.
@paginationBorder
@borderColor
Border color of paginator.
@paginationActiveBackgr ound
@primaryBackgroundL ight
Background color of selected item in the paginator.
Popover
Variable
Inheritance
Description
@popoverBackground
@baseBackground
Background color of popover.
@popoverArrowColor
@popoverBackground
Background color of popover arrow.
Popup
Variable
Inheritance/Value
Description
@uiPopupBackground
@baseBackground
Background color of popup.
@uiPopupBorder
@borderColor
Border color of popup.
@uiPopupHeaderBackground
@primaryBackground
Background color of popup header.
@header-popup-bg
#484848
Background header of popup.
@header-popup-title
#c1c1c1;
Text color of header popup.
Table
Variable
Inheritance/Value
Description
@tableBackground
transparent
Background color of table.
@tableBorder
@borderColor
Border color of table.
@tableBackgroundHover
@primaryBackgroundHover
Background color of hovered row.
@tableBackgroundAccent
@primaryBackgroundLight
Background of accent table.
Tab
Variable
Inheritance
Description
@tabNormalLinkBackground
@primaryBackground
Background color of tab item.
@tabNormalActiveLinkBackg round
@baseBackground
Background color of selected tab item.
@tabNormalLinkBorder
@borderColor
Border color of tab item.
@tabNormalLinkColor
@textColor
Text color of tab item.
@tabNormalContentBackgrou nd
@baseBackground
Background color of tab content.
Label
Variable
Inheritance
Description
@labelBackground
@baseColorLight
Background color of label.
@labelHoverBackground
@primaryLightColor
Background color of selected label.
@labelColor
@baseBackground
Text color of label.
Badget
Inheritance
Description
@primaryBackground
Background color of default badget.
@textColor
Text color of default badget.
@primaryColor
Background color of primary badget.
@baseBackground
Text color of primary badget.
@baseBackground
Background color of white badget.
@textColor
Text color of white badget.
Tooltip
Variable
Inheritance/Value
Description
@tooltipBackground
lighten(@black, 29%)
Background color of tooltip.
@tooltipArrowColor
@tooltipBackground
Background color of tooltip background.
Icons/images
PSD
folder contains all psd files. If user wants to customize some icons (for example, color), he/she can change these files.system
folder contains icons, backgrounds, and images that are commonly used in all applications.themes
folder contains some themes of PRODUCT. The default theme is named “default”. The others are themes that you can customize basing on the PSDs. You can also build your own theme by changing the PSD files.
Selecting a default skin¶
You can select a new default skin for a site via two following ways:
Directly inline, via the UI of eXo Platform. See the Editing configurations section for more details.
In your extension <PLFDevGuide.eXoAdd-ons.PortalExtension>, via the XML configuration file as below.
The default skin can be set in the portal configuration files. The skin configured as default is used by eXo Platform as the administrator starts/restarts the server.
To change the default skin of a site, for example Intranet, simply add a
skin tag to the
custom-extension.war!/WEB-INF/myintranet-conf/portal/intranet/portal.xml
file.
<portal-config>
<portal-name>intranet</portal-name>
<locale>en</locale>
<access-permissions>Everyone</access-permissions>
<edit-permission>*:/platform/administrators</edit-permission>
<skin>MySkin</skin>
...
</portal-config>
Best practices¶
How to develop a skin add-on that is compliant with eXo skin?
When developing a skin add-on, you may use some of your own components. Using your style is good. However, when you apply the new skin add-on, its theme would not be compatible with the eXo Platform one that is bad. See the below example - dark theme of the new skin add-on is already applied for all, but not applied for the Chat window of eXo Platform.
What is the solution?
It is simple. Reuse the eXo Platform UX component instead of your own UX add-on component. As a result, the theme of Chat add-on will be changed to the new style of your skin add-on.
To get the eXo Platform UX component, see http://exoplatform.github.io/ux-guidelines/Components.html.
How to develop an add-on compliant with eXo skin (login page for instance)?
Never use hard-coded CSS link in JSP file. Instead, use SkinService to get the CSS:
//Use SkinService to get the css SkinService skinService = (SkinService) PortalContainer.getCurrentInstance(session.getServletContext()) .getComponentInstanceOfType(SkinService.class); String loginCssPath = skinService.getSkin("portal/login", "Enterprise").getCSSPath(); //… <link href="<%=loginCssPath%>" rel="stylesheet" type="text/css"/>
The
WEB-INF/gatein-resources.xml
related:<!-- Login Page skins --> <portlet-skin> <application-name>portal</application-name> <portlet-name>login</portlet-name> <skin-name>Enterprise</skin-name> <css-path>/skin/css/platform/portlets/extensions/login.css</css-path> </portlet-skin>
How to develop a gadget compliant with eXo skin?
Simply get the parent CSS (CSS load in Portal):
$(document).ready(function(){
//Check the parent container of the Iframe
if(window.top && window.top.location.href != document.location.href) {
// Get all parent's <link>s
var linkrels = window.top.document.getElementsByTagName('link');
// Get the Iframe head
var iframeHead = document.getElementsByTagName('head').item(0);
// Loop through parent's links
for (var i = 0, max = linkrels.length; i < max; i++) {
// Get parent stylesheets
if (linkrels[i].rel && linkrels[i].rel == 'stylesheet') {
// Create new link element and copy all attributes
var thestyle = document.createElement('link');
var attrib = linkrels[i].attributes;
for (var j = 0, attribmax = attrib.length; j < attribmax; j++) {
thestyle.setAttribute(attrib[j].nodeName, attrib[j].value);
}
// add the newly created link element to the head
iframeHead.appendChild(thestyle);
}
}
}
});
Which tools are recommended for converting .less to .css files?
For developers using Windows, WinLess is an ideal tool. This converts
LESS code into static CSS automatically as you save the file and shows
reports if any error in the code. Using WinLess, you do not need to
rebuild the whole Web UI component packages. You just need to choose
output for .css
file one time, then modify the .less
file and
save it. As the result, you will see your changes once refreshing your
browser.
Download WinLess and install it. To be compatible with the Less version used in eXo Platform 4.2, the WinLess version should be 1.8.0.
Go to File –> Settings, and make sure these options are checked:
Automatically compile files when saved.
Show message on successful compile.
Note
Remember to untick the checkbox: Automatically check for less.js updates.
Click Add folder and locate the directory where you put your
.less
files. WinLess will scan and find all.less
files and show them on the list.Uncheck all files using the top checkbox.
Check the files you modify, or the file importing them. For example, if you want to modify something in
variables.less
that is imported inCore.less
, you only need to checkCore.less
.Set the output in case you want to save it elsewhere.
Change the output directory. For the
Core.less
file, its output directory is$PLATFORM_TOMCAT_HOME/webapps/eXoSkin/skin/css/core.css
.Open
variables.less
and make a few changes, then save it. The WinLESS will notify you when the file has been successfully compiled into.css
or if any error in the code. By using this way, you can check the.css
output directly, without waiting for the code to complete its compilation.Refresh the web browsers to see your changes.
Note
If you are using MAC or Linux, you can use Koala.
Mapping between .less and .css files
Here is the list of .less files corresponding to the compiled .css files in eXo Platform.
Projects |
Input Path Less |
Output Path Css |
---|---|---|
Platform UI |
|
|
Calendar |
|
|
Commons |
|
|
ECMS |
|
|
|
|
|
|
``webapps/eXoSkin/skin/css/e cms/portlets/fastcontentcrea tor/fast-content-creator.css `` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Forum |
|
``webapps/eXoSkin/skin/css/f orum/portlets/answer/faq.css `` |
|
|
|
|
|
|
|
|
|
Integration |
|
|
|
|
|
|
|
|
Platform |
|
|
|
|
|
``platform-ui-skin/src/main/ webapp/skin/less/platform/po rtlets/extensions/login.less `` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
``webapps/eXoSkin/skin/css/p latform/portlets/welcome-scr eens/termsandconditions.css` ` |
|
|
|
|
Portlets |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Social |
|
|
|
|
|
|
|
|
|
|
|
|
``webapps/eXoSkin/skin/css/s ocial/portlets/uiConnections NavigationPortlet/Style.css` ` |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Wiki |
|
|
Customizing layout¶
This section covers the following topics:
Customizing a site layout¶
Skins define the color scheme and other appearance aspects of the layout, such as graphics, fonts, or font size. Thus, the way you customize your site layout will require you to impact your skin’s CSS code.
In this section, instructions are related to the configuration, for example you can see a sample of Intranet site here. You can leave all the portlet’s preferences as blank, that means the default value will be taken and you do not need to care about it at this time.
The Intranet site is decorated with the default layout as below:
As an example, here are steps to alter the layout of Intranet site by moving the MY SPACES portlet from left to the right corner.
Register a NewPortalConfigListener into UserPortalConfigService under the
custom-extension.war!/WEB-INF/conf/myintranet-conf/my-portal-configuration.xml
file.<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd http://www.exoplatform.org/xml/ns/kernel_1_2.xsd" xmlns="http://www.exoplatform.org/xml/ns/kernel_1_2.xsd"> <external-component-plugins> <!-- The full qualified name of the UserPortalConfigService --> <target-component>org.exoplatform.portal.config.UserPortalConfigService</target-component> <component-plugin> <!-- The name of the plugin --> <name>new.portal.config.user.listener</name> <!-- The name of the method to call on the UserPortalConfigService in order to register the NewPortalConfigs --> <set-method>initListener</set-method> <!-- The full qualified name of the NewPortalConfigListener --> <type>org.exoplatform.portal.config.NewPortalConfigListener</type> <description>this listener init the portal configuration</description> <init-params> <object-param> <name>portal.configuration</name> <description>description</description> <object type="org.exoplatform.portal.config.NewPortalConfig"> <field name="predefinedOwner"> <collection type="java.util.HashSet"> <value> <string>intranet</string> </value> </collection> </field> <field name="ownerType"> <string>portal</string> </field> <field name="templateLocation"> <string>war:/conf/myintranet-conf</string> </field> <field name="importMode"><string>merge</string></field> </object> </object-param> </init-params> </component-plugin> </external-component-plugins> </configuration>
Import
my-portal-configuration.xml
intocustom-extension.war!/WEB-INF/conf/configuration.xml
.<import>war:/conf/mysite-config/my-portal-configuration.xml</import>
Create
portal.xml
to override configuration of the Intranet site undercustom-extension.war!/WEB-INF/myintranet-conf/portal/intranet
.<portal-config> <portal-name>intranet</portal-name> <locale>en</locale> <access-permissions>*:/platform/users</access-permissions> <edit-permission>*:/platform/administrators</edit-permission> <properties> <entry key="sessionAlive">onDemand</entry> <entry key="showPortletInfo">0</entry> </properties> <portal-layout> <move-apps-permissions>*:/platform/administrators</move-apps-permissions> <move-containers-permissions>*:/platform/administrators</move-containers-permissions> <container template="system:/groovy/portal/webui/container/UITableColumnContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <container id="Left" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <portlet-application> <portlet> <application-ref>platformNavigation</application-ref> <portlet-ref>UICompanyNavigationPortlet</portlet-ref> </portlet> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> </portlet-application> </container> <container id="Body" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <page-body> </page-body> </container> <container id="Right" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <portlet-application> <portlet> <application-ref>platformNavigation</application-ref> <portlet-ref>UISpaceNavigationPortlet</portlet-ref> </portlet> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> </portlet-application> </container> </container> </portal-layout> </portal-config>
As you see in the portal.xml
file above, every <container>
tag
has an id attribute, for example “<container id = ‘Right‘>”.
The UISpaceNavigationPortlet is registered in this container that
specifies the new position (right) of Space portlet. When you create a
CSS file, the property applied for this container should have the
following name manner:
${container_id}TDContainer
and the details of this container:
RightTDContainer
The reason is, when you have a look at the file system:
/groovy/portal/webui/container/UITableColumnContainer.gtmpl
shown
above, you will see this code fragment:
<table class="UITableColumnContainer"
style="table-layout: fixed; margin: 0px auto;">
<tr class="TRContainer">
<% for(uiChild in uicomponent.getChildren()) {%>
<td class="${uiChild.id}TDContainer TDContainer"><%
uicomponent.renderUIComponent(uiChild) %></td> <% } %>
</tr>
</table>
So, in the table element (which represents the outer container), there are many td elements, each of which has the class attribute that equals to the id of the corresponding child component plus the “TDContainer” string literal.
Create a
DefaultStylesheet.css
file undercustom-extension.war!/templates/skin
with the following content:.RightTDContainer { width: 200px; } .BodyTDContainer { } .LeftTDContainer { width: 200px; }
Register the newly created CSS in the above step for the Enterprise skin which is currently used by the Intranet site under the
gatein-resources.xml
.<gatein-resources xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_resources_1_3 http://www.gatein.org/xml/ns/gatein_resources_1_3" xmlns="http://www.gatein.org/xml/ns/gatein_resources_1_3"> <portal-skin> <skin-name>Enterprise</skin-name> <skin-module>myintranet-css</skin-module> <css-path>/templates/skin/EnterpriseStylesheet.css</css-path> </portal-skin> </gatein-resources>
Restart the server. The position of MY SPACE portlet is now rearranged at the top right corner of Intranet site.
Note
For customizing the top navigation bar and branding, see Customizing a shared layout.
For customizing the right body of a site, see Customizing a page layout.
Customizing a page layout¶
This section is related to the configuration. You can see a sample of Intranet here. You can leave all the portlet’s preferences as blank, this means the default value will be taken and you do not need to care about it at this time.
Like the site layout, you can easily define the layout for each page in a site easily.
As an example, here are steps to alter the homepage layout of Intranet by moving the Getting Started and Calendar portlets from right to the left corner.
Follow steps 1 and 2 which are similar when you change the site layout.
Create
pages.xml
to override configuration of the Intranet site undercustom-extension.war!/WEB-INF/myintranet-conf/portal/intranet
.<page-set xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.gatein.org/xml/ns/gatein_objects_1_2 http://www.gatein.org/xml/ns/gatein_objects_1_2" xmlns="http://www.gatein.org/xml/ns/gatein_objects_1_2"> <page> <name>homepage</name> <title>Home Page</title> <access-permissions>Everyone</access-permissions> <move-apps-permissions>*:/platform/administrators</move-apps-permissions> <move-containers-permissions>*:/platform/administrators</move-containers-permissions> <container template="system:/groovy/portal/webui/container/UITableColumnContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <container id="Left" template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <portlet-application> <portlet> <application-ref>homepage-portlets</application-ref> <portlet-ref>GettingStartedPortlet</portlet-ref> </portlet> <title>Getting Started</title> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> <show-application-state>false</show-application-state> <show-application-mode>false</show-application-mode> </portlet-application> <portlet-application> <portlet> <application-ref>homepage-portlets</application-ref> <portlet-ref>HomePageCalendarPortlet</portlet-ref> </portlet> <title>Calendar Portlet</title> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> <show-application-state>false</show-application-state> <show-application-mode>false</show-application-mode> </portlet-application> </container> <container template="system:/groovy/portal/webui/container/UIContainer.gtmpl"> <access-permissions>Everyone</access-permissions> <portlet-application> <portlet> <application-ref>social-portlet</application-ref> <portlet-ref>UserActivityStreamPortlet</portlet-ref> </portlet> <title>User Activity Stream</title> <access-permissions>*:/platform/users</access-permissions> <show-info-bar>false</show-info-bar> <show-application-state>false</show-application-state> <show-application-mode>false</show-application-mode> </portlet-application> </container> </container> </page> </page-set>
As you see in the
pages.xml
file above, the GettingStartedPortlet and HomePageCalendarPortlet are registered in the container with id = “Left” that specifies the new position (left) of Getting Started and Calendar portlets.Follow steps 5 and 6. The Getting Started and Calendar portlets are now in the left that is different from its default page layout:
Upload component¶
In this section, you will learn how to configure the Upload service that is defined by the org.exoplatform.upload.UploadService class.
This can be configured with the following XML code:
<component>
<type>org.exoplatform.upload.UploadService</type>
<init-params>
<value-param>
<name>upload.limit.size</name>
<description>Maximum size of the file to upload in MB</description>
<value>10</value>
</value-param>
</init-params>
</component>
This code allows uploading files with the default size limit (10MB). The default value unit is in Megabytes.
This limitation will be used by default by all applications if no application-specific limit is set.
If the value is set to 0
, the upload size is unlimited.
Using the Upload component
Create an org.exoplatform.webui.form.input.UIUploadInput object type by using one of three following constructors:
The default constructor that allows uploading the file with the size of 10 MB.
public UIUploadInput(String name, String bindingExpression, int limitFile)
This constructor allows you to customize the size limit of uploaded files by using the limitSize parameter. The default value unit is Megabytes.
public UIUploadInput(String name, String bindingExpression,int limitFile, int limitSize)
This constructor allows you to customize the size limit and the value unit by using the limitSize and unit parameters respectively.
In eXo Platform, you can set the value unit to Megabytes (MB), Kilobytes (KB) or Gigabytes (GB).
public UIUploadInput(String name, String bindingExpression, int limitFile, int limitSize, UploadUnit unit)
The following is an example using the third form:
PortletRequestContext pcontext = (PortletRequestContext)WebuiRequestContext.getCurrentInstance(); PortletPreferences portletPref = pcontext.getRequest().getPreferences(); int limitFile = Integer.parseInt(portletPref.getValue("uploadFileLimit", "1").trim()); int limitSize = Integer.parseInt(portletPref.getValue("uploadFileSizeLimit", "").trim()); UploadUnit limitUnit = UploadUnit.valueOf(portletPref.getValue("uploadFileLimitUnit", "MB").trim()); UIUploadInput uiInput = new UIUploadInput("upload", "upload", limitFile, limitSize, limitUnit);
Obtain the limitation from the XML configuration by adding the following code to either
portlet.xml
orportlet-preferences.xml
:<! The number of files are uploaded --> <preference> <name>uploadFileLimit</name> <value>3</value> <read-only>false</read-only> </preference> <! The size limit --> <preference> <name>uploadFileSizeLimit</name> <value>300</value> <read-only>false</read-only> </preference> <! The unit limit --> <preference> <name>uploadFileLimitUnit</name> <value>KB</value> <read-only>false</read-only> </preference>
Note
The 0
value means the upload size is unlimited, and the value unit is set to MegaBytes.
Use the getUploadDataAsStream() method to get the uploaded data:
UIUploadInput input = (UIUploadInput)uiForm.getUIInput("upload"); InputStream[] inputStreams = input.getUploadDataAsStreams(); ...
The upload service stores a temporary file on the file system during the upload process. When the upload is finished, the service must be cleaned to:
Delete the temporary file.
Delete the classes used for the upload.
Use the
removeUploadResource(String uploadId)
method defined in the upload service to purge the file:UploadService uploadService = uiForm.getApplicationComponent(UploadService.class) ; UIUploadInput uiChild = uiForm.getChild(UIFormUploadInput.class) ; for(String uploadId : uiChild.getUploadIds()) { uploadService.removeUpload(uploadId) ; }
Note
Ensure the file is saved before the service is cleaned.