Oracle DB, Oracle APEX, Linux etc.

понедельник, 29 марта 2010 г.

APEX: Popup Key LOV, onChange и Submit

Сегодня на повестке дня три небольших вопроса, которые возникают относительно Popup Key LOV в APEX. В ответах P1_POPUP_KEY будет использоваться в качестве элемента этого типа.

Внимание: ответы верны для APEX версии 3.2. В принципе, они должны быть правильными и для более ранних версий, но если что не так - задавайте вопросы в комментариях.

0. Как получить значение ключа выбранного значения?


Если попробовать использовать $v("P1_POPUP_KEY"), то мы получим отображаемое значение, а не ключ, ради которого и используется обычно Popup Key LOV. Но если взглянуть в исходный код страницы, то рядом с элементом P1_POPUP_KEY мы увидим ещё один скрытый - P1_POPUP_KEY_HIDDENVALUE, который и хранит ключ.

1. Можно ли использовать событие onChange у элемента Popup Key LOV?


Можно. :) Попробуйте добавить элементу следующий код в HTML Form Element Attributes и изменить его значение в приложении:
onChange="alert($v(this) + ';' + $v(this.id+'_HIDDENVALUE'))"

2. Можно ли сделать Popup LOV, выполняющий SUBMIT при изменении значения?


Зная ответ на предыдущий вопрос, легко понять, что можно. Вот небольшой JavaScript-код с объектом, автоматизирующим этот процесс:
/* Помните, этот код использует jQuery. */
submitPopupLov = {
addSubmitHandlerToPopupLov : function (pLovId, pSubmitRequest){
$("#" + pLovId).addClass("submit-popup-lov submit-popup-lov-processed");
$("#" + pLovId + "_HIDDENVALUE").change(function (){
doSubmit(!!pSubmitRequest ? pSubmitRequest : pLovId);
});
},
setPopupLovSubmit : function (pLovId){
$("#" + pLovId).addClass("submit-popup-lov submit-popup-lov-not-processed");
},
processSubmitablePopupLovs : function (){
$(".submit-popup-lov-not-processed").each(function(){
$(this).removeClass("submit-popup-lov-not-processed").addClass("submit-popup-lov-processed");
$("#"+this.id + "_HIDDENVALUE").change(function(){
doSubmit(this.id);
});
});
}
};


Я надеюсь, вопросов без ответов стало меньше. :)

Читать далее

воскресенье, 21 марта 2010 г.

APEX: Quick Picks (QP)

Прочитав довольно интересный пост о динамически создаваемых быстрых значениях (aka quick picks, далее QP) для элемента, я решил довести дело до более логически завершённого функционала. Вот спецификация пакета, который используется для создания QP:

CREATE OR REPLACE package apex_quick_picks_pkg as 
/* APEX_QUICK_PICKS_PKG
Автор: Александр "suPPLer" Поливаный
Дата релиза: 22.03.2010
Версия: 1.0
Назначение: функции для создания списка быстрых значений (aka quick picks, QP) элемента.

Распространяется под лицензией New BSD (http://cylib.iit.nau.edu.ua/Mirrors/ask.km.ru/unics/bsd.html) :

* Copyright (c) 2010, Александр "suPPLer" Поливаный
*
* Разрешается повторное распространение и использование как в виде исходного
* кода, так и в двоичной форме, с изменениями или без, при соблюдении
* следующих условий:
*
* * При повторном распространении исходного кода должно оставаться
* указанное выше уведомление об авторском праве, этот список условий и
* последующий отказ от гарантий.
* * При повторном распространении двоичного кода должна сохраняться
* указанная выше информация об авторском праве, этот список условий и
* последующий отказ от гарантий в документации и/или в других
* материалах, поставляемых при распространении.
* * Имена владельцев авторских прав не могут быть
* использованы в качестве поддержки или продвижения продуктов,
* основанных на этом ПО без предварительного письменного разрешения.
*
* ЭТА ПРОГРАММА ПРЕДОСТАВЛЕНА ВЛАДЕЛЬЦАМИ АВТОРСКИХ ПРАВ И/ИЛИ ДРУГИМИ
* СТОРОНАМИ "КАК ОНА ЕСТЬ" БЕЗ КАКОГО-ЛИБО ВИДА ГАРАНТИЙ, ВЫРАЖЕННЫХ ЯВНО
* ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ, ПОДРАЗУМЕВАЕМЫЕ
* ГАРАНТИИ КОММЕРЧЕСКОЙ ЦЕННОСТИ И ПРИГОДНОСТИ ДЛЯ КОНКРЕТНОЙ ЦЕЛИ. НИ В
* КОЕМ СЛУЧАЕ, ЕСЛИ НЕ ТРЕБУЕТСЯ СООТВЕТСТВУЮЩИМ ЗАКОНОМ, ИЛИ НЕ УСТАНОВЛЕНО
* В УСТНОЙ ФОРМЕ, НИ ОДИН ВЛАДЕЛЕЦ АВТОРСКИХ ПРАВ И НИ ОДНО ДРУГОЕ ЛИЦО,
* КОТОРОЕ МОЖЕТ ИЗМЕНЯТЬ И/ИЛИ ПОВТОРНО РАСПРОСТРАНЯТЬ ПРОГРАММУ, КАК БЫЛО
* СКАЗАНО ВЫШЕ, НЕ НЕСЁТ ОТВЕТСТВЕННОСТИ, ВКЛЮЧАЯ ЛЮБЫЕ ОБЩИЕ, СЛУЧАЙНЫЕ,
* СПЕЦИАЛЬНЫЕ ИЛИ ПОСЛЕДОВАВШИЕ УБЫТКИ, ВСЛЕДСТВИЕ ИСПОЛЬЗОВАНИЯ ИЛИ
* НЕВОЗМОЖНОСТИ ИСПОЛЬЗОВАНИЯ ПРОГРАММЫ (ВКЛЮЧАЯ, НО НЕ ОГРАНИЧИВАЯСЬ
* ПОТЕРЕЙ ДАННЫХ, ИЛИ ДАННЫМИ, СТАВШИМИ НЕПРАВИЛЬНЫМИ, ИЛИ ПОТЕРЯМИ
* ПРИНЕСЕННЫМИ ИЗ-ЗА ВАС ИЛИ ТРЕТЬИХ ЛИЦ, ИЛИ ОТКАЗОМ ПРОГРАММЫ РАБОТАТЬ
* СОВМЕСТНО С ДРУГИМИ ПРОГРАММАМИ), ДАЖЕ ЕСЛИ ТАКОЙ ВЛАДЕЛЕЦ ИЛИ ДРУГОЕ
* ЛИЦО БЫЛИ ИЗВЕЩЕНЫ О ВОЗМОЖНОСТИ ТАКИХ УБЫТКОВ.

*/

--
-- Набор функций, которые формируют соответствующий HTML-код QP и возвращают его как varchar2.
--

-- quick_picks_from_query - QP из запроса с двумя столбцами: отображаемые QP и устанавливаемые значения
-- p_item_name - элемент, значения которого устанавливают QP
-- p_query - запрос
-- p_show_clear - отображать QP с пустым значением
-- p_clear_text - отображаемый текст QP с пустым значением
-- p_qp_style - стиль HTML-элемента QP
-- p_qp_class - класс HTML-элемента QP
-- p_container_id - id HTML-элемента, в котором собраны QP (в качестве контейнера используется div)
-- p_container_style - стиль HTML-элемента, в котором собраны QP
-- p_container_class - класс HTML-элемента, в котором собраны QP
function quick_picks_from_query
( p_item_name in apex_application_page_items.item_name%type
, p_query in varchar2
, p_show_clear in varchar2 default 'N'
, p_clear_text in varchar2 default 'Clear'
, p_qp_style in varchar2 default null
, p_qp_class in varchar2 default null
, p_container_id in varchar2 default null
, p_container_style in varchar2 default null
, p_container_class in varchar2 default null)
return varchar2;
-- quick_picks_from_lov - QP на основе LOV из приложения APEX, в котором используются QP
-- p_item_name - элемент, значения которого устанавливают QP
-- p_lov_name - название LOV
-- p_show_clear - отображать QP с пустым значением
-- p_clear_text - отображаемый текст QP с пустым значением
-- p_qp_style - стиль HTML-элемента QP
-- p_qp_class - класс HTML-элемента QP
-- p_container_id - id HTML-элемента, в котором собраны QP (в качестве контейнера используется div)
-- p_container_style - стиль HTML-элемента, в котором собраны QP
-- p_container_class - класс HTML-элемента, в котором собраны QP
function quick_picks_from_lov
( p_item_name in apex_application_page_items.item_name%type
, p_lov_name in apex_application_lovs.list_of_values_name%type
, p_show_clear in varchar2 default 'N'
, p_clear_text in varchar2 default 'Clear'
, p_qp_style in varchar2 default null
, p_qp_class in varchar2 default null
, p_container_id in varchar2 default null
, p_container_style in varchar2 default null
, p_container_class in varchar2 default null)
return varchar2;
-- quick_picks_from_arrays - QP на основе коллекций отображаемых меток QP и устанавливаемых значений. Тип коллекции - dbms_sql.varchar2_table
-- p_item_name - элемент, значения которого устанавливают QP
-- p_texts - отображаемые метки QP
-- p_values - значения, которые устанавливаются QP
-- p_show_clear - отображать QP с пустым значением
-- p_clear_text - отображаемый текст QP с пустым значением
-- p_qp_style - стиль HTML-элемента QP
-- p_qp_class - класс HTML-элемента QP
-- p_container_id - id HTML-элемента, в котором собраны QP (в качестве контейнера используется div)
-- p_container_style - стиль HTML-элемента, в котором собраны QP
-- p_container_class - класс HTML-элемента, в котором собраны QP
function quick_picks_from_arrays
( p_item_name in apex_application_page_items.item_name%type
, p_texts in dbms_sql.varchar2_table
, p_values in dbms_sql.varchar2_table
, p_show_clear in varchar2 default 'N'
, p_clear_text in varchar2 default 'Clear'
, p_qp_style in varchar2 default null
, p_qp_class in varchar2 default null
, p_container_id in varchar2 default null
, p_container_style in varchar2 default null
, p_container_class in varchar2 default null)
return varchar2;

--
-- Набор процедур, который использует htp.p для OWA-вывода сформированного набора QP
--

-- prn_quick_picks_from_query - OWA-вывод сформированных QP из запроса с двумя столбцами: отображаемые QP и устанавливаемые значения
-- p_item_name - элемент, значения которого устанавливают QP
-- p_query - запрос
-- p_show_clear - отображать QP с пустым значением
-- p_clear_text - отображаемый текст QP с пустым значением
-- p_qp_style - стиль HTML-элемента QP
-- p_qp_class - класс HTML-элемента QP
-- p_container_id - id HTML-элемента, в котором собраны QP (в качестве контейнера используется div)
-- p_container_style - стиль HTML-элемента, в котором собраны QP
-- p_container_class - класс HTML-элемента, в котором собраны QP
procedure prn_quick_picks_from_query
( p_item_name in apex_application_page_items.item_name%type
, p_query in varchar2
, p_show_clear in varchar2 default 'N'
, p_clear_text in varchar2 default 'Clear'
, p_qp_style in varchar2 default null
, p_qp_class in varchar2 default null
, p_container_id in varchar2 default null
, p_container_style in varchar2 default null
, p_container_class in varchar2 default null);
-- prn_quick_picks_from_lov - OWA-вывод сформированных QP на основе LOV из приложения APEX, в котором используются QP
-- p_item_name - элемент, значения которого устанавливают QP
-- p_lov_name - название LOV
-- p_show_clear - отображать QP с пустым значением
-- p_clear_text - отображаемый текст QP с пустым значением
-- p_qp_style - стиль HTML-элемента QP
-- p_qp_class - класс HTML-элемента QP
-- p_container_id - id HTML-элемента, в котором собраны QP (в качестве контейнера используется div)
-- p_container_style - стиль HTML-элемента, в котором собраны QP
-- p_container_class - класс HTML-элемента, в котором собраны QP
procedure prn_quick_picks_from_lov
( p_item_name in apex_application_page_items.item_name%type
, p_lov_name in apex_application_lovs.list_of_values_name%type
, p_show_clear in varchar2 default 'N'
, p_clear_text in varchar2 default 'Clear'
, p_qp_style in varchar2 default null
, p_qp_class in varchar2 default null
, p_container_id in varchar2 default null
, p_container_style in varchar2 default null
, p_container_class in varchar2 default null);
-- prn_quick_picks_from_arrays - OWA-вывод сформированных QP на основе коллекций отображаемых меток QP и устанавливаемых значений. Тип коллекции - dbms_sql.varchar2_table
-- p_item_name - элемент, значения которого устанавливают QP
-- p_texts - отображаемые метки QP
-- p_values - значения, которые устанавливаются QP
-- p_show_clear - отображать QP с пустым значением
-- p_clear_text - отображаемый текст QP с пустым значением
-- p_qp_style - стиль HTML-элемента QP
-- p_qp_class - класс HTML-элемента QP
-- p_container_id - id HTML-элемента, в котором собраны QP (в качестве контейнера используется div)
-- p_container_style - стиль HTML-элемента, в котором собраны QP
-- p_container_class - класс HTML-элемента, в котором собраны QP
procedure prn_quick_picks_from_arrays
( p_item_name in apex_application_page_items.item_name%type
, p_texts in dbms_sql.varchar2_table
, p_values in dbms_sql.varchar2_table
, p_show_clear in varchar2 default 'N'
, p_clear_text in varchar2 default 'Clear'
, p_qp_style in varchar2 default null
, p_qp_class in varchar2 default null
, p_container_id in varchar2 default null
, p_container_style in varchar2 default null
, p_container_class in varchar2 default null);
end apex_quick_picks_pkg;
/


Скрипт установки пакета пока находится здесь, возможно, дальше он переместится на что-нибудь более предназначенное для хранения исходников, например, sourceforge.
Как пользоваться этим пакетом для генерации быстрых ссылок:

  1. Создать элемент типа Display Only.

  2. Расположить его где-то рядом с тем элементом, который будут изменять QP. Хорошим вариантом может стать расположение снизу сразу под нужным элементом.

  3. Изменить у него отображение (Display As) на Display As Text (based on PL/SQL, does not save state). Помните, этот тип отображения не доступен изначально при создании, он появляется только при редактировании элемента.

  4. Изменить шаблон метки Template->No Label, очистить поле Label.

  5. Изменить тип источника значения Source Type->PL/SQL Anonymous Block.

  6. И, наконец, в качестве Source value or expression указать соответствующий PL/SQL-блок с вызовом пакета. Например, для создания быстрых значений к элементу P1_TEXT:
    begin
    apex_quick_picks_pkg.prn_quick_picks_from_query
    ( p_item_name=>'P1_TEXT'
    , p_query=>
    'select d, ltrim(to_char(d, ''RЪ'')) r
    from (select 1 d from dual
    union all
    select 2 from dual
    union all
    select 5 from dual)'
    , p_show_clear=>'Y'
    );
    end;




Ну и ещё кое-что для тех, кто не любит каждый раз всё набирать руками. Если Вы используете APEX Builder Plugin, то можно расширить предустановленные наборы (aka Set), которые он добавляет, ещё одним, который установит все необходимые атрибуты у элемента и облегчит написание PL/SQL-кода в Source value or expression (пока что есть набор только для работы с QP на основе LOV). Вот изменённый основной скрипт плагина версии 1.9.1 для FF, им нужно подменить установленный скрипт плагина в GreaseMonkey. Для этого можно открыть следующее: Инструменты->GreaseMonkey->Управление скриптами, выбрать APEX Builder Plugin 1.9, нажать "Изменить", заменить полностью содержимое скрипта текстом скрипта из архива. Затем нужно добавить к уже установленным скриптам плагина новый пользовательский скрипт и выбрать в настройках (Инструменты->GreaseMonkey->Команды скрипта->APEX Builder Plugin Settings) использование пользовательских скриптов для страниц (Adding user scripts for pages). Всё.

Пользуйтесь на здоровье!

PS: Я постараюсь написать нормальную документацию, если этот минипроект будет кому-нибудь интересен, и возникнут вопросы. Кроме того, при возникновении проблем, просьб и интересных мыслей, связанных с этим проектом, буду рад, если они отразятся в комментариях.

Читать далее

понедельник, 15 марта 2010 г.

ORA-38104: Бомба с часовым механизмом

Так сказать, моя небольшая история об неудачном дизайне.

Была задача, которую в двух словах можно описать так: хранить некоторые меняющиеся во времени значения. Одно значение должно сменять другое, без разрывов в периодах.

Нет проблем, подумал я. Всё это реализуется декларативно, через ограничения целостности. Где-то так:


create table test_params
( param_id number -- параметр
, prev_end_date date -- дата окончания прошлой записи
, start_date date -- дата начала текущей записи
, end_date date -- дата конца текущей записи
, value varchar2(10)
, primary key (param_id, start_date)
, unique (param_id, end_date)
, check(start_date - 1 = prev_end_date)
, check(end_date >= start_date)
, check(start_date = trunc(start_date))
, check(end_date = trunc(end_date))
, check(prev_end_date = trunc(prev_end_date)) );

alter table test_params
add constraint test_params_fk_chain
foreign key (param_id, prev_end_date)
references test_params(param_id, end_date)
deferrable
initially immediate;


В реальной системе всё немного сложнее (param_id ссылается на таблицу с определениями параметров), но это хорошо отражает суть.

Всё было отлично, вокруг таблицы создавался PL/SQL-код... А затем мне понадобилось написать в пакете процедуру для обновления start_date и end_date у заданной записи. Очевидно, что при их изменении нужно соответствующим образом изменить предыдущую и следующую запись в цепочке значений параметра. "Если ты можешь сделать это через SQL - сделай это так," - говорит дядя Том. И я решил сделать это через MERGE. И вот тут-то стремление к минимализму при проектировании меня подвело: изменять поле, которое используется в on_clause, MERGE отказался наотрез. ORA-38104, господа и дамы. А пересоздание ограничений откладываемыми привнесёт ещё больше проблем.

Вывод: сэкономив на поле для суррогатного первичного ключа, я потерял время, которое теперь необходимо для того, чтобы его добавить. Дизайн нужно продумывать с учётом ограничений способов, которыми будет вестись работа. :)

Читать далее

четверг, 4 марта 2010 г.

APEX: Автовход

Пользуясь APEX как разработчик, я пару месяцев назад заскучал по одной определённой возможности, а именно - автовходе. Когда работаешь в одном и том же рабочем пространстве под одной и той же учёткой, вводить логин и пароль каждый раз становится лень. Потому я, недолго думая, написал скрипт для GreaseMonkey:
$s("F4550_P1_COMPANY", "MYWORKSPACE");
$s("F4550_P1_USERNAME", "MYLOGIN");
$s("F4550_P1_PASSWORD", "MYPASSWORD");
$x("LOGIN_BUTTON").click();

Вместо MYWORKSPACE, MYLOGIN и MYPASSWORD, конечно, указаны мои рабочее пространство, логин и пароль.

Если кто-то так же ленив, как и я, возможно, это ему пригодится. :)
Читать далее