XPages. Обо всем понемногу

R

romych2004

Начну с мини описания, далее как-то это все постараюсь структурировать и дополнять и не бросать. Прошу меня поправлять, если я где-то буду не прав. Так же прошу отправлять свои материалы, если таковые имеются :)

Итак, XPages - это технология разработки веб приложений от IBM на основе сервера Domino. XPages базируется на основе JSF. По умолчанию использует клиентский фреймворк dojo(обновляется с обновлением сервера/extention library). Но с некой версии появилась тема Bootstrap.
Ряд энтузиастов написал дополнения для пейджей Extention Library, эти дополнения вошли в стандартные релизы сервера. Разработка ведется в Domino Designer, но вроде как есть плагин для Eclipse

XSP, пейджи - XPages, xpages-страницы
csjs - client-side javascript - скрипт, который выполняется на клиенте
ssjs - server-side javascript - скрипт, который выполняется на сервере
JSF - JavaServer Faces
EL - expression language - язык, использующийся в JSF
CC - Cusom Control
Чтобы не было путаницы - открываем перспективу: Window -> Open perspective -> XPages

Где ведется разработка:
  • Вкладка XPages - разработка непосредственно страниц, в вебе запускаться будет именно страница из этой вкладки
  • Вкладка Custom controls - тоже самое что XPages, только с возможностью переиспользования и без возможности самостоятельного запуска(лотусовые подформы)
  • Вкладка Script Libraries - LS, csjs, ssjs библиотеки
  • Вкладка Code -> Java - классы для работы в XPages(только в XPages)
  • Вкладка Application configuration -> Xsp Properties - свойства XSP для текущей базы
  • Вкладка Application configuration -> Faces-config - java-bean конфигурации
  • Так же есть папка WebContent, но о ней позже

В XSP используются как стандартные html-теги, так и пейджевые. XSP теги, как правило, имеют префиксы xp(стандартные контролы ), xe(контролы из Extention Library), xc(Custom controls). Например:
XML:
<xp:panel></xp:panel>
Если необходимо получить клиентский ID, то в дизайнере необходимо использоваться конструкцию #{id:XXX}, где XXX - id элемента. Данная конструкция автоматически строит динамический ID для елемента, в зависимости от контекста. Допустим, у нас может быть два CC, в обоих CC есть <xp:inputText id="text"></xp:inputText>.
В браузере ID будет выглядеть подобным образом:
Код:
view:_id1:myCustomControl1:text
view:_id1:myCustomControl2:text

Для получения этого элемента на клиенте надо будет использовать #{id:text}. Например, получение значения
JavaScript:
alert(dojo.byId("#{id:text}").value);
ВАЖНО! id вычисляется из контекста, т.о. в csjs библиотеке нельзя писать конструкцию #{id:XXX}, т.к. неизвестно к какому именно контексту относится данный элемент. Вместо этого ID можно передавать как параметр из XPages или Custom Control

В серверной части достаточно вызвать getComponent("text"). Контекст определится автоматически вне зависимости от того, где вызываем функцию, в библиотеке или в CC
Функции предназначены для обновления части страницы. Функции *Get и *Post аналогичны, обе функции обновляют часть страницы на клиенте. Разница в том, что POST отправляет данные с клиента на сервер, и, соответственно, обновляет данные на сервере.
Синтаксис:
JavaScript:
XSP.partialRefreshGet(id, params)
где
id - javascript id елемента, который нужно обновить
params - необязательный параметр, js-объект. Параметры для обновления.​
JavaScript:
XSP.partialRefreshGet(id, {
    params: {
        "$$xspsubmitvalue": "значение", // на сервере значение можно будет получить с помощью context.getSubmittedValue();
        "моя переменная": значение // на сервере значение можно будет получить с помощью param["моя переменная"]
    },
    onStart1: function() { doSomething; }, // js функция, выполняемая перед отправкой GET запроса
    onComplete1: function() { doSomething; }, // js функция, выполняемая в случае удачной отправки запроса и получения ответа
    onError1: function() { doSomething; } // js функция, выполняемая при ошибке
});
p.s. необходимо убрать единицы из названий свойств. Форумский движок не пропустил данные названия.

Во время выполнения одного из partial refresh второй выполнить нельзя. Чтобы можно было выполнить два сразу - необходимо вызвать перед вторым рефрешем
JavaScript:
XSP.allowSubmit();

Но делать этого не советуют.
Правильная конструкция для вызова нескольких рефрешей:
JavaScript:
XSP.partialRefreshGet(id1, {
    onComplete: function() {
        XSP.partialRefreshGet(id2, {
            onComplete: function() { 
                XSP.partialRefreshGet(id3); 
            }
        } 
    }
}
 
  • Нравится
Реакции: rinsk

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
471
ВАЖНО! id вычисляется из контекста, т.о. в csjs библиотеке нельзя писать конструкцию #{id:XXX}, т.к. неизвестно к какому именно контексту относится данный элемент. Вместо этого ID можно передавать как параметр из XPages или Custom Control
можно использовать глобальные переменные JS, для хранения этих ИД, инициализировать в блоке script
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
471
Правильная конструкция для вызова нескольких рефрешей:
можно вызывать xhr запросы, методами dojo, по результату - обновлять элементы
либо патиалрефреш ставится на блок
но это все зависит от контекста...
 
R

romych2004

При разработке важно понимать разницу между клиентским скриптом и серверным. Клиентский скрипт выполняется в браузере, у каждого пользователя на своем компьютере. Серверный же скрипт выполняется на сервере.

К примеру, чтобы получить элемент по айди в клиентском надо написать dojo.byId('#{id:element}');, а чтобы получить компонент на сервере - getComponent('element');
Вернутся две фундаментально разные вещи. В первом случае вернется HTMLElement, во втором Java класс компонента(потом расскажу подробнее).

ВАЖНО!!! Оператор AND в javascript - &&, оператор OR - ||, т.о. чтобы написать условие, необходимо писать:
JavaScript:
if((exp1 && exp2) || exp3) { что-то выполняем }
 
R

romych2004

Как я написал выше, XPages наследуется от JSF. Давайте изучим фундаментальные вещи JSF.
В разработке нам помогают такие инструменты как Java-Bean, Expression language, Components и Renderers
  • Java-Bean - это сериализуемые java классы, которые могут храниться в разных scopes (request, view, session, application)
    Пример java bean:
    Java:
    package ru.beans;
    public class MyBean implements java.io.Serializable {
        private String property1;
        private String property2;
    
        public String getProperty1() {
            return property1;
        }
        public void setProperty1(String property1) {
             this.property1 = property1;
        }
        public String getProperty2() {
            return property2;
        }
        public void setProperty2(String property2) {
             this.property2 = property2;
        }
        public String getHelloWorld() {
            return "Hello world!"
        }
    }

    Настраивается бин в Faces-config. Простейшая настройка выглядит вот так:
    XML:
        <managed-bean>
            <managed-bean-name>myBean</managed-bean-name>
            <managed-bean-class>ru.beans.MyBean</managed-bean-class>
            <managed-bean-scope>request</managed-bean-scope>
        </managed-bean>
    Java-beans доступны в Expression Language. К примеру, мы можем привязать инпут бокс к свойству бина
    XML:
    <xp:inputText id="inputText1" value="#{myBean.property1}"></xp:inputText>
  • Expression language - язык, использующийся в JSF для связи веб-страницы с логикой приложения. В примере выше мы привязали свойство бина к input box с помощью как раз EL. Конструкция выглядит - #{...}. Теперь когда пользователь введет что-нибудь в него, это будет записано в бин.
    EL поддерживает выражения, например:
    XML:
    <xp:text escape="true" id="computedField1" value="#{not empty myBean.property1 ? myBean.property1 : myBean.property2}"></xp:text>
    В данном примере если свойство property1 в бине не пустое(не null и не пустая строка), то выводим его, иначе выводим property2.
    Так же можно использовать такую конструкцию:
    XML:
    <xp:text escape="true" id="computedField1" value="#{not empty myBean['property1'] ? myBean['property1'] : myBean['property2']}"></xp:text>
    Значение можно вывести без наличия свойства, достаточно только get-функция:
    XML:
    <xp:text escape="true" id="computedField2" value="#{myBean.helloWorld}"></xp:text>
  • Components - это объект с некими данными, который необходимо вывести. Код на ssjs getComponent("inputText1") вернет нам объект компонента инпут бокса
  • Renderers - это отрисовщик, который рисует нам как этот компонент будет выглядеть в html, т.е. html, который увидит пользователь. Все рендеры настраиваются так же в Faces-config.
 
  • Нравится
Реакции: alexas1
R

romych2004

Оставлю здесь несколько ссылок. Если кому-то будет необходимо - могу описать


link removed
 
R

romych2004

Проблема: при агрегировании ресурсов все наши js и css соединяются в один файл с общим названием, которое кэшируется браузером.

Например, мы добавили на пейдж csjsLib1, csjsLib2, csjsLib3. В итоге у нас будет подцеплен один файл, с примерным названием
HTML:
<script type="text/javascript" src="НАША_БАЗА/xsp/.ibmmodres/.js/csjsLib1.js&csjsLib2.js&csjsLib3.js"></script>
При этом браузер сохраняет этот новый файл в кэше, и при изменении любой из этих библиотек потребуется очистка кэша.
Чтобы КЭШ не нужно было чистить, добавим в конец параметр, отвечающий за обновление кэша.

Т.к. добавляет библиотеки компонент - UIViewRoot(в ssjs - view), нам надо написать для него рендерер.

Итак, по шагам
1. Узнаем какой он использует рендерер, чтобы мы могли наследовать от него:
XML:
view.getRenderer(facesContext).getRenderer().getClass().getName()

2. Узнаем семейство(Family, необходимо для настройки):
XML:
view.getFamily()

3. Создаем рендерер:
Java:
package ru.renderer;

import java.util.ArrayList;
import javax.faces.context.FacesContext;
import com.ibm.xsp.resource.ScriptResource;
import com.ibm.xsp.resource.StyleSheetResource;

public class ViewRootRenderer extends com.ibm.xsp.renderkit.html_basic.ViewRootRendererEx2 /* из п.1 */ {
 
    @Override
    protected String getApplicationCSSUrl(FacesContext arg0,
            ArrayList<StyleSheetResource> arg1) {
        return getUrlWithVersion(arg0, super.getApplicationCSSUrl(arg0, arg1));
    }

    @Override
    protected String getApplicationJSUrl(FacesContext arg0,
            ArrayList<ScriptResource> arg1) {
        return getUrlWithVersion(arg0, super.getApplicationJSUrl(arg0, arg1));
    }
 
    protected String getUrlWithVersion(FacesContext facesContext, String url) {
        try {
            if(url == null) return null;
            String sym;
        
            if(url.indexOf("?") > -1) sym = "&";
            else sym = "?";
        
            String version = XSPContext.getXSPContext(facesContext).getProperty("myapp.version"); // получаем версию из XSP properties
            if(version == null || "".equals(version)) version = "1.0";
            version = sym + "open&version=" + version;
        
            return url + version;
        } catch(Exception e) {
            e.printStackTrace();
        }
        return url;
    }
}
Замечание: узнать какие методы можно перегрузить - кликнем правой кнопкой на название класса ViewRootRenderer -> Source -> Override/Implement methods

4. Добавляем настройку в Faces-config
XML:
<render-kit>
    <renderer>
      <component-family>javax.faces.ViewRoot</component-family>
      <renderer-type>com.ibm.xsp.ViewRootEx</renderer-type>
      <renderer-class>ru.renderer.ViewRootRenderer</renderer-class>
    </renderer>
</render-kit>
Замечание: чтобы получить renderer-type, необходимо вывести аналогично п.1 - view.getRendererType()
Замечание2: если мы не хотим, чтобы для ВСЕХ страниц применялся данный рендерер, тогда в поле renderer-type указываем любое свое название, например ru.SuperPuperRenderer, далее в свойствах самой страницы(XPage) указываем rendererType="ru.SuperPuperRenderer". Выглядеть будет так:
XML:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core"
    pageTitle="Custom renderer" rendererType="ru.SuperPuperRenderer">
    <xp:this.resources>
        <xp:script src="/csjsLib1.js" clientSide="true"></xp:script>
        <xp:script src="/csjsLib2.js" clientSide="true"></xp:script>
        <xp:script src="/csjsLib3.js" clientSide="true"></xp:script>
    </xp:this.resources>
</xp:view>

Готово! Теперь агрегируемые ресурсы будут иметь суффикс в виде версии, которую можно подправить в XSP properties, а пользователи всегда будут получать скрипты и css со свежей версии
 
Последнее редактирование модератором:
  • Нравится
Реакции: alexas1

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
471
в контексте - нужно еще про вывод исключений рассказать (какие/куда в каких случаях)
 
R

romych2004

в контексте - нужно еще про вывод исключений рассказать (какие/куда в каких случаях)
ну на мой взгляд все это довольно личные решения.. у нас принято использовать почту, где-то принято хранить в логгере.. единственное что тут можно рассказать - это про функцию postScript, чтобы выводить пользователям сообщение

про агрегирование и последствия её отмены - тоже будут интересны подробности
Тут может я чего-то не знаю? Для меня это просто производительность. С агрегированием она должна быть выше. Но если я не ошибаюсь, то в http/2 несколько файлов быстрее загружаются, чем один большой.
Или про поведение AMD рассказать?

если это мануал для новичков то напиши где этот файл находится. кому то может быть не понятно как его править при стандарном лейауте дизайнера.
В первом посте, в разделе "Общие особенности" это описано
 

lmike

нет, пердело совершенство
Lotus Team
27.08.2008
7 985
611
BIT
471
Или про поведение AMD рассказать?
да
[doublepost=1483984734,1483984499][/doublepost]
ну на мой взгляд все это довольно личные решения.. у нас принято использовать почту, где-то принято хранить в логгере.. единственное что тут можно рассказать - это про функцию postScript, чтобы выводить пользователям сообщение
исключения бывают на разном уровне...
для отладки их желательно как-то агрегировать
ну и пользователям это может "плевать" на страницу (если настройка стоит), но лучше как-то это "заворачивать", для разработчика (как по типу - отправить разработчику)
 
M

motogarri

Готово! Теперь агрегируемые ресурсы будут иметь суффикс в виде версии, которую можно подправить в XSP properties, а пользователи всегда будут получать скрипты и css со свежей версии
Скрипты будут ложиться рядом? Старые не перезаписыюватся, так ведь?
 
R

romych2004

Скрипты будут ложиться рядом? Старые не перезаписыюватся, так ведь?
Прошу прощения, что значит будут ложиться рядом? Я описал ситуацию с агрегацией ресурсов, т.е. у Вас есть, к примеру, 3 js файла и 5 css файлов. XPages их объединит в один большой файл.. и вот к этому большому файлу и будет дописана версия :)
 
M

motogarri

Я к тому, что кэш будет расти? Пример. В андроид-приложение встроен браузер, который отображает мой xpage. Я обновил extension library на сервере, в браузере на телефоне очистил кэш, скрипты обновились. В мобильном приложении - нет. И если там не предусмотрена очистка кэша это как раз та ситуация, когда подойдет агрегация?
 
R

romych2004

По-моему ресурсы живут порядка 10 дней(настроечный парамметр), потом запрашиваются новые. Но это если используется закэшированный ресурс. А что если он не будет запрашиваться - не знаю :) Мне кажется тут уже браузер будет решать, очищать или нет. Но мне кажется что ниче они не очищают :)
К сожалению, описанный мной способ подойдет только для пользовательских скриптов. Для системных не прокатит. Теоретически, конечно, можно и в каждый файл добавить параметры.. надо пробовать :)
 
Мы в соцсетях:

Обучение наступательной кибербезопасности в игровой форме. Начать игру!