Всегда приятно общаться с приложением, которое запоминает твои повадки и словно чувствует тебя, того, что ты хочешь. Любая UI библиотека или платформа обладает увы лишь базовым функционалом и набором компонент. Например, если колонка в таблице не перемещается или по ней нельзя отсортировать, то такое приложение, в котором она используется сложно назвать дружелюбным. К счастью, сегодня таким функционалом никого не удивишь. Однако, не каждая программа запомнит положение этой колонки и в следующем сеансе отобразит ее именно на том же месте. Возможно, также будет раздражать каждый раз устанавливать положение разделителя в SplitPane или вводить одни и те же параметры фильтра. Как правило, такие удобства приходится обеспечивать самим разработчикам.
Примеров таких на первый взгляд маленьких доработок множество, но предлагаемых платформой решений всего два, и по сути они похожи: создать свой компонент на основе базового, создать свой Skin к базовому компоненту, переопределив поведение. Ни тот, ни другой способ не является простым в реализации, к тому же на каждый компонент необходимо будет писать свой компонент-адаптер. Я встречал не мало людей, кому этот способ был более знаком и понятен.
Но он далеко не единственный. Что, если взять возможности платформы, которая поддерживает шаблон обозреватель для дочерних Node, и при добавлении или удалении подграф Node прогонять через набор плагинов, каждый из которых занимается своей специфической работой? Один умеет все запоминать и восстанавливать при повторном сеансе, другой — указанным компонентам меняет контекстное меню, добавляя функцию копирования текста. Кто-то из них добавляет три точки в конце текста, если он не умещается, а при наведении мыши показывает подсказку с полным текстом, только если он не уместился. Самое главное, что не важно из какой библиотеки этот компонент, можем ли мы от него наследоваться и переопределить нужное нам поведение. Все что нам в таком случае надо — научить плагин работать с нужными компонентами при необходимости по разному.
Таким мог бы быть слушатель коллекции дочерних элементов:
private final ListChangeListener changeListener = (ListChangeListener<Node>) (ListChangeListener.Change<? extends Node> c) -> {
if (c.next()) {
c.getAddedSubList().forEach(this::applySettingsForNodeAndAddListenerForItsChild);
}
};
Таким бы — код обработки каждой измененной Node-ы:
private void applySettingsForNodeAndAddListenerForItsChild(Node n) {
if (!checkApplySettings(n)) {
apply(n);
ObservableList<Node> children = getChildren(n);
if (children != null) {
addListnerForUpdateChildren(children);
}
markNodePropertyApplied(n);
}
}
А таким — непосредственно код вызова самого плагина, который зарегистрирован на этот тип компонент:
public Node apply(Node node) {
List<SettingsPlugin> settingsPlugins = settingsMap.get(Node.class);
if (settingsPlugins != null) {
for (SettingsPlugin plugin : settingsPlugins) {
node = plugin.apply(node, userSettings.getSettings());
}
}
List<SettingsPlugin> settingList = settingsMap.get(node.getClass());
if (settingList != null) {
for (SettingsPlugin plugin : settingList) {
node = plugin.apply(node, userSettings.getSettings());
}
}
return node;
}
Вот интерфейс самого плагина:
public interface SettingsPlugin {
public Node apply(Node node, Map<String, Object> userSettings);
}
Необходимо только на коллекции дочерних элементов Root элемента Scene один раз зарегистрировать слушателя, а на остальном подграфе он зарегистрируется сам...
Последнее время провожу аналогию по возможностям платформ для настольных и веб приложений. Было бы интересно узнать, как подобный функционал можно реализовать на разных фреймворках.