Доброго времени суток.
Я уже писал несколько статей о работе с новыми плагинами в Liferay 7.x. Настало время очередной такой статьи.
При миграции некоторого портала с Liferay 6.2 на 7.x потребовалось перенести существующий плагин для CKEditor'а: он менял версию редактора, кастомизировал панель инструментов и добавлял некоторых новых возможностей (интеграция с SyntaxHighlighter и что-то ещё). Я уже писал статью о создании JSP hooks в лайфрее - казалось бы, просто можно было бы следовать описанной инструкции. Но в итоге я наткнулся на несколько подводных камней, о которых и хотел бы рассказать.
Первое, с чем пришлось столкнуться - это исчезновение привычного CKEditor'а. При установке Liferay 7.x "с нуля" повсюду обнаруживается непривычный редактор.
Прошу любить и жаловать, гордость разработчиков лайфрея - AlloyEditor!
К сожалению, мы стараний команды лайфрея не оценили и захотели вернуться к привычному редактору. Как оказалось, это делается очень легко - достаточно лишь отредактировать файл portal-ext.properties
(или создать его в $LIFERAY_HOME
, если его ещё нет) и добавить в него следующие строки
editor.wysiwyg.default=ckeditor
editor.wysiwyg.portal-impl.portlet.ddm.text_html.ftl=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.announcements.edit_entry.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.blogs.edit_entry.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.mail.edit_message.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.portlet.message_boards.edit_message.bb_code.jsp=ckeditor_bbcode
editor.wysiwyg.portal-web.docroot.html.portlet.message_boards.edit_message.html.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.taglib.ui.discussion.jsp=ckeditor
editor.wysiwyg.portal-web.docroot.html.taglib.ui.email_notification_settings.jsp=ckeditor
после чего перезапустить портал.
По именам свойств несложно понять, за что они отвечают. Возможно, вам будет важно знать, что ckeditor - не единственный доступный редактор, у вас есть выбор из следующих значений свойств:
- alloyeditor
- alloyeditor_creole
- ckeditor
- ckeditor_bbcode
- ckeditor_creole
- simple
- tinymce
- tinymce_simple.
Итак, простыми манипуляциями мы вернули горячо любимый нами редактор. Что же дальше? А дальше нам хотелось бы немного подправить его под наши нужды: обновить версию, изменить порядок/номенклатуру кнопок в панели управления и так далее. Раньше это легко делалось с помощью хуков - можно было просто заменить существующие *.js файлы редактора прямо в портале. К сожалению, современные хуки (OSGI Fragments) не дают такой возможности - они позволяют заменить динамический контент (JSP-страницы) и добавить НОВЫЙ статический контент, но не дают возможности заменить старый. К счастью, мудрые разработчики лайфрея предусмотрели такую потребность и оставили "точку расширения" для конфигурации редактора. Как нынче водится, это очерденой OSGI-компонент.
Какова же будет последовательность действий? Для начала необходимо создать OSGI-плагин.
mvn archetype:generate -DarchetypeGroupId=com.liferay -DarchetypeArtifactId=com.liferay.project.templates.service -DliferayVersion=7.0 -DgroupId=test -DartifactId=ckeditot-config -Dversion=1.0 -Dpackage=test
И в проекте создать класс по образцу этого:
import com.liferay.portal.kernel.editor.configuration.BaseEditorConfigContributor;
import com.liferay.portal.kernel.editor.configuration.EditorConfigContributor;
import com.liferay.portal.kernel.json.JSONObject;
import com.liferay.portal.kernel.portlet.RequestBackedPortletURLFactory;
import com.liferay.portal.kernel.theme.ThemeDisplay;
import org.osgi.service.component.annotations.Component;
import java.util.Map;
@Component(
immediate = true,
property = {
"editor.name=ckeditor",
"editor.name=alloyeditor",
"editor.config.key=contentEditor",
"javax.portlet.name=com_liferay_blogs_web_portlet_BlogsPortlet",
"javax.portlet.name=com_liferay_blogs_web_portlet_BlogsAdminPortlet",
"service.ranking:Integer=1000000"
},
service = EditorConfigContributor.class
)
public class EditorConfig extends BaseEditorConfigContributor {
@Override
public void populateConfigJSONObject(JSONObject jsonObject, Map<String, Object> inputEditorTaglibAttributes, ThemeDisplay themeDisplay, RequestBackedPortletURLFactory requestBackedPortletURLFactory) {
//YOUR EDITOR CONFIGURATION HERE
}
}
Давайте разберём по частям здесь написанное.
Для начала необходимо показать, что данный класс является сервисом, который будет подключен к "точке расширения". Для создания OSGI-сервиса используется аннотация @Component
с указанием реализуемого сервиса (service = EditorConfigContributor.class
), а для упрощения реализации используется наследование от класса BaseEditorConfigContributor
.
А к какому из множества редакторов будет применена данная конфигурация? Их в портале великое множество, как по типам (AlloyEditor, CKEditor...), так и по экземплярам - один и тот же редактор используется в разных портлетах, а порой и по нескольку раз в рамках одного. Чтобы указать, к каким именно редакторам будет применена конфигурация, необходимо задать несколько свойств в массиве property
аннотации @Component
.
Во-первых, можно указать тип редактора (ckeditor, alloyeditor и прочие, упомянутые ранее) посредством свойства editor.name
("editor.name=ckeditor"
).
Во-вторых, можно указать портлет, в котором будет изменена конфигурация редактора - свойство javax.portlet.name
("javax.portlet.name=com_liferay_blogs_web_portlet_BlogsPortlet"
).
В-третьих, можно указать конкретный редактор в портлете, так как их может быть несколько. Так, в блогах на странице редактирования есть несколько редакторов:
<div class="cover-image-caption <%= (coverImageFileEntryId == 0) ? "invisible" : "" %>">
<small>
<liferay-ui:input-editor
contents="<%= coverImageCaption %>"
editorName="alloyeditor"
name="coverImageCaptionEditor"
placeholder="caption"
showSource="<%= false %>"
/>
</small>
</div>
<div class="col-md-8 col-md-offset-2">
<div class="entry-title form-group">
<h1>
<liferay-ui:input-editor
contents="<%= HtmlUtil.escape(title) %>"
editorName="alloyeditor"
name="titleEditor"
placeholder="title"
showSource="<%= false %>"
/>
</h1>
</div>
<aui:input name="title" type="hidden" />
<div class="entry-subtitle">
<h4><liferay-ui:input-editor contents="<%= HtmlUtil.escape(subtitle) %>" editorName="alloyeditor" name="subtitleEditor" placeholder="subtitle" showSource="<%= false %>" /> </h4>
</div>
Обратите внимание на тэг liferay-ui:input-editor
- это и есть редактор. Каждый из них идентифицируется атрибутом name
(name="subtitleEditor", name="titleEditor"
и т.д.). И в массиве property
можно указать нужное вам имя через editor.config.key
("editor.config.key=contentEditor"
).
Наконец, можно повысить приоритет данного класса по отношению к другим с помощью свойства service.ranking
("service.ranking:Integer=1000000"
).
После указания конфигурируемых редакторов можно приступить к самому процессу настройки. Вся работа происходит в методе populateConfigJSONObject
, в который первым аргументом передаётся JSON-объект, в котором вы можете указать требуемые вам параметры настройки. Тут уже обращайтесь к документации по редактору, чтобы узнать, как совершить требуемые вам изменения.
В качестве примера можно рассмотреть изменение стандартной панели инструментов. В объекте конфигурации есть несколько свойств, отвечающих за внешний вид панели в различных ситуациях:
- toolbar_liferayArticle
- toolbar_liferay
- toolbar_simple
- toolbar_editInPlace
- toolbar_tablet
- toolbar_email
А так будет выглядеть простейшая конфигурация для редактора в блогах:
JSONArray fst = JSONFactoryUtil.createJSONArray();
fst.put("Bold");
fst.put("Italic");
JSONArray snd = JSONFactoryUtil.createJSONArray();
snd.put("NumberedList");
snd.put("BulletedList");
JSONArray toolbars = JSONFactoryUtil.createJSONArray();
toolbars.put(fst);
toolbars.put("-");
toolbars.put(snd);
jsonObject.put("toolbar_liferay", toolbars);
Важно отметить, что в рамках портала существует множество "конфигураторов", и все они могут работать с одним и тем же JSON-объектом. Потому хорошей идеей будет повышение специфичности вашего сервиса - по возможности старайтесь указывать портлет, а порой даже и имя редактора. Я потратил немало времени, пытаясь изменить занчение свойства allowedContent
на true
(потребовалось для интеграции с SyntaxHighlighter) - в итоге проблема решилась, когда я указал в массиве свойств "editor.config.key=contentEditor"
.
На данном моменте позвольте откланяться. Краткое руководство по подключению плагинов к CKEditor я дам в следующих статьях этого уютного бложика.