В этой статье описывается процесс создания в среде Oracle APEX страницы c интерактивным отчетом, в котором можно отметить записи для последующей их обработки. Предполагается,
что читатель имеет базовые знания о среде разработки APEX и БД Oracle.

image

Отчет с возможностью отметки отдельных записей для обработки — вещь очень удобная, но к сожалению до сих пор не реализованная «из коробки». В 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)


  1. ottello
    11.01.2017 11:41

    А зачем коллекции, вместо стандартных G_Fxx? Тогда код сократился бы…


  1. pic16f874
    11.01.2017 11:59

    Сказать честно — искал решение и собрал его из нескольких статей, не внедряясь в дебри документации.
    В оригинале, в коллекции хранились не только ID, но и еще целый набор данных для выгрузки.

    Если Вы готовы предоставить свой вариант решения данной задачи, то я с радостью протестирую — сравню — опишу результаты.


    1. ottello
      11.01.2017 14:20

      Посмотрите здесь https://docs.oracle.com/cd/E59726_01/doc.50/e39149/apex_item.htm#AEAPI194, в доке есть описание как создавать процесс.


  1. AlgenGold
    11.01.2017 12:38

    Интересно узнать почему был выбран именно APEX? Как называется должность? Каковы перспективы этого продукта? Какая ЗП у тех кто с этим работает? Кто и где использует этот продукт?


  1. pic16f874
    11.01.2017 12:47
    +1

    Апекс был выбран из-за высокой скорости разработки интерфейсов к таблицам БД. Должность называлась инженер электросвязи, зарплата за гранью добра и зла :). Хотя на фрилансе за такую работу платят как за Web-программирование. Продукт успешно развивается, хотя и не сильно продвигается оракулом.
    Можно использовать везде где нужен интерфейс к таблицам БД. Одна из фишек — система
    сама проверяет валидность данных при изменении: т.е. если несколько пользователей одновременно
    открыли одну и ту же запись, сделали изменения и нажали кнопку «сохранить», то сохранятся данные первого пользователя, все остальные получат ошибку.


    1. AlgenGold
      11.01.2017 13:17

      Скорость действительно высока, если использовать стандартные компоненты(интерфейс при этом будет ужасен), но шаг в сторону, как например то, о чем вы пишите в статье, и уже скорость значительно падает, если вообще все колом не встает, а задача элементарная.
      Использовать этот продукт можно не везде, а только там где БД оракл.


      1. ottello
        11.01.2017 14:19

        Это не шаг в сторону, это вполне штатная фишка, описанная в документации. Выше дал ссылку.


        1. AlgenGold
          11.01.2017 15:31

          шаг можно сделать не только в сторону штатной фишки описанной в документации. Например вместо чекбоксов кнопка с тремя состояниями Вкл\выкл\не нажата. Задача простая, но не каждый сможет ее решить.


          1. pic16f874
            11.01.2017 15:59

            динамически генерируемая картинка с соответствующей кнопкой по значению поля.
            по ссылке — действие.


            1. AlgenGold
              11.01.2017 18:44

              Делать кнопки картинками перестали лет 15 назад… Или о чем тут речь?


              1. pic16f874
                11.01.2017 18:54

                я Вам предложил простой вариант решения «того что не каждый сможет решить»


      1. ottello
        11.01.2017 14:21

        На самом деле, если проявить смекалку, то думаю APEX, можно прикрутить к любой БД используя Dblink =)


        1. AlgenGold
          11.01.2017 15:09

          Зачем так извращаться?


          1. ottello
            11.01.2017 15:50

            не знаю, я потенциальную возможность описал


  1. BigD
    11.01.2017 14:56

    Прочитав, я понял, как и за что я особенно люблю QlikView.


  1. viktprog
    11.01.2017 16:30

    Во-первых, можно реализовать на стандартном g_fxx, он для этого и нужен.
    Во-вторых, по каждому клику происходит целых два запроса к серверу, причем оба запроса асинхронные и второй не ждет завершения первого.
    В-третьих, зачем там вообще обновление региона?
    Если-таки изобретать велосипед, то думаю, не проще, но правильнее было бы класть состояния чекбоксов в json и отправлять через apex.ajax.clob непосредственно перед самим действием и по before refresh, а запрос переписать с нативным парсингом этого json из clob_content. Либо, если не хочется парсить json на лету, можно отправить в свой процесс и так же по коллекции разложить. Если совсем не хочется json, то можно все отправить в процесс в тех же g_fxx. А можно не изобретать велосипед.


    1. pic16f874
      11.01.2017 17:20

      приведите свой пример кода, если Вам не трудно.
      любопытно сравнить производительность.