It is possible to add additional AlloyEditor functionality through OSGi bundles. This tutorial demonstrates how to add a button to the editor.
In this tutorial, you will learn how to
- Create an OSGi bundle for your own button
- Create a custom button for
AlloyEditor
- Contribute your button to the list of available buttons
- Use your custom button in a toolbar in
AlloyEditor
Go ahead and get started by creating the OSGi bundle next.
Creating the OSGi Bundle
AlloyEditor is built on React.js
and uses jsx
to render each button in the
editor. Below is the folder structure for a module that adds a new button:
frontend-editor-my-button-web
-
src
main
java
-com/liferay/frontend/editor/my/button/web/
editor
configuration
AlloyEditorMyButtonConfigContributor.java
servlet
taglib
AlloyEditorMyButtonDynamicInclude.java
resources
META-INF
resources
js
my_button.jsx
-
.babelrc
- needed sinceJSX
is being compiled -
bnd.bnd
(example configuration shown below)Bundle-Name: Liferay Frontend Editor AlloyEditor My Button Web Bundle-SymbolicName: com.liferay.frontend.editor.alloyeditor.my.button.web Bundle-Version: 1.0.0 Liferay-Releng-Module-Group-Description: Liferay-Releng-Module-Group-Title: Rich Text Editors Web-ContextPath: /frontend-editor-alloyeditor-my-button-web
-
build.gradle
(contents shown below)configJSModules
dependencies { provided group: “com.liferay.portal”, name: “com.liferay.portal.kernel”, version: “2.0.0” provided group: “javax.servlet”, name: “javax.servlet-api”, version: “3.0.1” provided group: “org.osgi”, name: “org.osgi.service.component.annotations”, version: “1.3.0” }
transpileJS
-
package.json
(contents shown below){ “devDependencies”: { “babel-preset-react”: “^6.11.1”, “metal-cli”: “^2.0.0” }, “name”: “frontend-editor-alloyeditor-my-button-web”, “version”: “1.0.0” }
-
The contents of some of the files have been added as well, since the
build gradle
file requires some customizing.
Now that your OSGi bundle is configured, you can learn how to create buttons for the AlloyEditor next.
Creating the Button
Below is an example configuration for a JSX file that creates a new button:
/* global React, ReactDOM AlloyEditor */
(function() {
'use strict';
var React = AlloyEditor.React;
var ButtonMyButton = React.createClass(
{
mixins: [AlloyEditor.Compat.ButtonStateClasses],
displayName: 'ButtonMyButton',
propTypes: {
editor: React.PropTypes.object.isRequired
},
statics: {
key: 'myButton'
},
/**
* Lifecycle. Renders the UI of the button.
*
* @method render
* @return {Object} The content which should be rendered.
*/
render: function() {
var cssClass = 'ae-button ' + this.getStateClasses();
return (
<button className={cssClass}
onClick={this._requestExclusive}
tabIndex={this.props.tabIndex}>
<small className="ae-icon small">
Alt
</small>
</button>
);
},
/**
* @protected
* @method _doSomething
* @param {MouseEvent} event
*/
_doSomething: function(event) {
console.log('do something!');
}
}
);
AlloyEditor.Buttons[ButtonMyButton.key] = AlloyEditor.ButtonMyButton
= ButtonMyButton;
}());
The configuration above creates a new button called ButtonMyButton
. The key
aspects to note here are the lines that reference the global AlloyEditor
. You
can create your own JavaScript functions to interact with your button.
Now that you’ve seen how you can use a JSX file to create a new button, you can learn how to use your button in the editor next.
Contributing the Button
The next step is to add your button to the list of already available buttons.
This can be achieved thanks to some smartly placed
<liferay-util:dynamic-include />
tags in the editor’s infrastructure. To make
your button available in the AlloyEditor, you must extend the
BaseDynamicInclude
class.
Below is an example configuration that extends this class:
package com.liferay.frontend.editor.alloyeditor.my.button.web.servlet.taglib;
import com.liferay.portal.kernel.servlet.taglib.BaseDynamicInclude;
import com.liferay.portal.kernel.servlet.taglib.DynamicInclude;
import com.liferay.portal.kernel.theme.ThemeDisplay;
import com.liferay.portal.kernel.util.PortalUtil;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.WebKeys;
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
@Component(immediate = true, service = DynamicInclude.class)
public class AlloyEditorMyButtonDynamicInclude extends BaseDynamicInclude {
@Override
public void include(
HttpServletRequest request, HttpServletResponse response,
String key)
throws IOException {
ThemeDisplay themeDisplay = (ThemeDisplay)request.getAttribute(
WebKeys.THEME_DISPLAY);
PrintWriter printWriter = response.getWriter();
StringBundler sb = new StringBundler(7);
sb.append("<script src=\"");
sb.append(themeDisplay.getPortalURL());
sb.append(PortalUtil.getPathProxy());
sb.append(_servletContext.getContextPath());
sb.append("/js/buttons.js");
sb.append("\" ");
sb.append("type=\"text/javascript\"></script>");
printWriter.println(sb.toString());
}
@Override
public void register(DynamicIncludeRegistry dynamicIncludeRegistry) {
dynamicIncludeRegistry.register(
"com.liferay.frontend.editor.alloyeditor.web#alloyeditor#" +
"additionalResources");
}
@Reference(
target = "(osgi.web.symbolicname=com.liferay.frontend.editor.alloyeditor.my.button.web)"
)
private ServletContext _servletContext;
}
Now that your button is included, you can learn how to make the button available in the editor’s toolbar next.
Using the Button in a Toolbar
As explained in the Modifying an Editor’s Configuration
tutorial, you can configure which buttons show in the AlloyEditor toolbars by
adding your own EditorConfigContributor
. This file allows you to specify where
in the toolbar your button should appear. The example configuration below
doesn’t specify a portlet name, so the button is added to the global
AlloyEditor.
package com.liferay.frontend.editor.alloyeditor.my.button.web.editor.configuration;
import com.liferay.portal.kernel.editor.configuration.BaseEditorConfigContributor;
import com.liferay.portal.kernel.editor.configuration.EditorConfigContributor;
import com.liferay.portal.kernel.json.JSONArray;
import com.liferay.portal.kernel.json.JSONFactoryUtil;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.portlet.RequestBackedPortletURLFactory;
import com.liferay.portal.kernel.theme.ThemeDisplay;
import java.util.Map;
import java.util.Objects;
import org.osgi.service.component.annotations.Component;
@Component(
property = {"editor.name=alloyeditor", "service.ranking:Integer=1000"},
service = EditorConfigContributor.class
)
public class AlloyEditorMyButtonConfigContributor
extends BaseEditorConfigContributor {
@Override
public void populateConfigJSONObject(
JSONObject jsonObject, Map<String, Object> inputEditorTaglibAttributes,
ThemeDisplay themeDisplay,
RequestBackedPortletURLFactory requestBackedPortletURLFactory) {
JSONObject toolbarsJSONObject = jsonObject.getJSONObject("toolbars");
if (toolbarsJSONObject == null) {
toolbarsJSONObject = JSONFactoryUtil.createJSONObject();
}
JSONObject stylesJSONObject = toolbarsJSONObject.getJSONObject(
"styles");
if (stylesJSONObject == null) {
stylesJSONObject = JSONFactoryUtil.createJSONObject();
}
JSONArray selectionsJSONArray = stylesJSONObject.getJSONArray(
"selections");
for (int i = 0; i < selectionsJSONArray.length(); i++) {
JSONObject selection = selectionsJSONArray.getJSONObject(i);
if (Objects.equals(selection.get("name"), "text")) {
JSONArray buttons = selection.getJSONArray("buttons");
buttons.put("myButton");
}
}
stylesJSONObject.put("selections", selectionsJSONArray);
toolbarsJSONObject.put("styles", stylesJSONObject);
jsonObject.put("toolbars", toolbarsJSONObject);
}
}
There you have it. Now you know how to create and use custom buttons in the AlloyEditor!