что читатель имеет базовые знания о среде разработки APEX и БД Oracle.
Отчет с возможностью отметки отдельных записей для обработки — вещь очень удобная, но к сожалению до сих пор не реализованная «из коробки». В APEX API имеется функция apex_item.checkbox2 которая позволяет показать в отчете чекбоксы, но обрабатывать их приходится программисту. Обработку сильно осложняет то, что в многостраничном отчете чекбоксы существуют только для текущей страницы, и при переходах между страницами отчета значения отмеченных чекбоксов нужно где-то сохранять. В данном случае для этого используется коллекция APEX.
Несколько слов о коллекции APEX. Коллекция APEX — это именованная структура данных, существующая в пределах пользовательской сессии, предназначенная для манипуляций с данными при построении сложных форм ввода данных, мастеров и пр. Коллекция напоминает своей структурой таблицу из 50 атрибутов типа VARCHAR2(4000), 5 атрибутов типа Number, 5 атрибутов типа Date, 1 атрибута типа XML, 1 атрибута типа BLOB и 1 атрибута типа СLOB. Работают с коллекцией через PL/SQL API APEX_COLLECTION.
Итак, на страничке будем использовать такие хитрости и трюки:
- чекбоксы в отчет добавим при помощи функции apex_item.checkbox2.
- изменение состояния чекбокса будем обрабатывать при помощи javascript функции, вызываемой из Dynamic Action.
- сохранять состояние чекбоксов будем в коллекции APEX, для этого будем вызывать AJAX-ом OnDemand процедуру записи в коллекцию.
Будем считать, что у нас уже есть страничка с интерактивным отчетом на базе запроса:
select id, name, descr from geo
где id — поле первичного ключа таблицы. Его мы будем передавать в качестве атрибута чекбокса в коллекцию, и его будем использовать при обработке отмеченных записей.
П.1 Создадим процесс на странице :
Create > Page Component > Process >
Process type: PL/SQL code
Name: AJAX_UpdateChBoxCollection
Point: Ajax Callback
PL/SQL Page Process заполняем следующим кодом:
declare
l_value varchar2(4000);
l_seq_id number := 0;
seq number := 0;
l_collection_name constant varchar2(30) := 'CHBOXCOLL';
begin
------------------------------------------------------------------
-- Get the value of the global var which was set by JavaScript
------------------------------------------------------------------
l_value := apex_application.g_x01;
------------------------------------------------------------------------
-- If our collection named doesn't exist yet, create it
------------------------------------------------------------------------
if apex_collection.collection_exists( l_collection_name ) = FALSE then
apex_collection.create_collection( p_collection_name => l_collection_name );
end if;
---------------------------------------------------------------------
-- See if the specified value is already present in the collection
---------------------------------------------------------------------
for c1 in (select seq_id
from apex_collections
where collection_name = l_collection_name
and c001 = l_value) loop
l_seq_id := c1.seq_id;
exit;
end loop;
-------------------------------------------------------------------
-- If the current value was not found in the colleciton, add it.
-- Otherwise, delete it from the collection.
-------------------------------------------------------------------
-- Htp.Prn('Seq:'||l_seq_id);
if l_seq_id = 0 then
begin
seq := apex_collection.add_member( p_collection_name => l_collection_name,
p_c001 => l_value );
-- Htp.Prn(' Set:'||l_value||' seq_id:'||seq);
end;
else
begin
apex_collection.delete_member( p_collection_name => l_collection_name,
p_seq => l_seq_id );
-- Htp.Prn(' Rst:'||l_value);
end;
end if;
commit;
end;
Эта процедура создает коллекцию с именем «CHBOXCOLL» если она еще не была создана,
и добавляет/удаляет запись в коллекцию для изменившего состояние чекбокса. Название коллекции везде пишется капсом. Оно должно быть уникальным. В результате в коллекции будут атрибуты отмеченных записей.
П.2 Далее добавим на страницу код javascript функции который будет вызывать этот процесс:
Edit Page > JavaScript > Function and Global Variable Declaration
function ajax_call_func(val)
{ apex.server.process( "AJAX_UpdateChBoxCollection",
{ x01: val},
{ dataType: "text",
success: function( pData )
{ console.log(pData); }
}
);
};
Эта функция вызовет ранее нами созданную функцию с именем «AJAX_UpdateChBoxCollection» и передаст ей значение «val» через глобальную переменную apex_application.g_x01.
Результат типа «text» может быть возвращен из процедуры «AJAX_UpdateChBoxCollection» при
помощи функции Htp.Prn('какой-то результат').
П.3 Создадим чекбоксы в отчете, для этого отредактируем запрос следующим образом:
select apex_item.checkbox2(p_idx => 1,
p_value => id,
p_attributes => 'class="chbox_UpdColl"',
p_checked_values => a.c001) cbox,
id,name,descr
from geo,apex_collections a
where a.c001 (+)= id
and a.collection_name (+)= 'CHBOXCOLL'
В данном случае в запрос добавляется колонка на базе функции apex_item.checkbox2,
а также наша коллекция, связанная с нашей таблицей по id. Коллекция нам нужна лишь для получения состояния чекбокса при первоначальной загрузке страницы и при переходе между страницами отчета. Тип колонки с чекбоксами должен быть «Standart report column».
Коротко опишу используемые параметры apex_item.checkbox2:
p_idx => 1
номер переменной APEX_APPLICATION 1 соответствует F01; 2 — F02 и тп.
p_value => id
значение, которое будем передавать в коллекцию, в данном случае это поле «id» таблицы
p_attributes => 'class=«chbox_UpdColl»'
HTML атрибуты, в данном случае используется тэг class=«chbox_UpdColl» который потом используется в качестве jQuery selector в Dynamic Action.
p_checked_values => a.c001
использует значение из коллекции для указания состояния чекбокса
Для наглядности добавим еще один отчет на базе Sql запроса, который назовем Collecton и который будет показывать нам содержимое коллекции:
select a.seq_id,a.c001
from apex_collections a
where a.collection_name (+)= 'CHBOXCOLL'
П.4 Создадим Dynamic Action который будет срабатывать по изменению состояния чекбокса и вызывать созданную в П.3 JavaScript функцию:
Event: Change
Selection Type: jQuery Selector
jQuery Selector:.chbox_UpdColl
Condition: none
Action: Execute JavaScript Code
Fire When Event Result Is: True
Fire On Page Load: False
Code:
var
$checkBox = $(this.triggeringElement);
ajax_call_func( $checkBox.val() );
В поле jQuery Selector записывается значение атрибута 'class' из параметров функции apex_item.checkbox2 в П.3. Перед ним нужно обязательно поставить точку.
В созданный Dynamic Action добавим еще одно действие — обновление отчета коллекции после нажатия на чекбокс:
Action: Refresh
Selection Type: Region
Region: Collecton
Функционал чекбоксов уже работает, остается только добавить обработчик отмеченных чекбоксами записей. Пример такого обработчика с последовательным извлечением данных из коллекции при помощи курсора и записью их в файл собираюсь оформить в виде отдельной статьи.
Основная идея почерпнута здесь. Автор сохраняет отмеченные чекбоксы списком в Item на странице.
Недостаток — максимальная длина хранимого списка — 4000 символов. Использование коллекции в нашем случае снимает это ограничение.
Про использование Ajax Callback в APEX хорошо написано здесь.
Код тестировался на сайте apex.oracle.com на Application Express 5.1.0.00.45
Пожелания и конструктивная критика приветствуется.
Комментарии (17)
pic16f874
11.01.2017 11:59Сказать честно — искал решение и собрал его из нескольких статей, не внедряясь в дебри документации.
В оригинале, в коллекции хранились не только ID, но и еще целый набор данных для выгрузки.
Если Вы готовы предоставить свой вариант решения данной задачи, то я с радостью протестирую — сравню — опишу результаты.ottello
11.01.2017 14:20Посмотрите здесь https://docs.oracle.com/cd/E59726_01/doc.50/e39149/apex_item.htm#AEAPI194, в доке есть описание как создавать процесс.
AlgenGold
11.01.2017 12:38Интересно узнать почему был выбран именно APEX? Как называется должность? Каковы перспективы этого продукта? Какая ЗП у тех кто с этим работает? Кто и где использует этот продукт?
pic16f874
11.01.2017 12:47+1Апекс был выбран из-за высокой скорости разработки интерфейсов к таблицам БД. Должность называлась инженер электросвязи, зарплата за гранью добра и зла :). Хотя на фрилансе за такую работу платят как за Web-программирование. Продукт успешно развивается, хотя и не сильно продвигается оракулом.
Можно использовать везде где нужен интерфейс к таблицам БД. Одна из фишек — система
сама проверяет валидность данных при изменении: т.е. если несколько пользователей одновременно
открыли одну и ту же запись, сделали изменения и нажали кнопку «сохранить», то сохранятся данные первого пользователя, все остальные получат ошибку.AlgenGold
11.01.2017 13:17Скорость действительно высока, если использовать стандартные компоненты(интерфейс при этом будет ужасен), но шаг в сторону, как например то, о чем вы пишите в статье, и уже скорость значительно падает, если вообще все колом не встает, а задача элементарная.
Использовать этот продукт можно не везде, а только там где БД оракл.ottello
11.01.2017 14:19Это не шаг в сторону, это вполне штатная фишка, описанная в документации. Выше дал ссылку.
AlgenGold
11.01.2017 15:31шаг можно сделать не только в сторону штатной фишки описанной в документации. Например вместо чекбоксов кнопка с тремя состояниями Вкл\выкл\не нажата. Задача простая, но не каждый сможет ее решить.
pic16f874
11.01.2017 15:59динамически генерируемая картинка с соответствующей кнопкой по значению поля.
по ссылке — действие.
viktprog
11.01.2017 16:30Во-первых, можно реализовать на стандартном g_fxx, он для этого и нужен.
Во-вторых, по каждому клику происходит целых два запроса к серверу, причем оба запроса асинхронные и второй не ждет завершения первого.
В-третьих, зачем там вообще обновление региона?
Если-таки изобретать велосипед, то думаю, не проще, но правильнее было бы класть состояния чекбоксов в json и отправлять через apex.ajax.clob непосредственно перед самим действием и по before refresh, а запрос переписать с нативным парсингом этого json из clob_content. Либо, если не хочется парсить json на лету, можно отправить в свой процесс и так же по коллекции разложить. Если совсем не хочется json, то можно все отправить в процесс в тех же g_fxx. А можно не изобретать велосипед.pic16f874
11.01.2017 17:20приведите свой пример кода, если Вам не трудно.
любопытно сравнить производительность.
ottello
А зачем коллекции, вместо стандартных G_Fxx? Тогда код сократился бы…