Beyond The Limit

How to create Oracle APEX Pages dynamically

·

5 min read

Beyond The Limit

Sphinx Blog

create PL/SQL Package or Procedure,
because it is a Prototyp i just create an Procedure.

create or replace procedure create_page_with_region_ig 
 (p_schema_name varchar2,
  p_pagename varchar2,
  p_page_id number, 
  p_table_name varchar2, 
  p_column_list varchar2) as 

-- we have to store different ids to handle dynamic creation
l_region_id number;
l_rowact_id number;
l_rowsel_id number;
l_irgrid_rep_view_id number;
l_rowid_id number;
l_irgrid_id number;
l_irgrid_rep_id number;
l_data_type varchar2(30);
l_item_type varchar2(30);
l_is_nullable varchar2(1);
l_is_required boolean;
l_display_sequence number;
l_column_list varchar2(4000) := replace(p_column_list,':',',');

type l_numarray is table of number; 
l_array l_numarray; 

begin

The API Calls:
wwv_flow_imp_page Calls are in each APEX Installationfile, so i think it will be exists in the next 20 years from now :-)

FYI: I just make an manuel created Apex application and exported it and take a look on it. (You will not find a lot about the API in the web or doc)

-- create a MODAL Page

wwv_flow_imp_page.create_page(
 p_id=>p_page_id
,p_name=>p_schema_name||'_'||p_table_name||'_'||p_page_id
,p_alias=>p_schema_name||'_'||p_table_name||'_'||p_page_id
,p_page_mode=>'MODAL'
,p_step_title=>nvl(p_pagename,p_schema_name||'_'||p_table_name)||'_'||p_page_id
,p_autocomplete_on_off=>'OFF'
,p_page_template_options=>'#DEFAULT#:ui-dialog--stretch'
,p_protection_level=>'C'
,p_page_component_map=>'11'
,p_last_updated_by=>'ADMIN'
,p_last_upd_yyyymmddhh24miss=>to_char(sysdate,'YYYYMMDDHH24MISS') 
);

Add REGION so called a PLUG:


-- get an id from the apex environment
l_region_id :=wwv_flow_id.next_val;

-- create_page_plug is for an Region
-- the Plug_template ID i got from my first export auf the manuel 
-- created apex export file -- i have to change this to a generative one
-- in my case it is a interactive grid region 

wwv_flow_imp_page.create_page_plug(
 p_id=>wwv_flow_imp.id(l_region_id)
,p_plug_name=>p_table_name||'_'||p_page_id
,p_region_template_options=>'#DEFAULT#'
,p_component_template_options=>'#DEFAULT#'
--,p_plug_template=>wwv_flow_imp.id(10663362955261417) /* dynamic ? */
,p_plug_display_sequence=>10
,p_query_type=>'SQL'
-- we choose ROWID -- dont care about PKs for my use-case
,p_plug_source=>'select rowid,' || l_column_list || ' from ' || p_schema_name||'.'||p_table_name || ' a'
,p_plug_source_type=>'NATIVE_IG'
-- this are standards - i dont care about 
,p_prn_units=>'INCHES'
,p_prn_paper_size=>'LETTER'
,p_prn_width=>11
,p_prn_height=>8.5
,p_prn_orientation=>'HORIZONTAL'
,p_prn_page_header=>p_table_name
,p_prn_page_header_font_color=>'#000000'
,p_prn_page_header_font_family=>'Helvetica'
,p_prn_page_header_font_weight=>'normal'
,p_prn_page_header_font_size=>'12'
,p_prn_page_footer_font_color=>'#000000'
,p_prn_page_footer_font_family=>'Helvetica'
,p_prn_page_footer_font_weight=>'normal'
,p_prn_page_footer_font_size=>'12'
,p_prn_header_bg_color=>'#eeeeee'
,p_prn_header_font_color=>'#000000'
,p_prn_header_font_family=>'Helvetica'
,p_prn_header_font_weight=>'bold'
,p_prn_header_font_size=>'10'
,p_prn_body_bg_color=>'#ffffff'
,p_prn_body_font_color=>'#000000'
,p_prn_body_font_family=>'Helvetica'
,p_prn_body_font_weight=>'normal'
,p_prn_body_font_size=>'10'
,p_prn_border_width=>.5
,p_prn_page_header_alignment=>'CENTER'
,p_prn_page_footer_alignment=>'CENTER'
,p_prn_border_color=>'#666666'
);

Now we need Region Colums, 3 Columns are fixed based for each Object for this use-case: ROW_SELECTOR, ROW_ACTION and ROWID as Primary Key:


l_rowsel_id := wwv_flow_id.next_val;
-- now we need to create Region COLUMNS based on input
-- first i decide to use the IGRid Classical Columns for Interactiveiness
wwv_flow_imp_page.create_region_column(
 p_id=>wwv_flow_imp.id(l_rowsel_id)
,p_name=>'APEX$ROW_SELECTOR'
,p_item_type=>'NATIVE_ROW_SELECTOR'
,p_display_sequence=>10
,p_attribute_01=>'Y'
,p_attribute_02=>'Y'
,p_attribute_03=>'N'
);
l_rowact_id := wwv_flow_id.next_val;
wwv_flow_imp_page.create_region_column(
 p_id=>wwv_flow_imp.id(l_rowact_id)
,p_name=>'APEX$ROW_ACTION'
,p_item_type=>'NATIVE_ROW_ACTION'
,p_display_sequence=>20
);
l_rowid_id := wwv_flow_id.next_val;
-- the Primary KEY Column is the ROWID 
wwv_flow_imp_page.create_region_column(
 p_id=>wwv_flow_imp.id(l_rowid_id)
,p_name=>'ROWID'
,p_source_type=>'DB_COLUMN'
,p_source_expression=>'ROWID'
,p_data_type=>'ROWID'
,p_session_state_data_type=>'VARCHAR2'
,p_item_type=>'NATIVE_HIDDEN'
,p_display_sequence=>30
,p_attribute_01=>'Y'
,p_use_as_row_header=>false
,p_is_primary_key=>true
,p_include_in_export=>false
);

Now we need the Selected Columns from the p_column_list parameter:
Get the Metadata from the Dictonary:

/* read oracle metadata to create the other columns */
 l_array := l_numarray(); 
for c in (select column_name, data_type, nullable,column_id
              from all_tab_columns
             where table_name = p_table_name
             and owner = p_schema_name
             and instr(l_column_list, column_name) > 0
             and data_type not like ('BLOB')) 
  loop
    l_display_sequence := 30+c.column_id;
    l_data_type := c.data_type;
    l_is_nullable := c.nullable;

-- Mapping from DATATYPE to ITEM TYPE --     
    l_item_type := 'NATIVE_'   ||case when l_data_type in ('NUMBER','FLOAT') then 'NUMBER_FIELD'
                                      when l_data_type like '%CHAR%' or l_data_type like '%TIMESTAMP%' then 'TEXT_FIELD'
                                      when l_data_type = 'DATE' then 'DATE_PICKER_APEX'
                                      when l_data_type in ('CLOB') then 'TEXTAREA'
                                else 'TEXT_FIELD'
                                end ; 
    -- set p_is_required based on nullable value
    if l_is_nullable = 'N' then
      l_is_required := true;
    else
      l_is_required := false;
    end if;

    l_array.extend;
    -- we add the generated IDs into an array too handle the IG Report Column mapping later
    l_array(l_array.last) := wwv_flow_id.next_val;

Now we need the mapping to the ITEM Types:


-- not that super fance code ever -- 

    if l_item_type = 'NATIVE_NUMBER_FIELD' then  
    wwv_flow_imp_page.create_region_column(
      p_id               => wwv_flow_imp.id( l_array(l_array.last)),
      p_name             => c.column_name,
      p_source_type      => 'DB_COLUMN',
      p_source_expression => c.column_name,
      p_data_type        => l_data_type,
      p_is_query_only    => false,
      p_item_type        => l_item_type,
      p_heading          => c.column_name,
      p_heading_alignment => 'RIGHT',
      p_display_sequence => l_display_sequence,
      p_value_alignment  => 'RIGHT',
      p_attribute_03     => 'left',
      p_attribute_04     => 'decimal',
      p_is_required      => l_is_required,
      p_enable_filter    => true,
      p_filter_is_required => false,
      p_filter_lov_type  => 'NONE',
      p_use_as_row_header => false,
      p_enable_sort_group => true,
      p_enable_control_break => true,
      p_enable_hide      => true,
      p_is_primary_key   => false,
      p_duplicate_value  => true,
      p_include_in_export => true
    );
    end if;

    if l_item_type = 'NATIVE_DATE_PICKER_APEX' then  
    wwv_flow_imp_page.create_region_column(
      p_id               => wwv_flow_imp.id( l_array(l_array.last)),
      p_name             => c.column_name,
      p_source_type      => 'DB_COLUMN',
      p_source_expression => c.column_name,
      p_data_type        => l_data_type,
      p_is_query_only    => false,
      p_item_type        => l_item_type,
      p_heading          => c.column_name,
      p_heading_alignment=>'LEFT',
      p_display_sequence => l_display_sequence,
      p_value_alignment=>'LEFT',
      p_attribute_01=>'N',
      p_attribute_02=>'POPUP',
      p_attribute_03=>'NONE',
      p_attribute_06=>'NONE',
      p_attribute_09=>'N',
      p_attribute_11=>'Y',
      p_is_required      => l_is_required,
      p_enable_filter=>true,
      p_filter_is_required=>false,
      p_filter_date_ranges=>'ALL',
      p_filter_lov_type=>'DISTINCT',
      p_use_as_row_header=>false,
      p_enable_sort_group=>true,
      p_enable_control_break=>true,
      p_enable_hide=>true,
      p_is_primary_key=>false,
      p_duplicate_value=>true,
      p_include_in_export=>true
    );
    end if;

    if l_item_type = 'NATIVE_TEXTAREA' then  
    wwv_flow_imp_page.create_region_column(
      p_id               => wwv_flow_imp.id( l_array(l_array.last)),
      p_name             => c.column_name,
      p_source_type      => 'DB_COLUMN',
      p_source_expression => c.column_name,
      p_data_type        => l_data_type,
      p_is_query_only    => false,
      p_item_type        => l_item_type,
      p_heading          => c.column_name,
      p_heading_alignment=>'LEFT',
      p_display_sequence => l_display_sequence,
      p_value_alignment=>'LEFT',
      p_attribute_01=>'Y',
      p_attribute_02=>'N',
      p_attribute_03=>'N',
      p_attribute_04=>'BOTH',
      p_is_required=>l_is_required,
      p_max_length=>2000,
      p_enable_filter=>true,
      p_filter_operators=>'C:S:CASE_INSENSITIVE:REGEXP',
      p_filter_is_required=>false,
      p_filter_text_case=>'MIXED',
      p_filter_lov_type=>'NONE',
      p_use_as_row_header=>false,
      p_enable_sort_group=>false,
      p_enable_hide=>true,
      p_is_primary_key=>false,
      p_duplicate_value=>true,
      p_include_in_export=>true
    );
    end if;

    if l_item_type = 'NATIVE_TEXT_FIELD' then  
    wwv_flow_imp_page.create_region_column(
      p_id               => wwv_flow_imp.id( l_array(l_array.last)),
      p_name             => c.column_name,
      p_source_type      => 'DB_COLUMN',
      p_source_expression => c.column_name,
      p_data_type        => l_data_type,
      p_is_query_only    => false,
      p_item_type        => l_item_type,
      p_heading          => c.column_name,
      p_heading_alignment=>'LEFT',
      p_display_sequence => l_display_sequence,
      p_value_alignment=>'LEFT',
      p_attribute_05=>'BOTH',
      p_is_required=>l_is_required,
      p_max_length=>2000,
      p_enable_filter=>true,
      p_filter_operators=>'C:S:CASE_INSENSITIVE:REGEXP',
      p_filter_is_required=>false,
      p_filter_text_case=>'MIXED',
      p_filter_lov_type=>'DISTINCT',
      p_use_as_row_header=>false,
      p_enable_sort_group=>true,
      p_enable_hide=>true,
      p_is_primary_key=>false,
      p_duplicate_value=>true,
      p_include_in_export=>true
    );
    end if;
  end loop;
l_irgrid_id := wwv_flow_id.next_val;

Time for the Interactive Grid Component:


-- now we can create the interactive grid component
wwv_flow_imp_page.create_interactive_grid(
 p_id=>wwv_flow_imp.id(l_irgrid_id)
,p_internal_uid=>l_irgrid_id
,p_is_editable=>true
,p_edit_operations=>'i:u:d'
,p_lost_update_check_type=>'VALUES'
,p_add_row_if_empty=>true
,p_submit_checked_rows=>false
,p_lazy_loading=>false
,p_requires_filter=>false
,p_select_first_row=>true
,p_fixed_row_height=>true
,p_pagination_type=>'SCROLL'
,p_show_total_row_count=>true
,p_show_toolbar=>true
,p_enable_save_public_report=>false
,p_enable_subscriptions=>true
,p_enable_flashback=>true
,p_define_chart_view=>true
,p_enable_download=>true
,p_download_formats=>'CSV:HTML:XLSX:PDF'
,p_enable_mail_download=>true
,p_fixed_header=>'PAGE'
,p_show_icon_view=>false
,p_show_detail_view=>false
);
l_irgrid_rep_id := wwv_flow_id.next_val;

-- each interactive grid also need an ig report 
-- for correct working

wwv_flow_imp_page.create_ig_report(
 p_id=>wwv_flow_imp.id(l_irgrid_rep_id)
,p_interactive_grid_id=>wwv_flow_imp.id(l_irgrid_id)
,p_static_id=>to_number(to_char(sysdate,'ddmmhh24miss'))
,p_name=>to_char(sysdate,'ddmmhh24miss')
,p_type=>'PRIMARY'
,p_default_view=>'GRID'
,p_show_row_number=>false
,p_settings_area_expanded=>true
);
l_irgrid_rep_view_id := wwv_flow_id.next_val;
wwv_flow_imp_page.create_ig_report_view(
 p_id=>wwv_flow_imp.id(l_irgrid_rep_view_id)
,p_report_id=>wwv_flow_imp.id(l_irgrid_rep_id)
,p_view_type=>'GRID'
,p_srv_exclude_null_values=>false
,p_srv_only_display_columns=>true
,p_edit_mode=>false
);
wwv_flow_imp_page.create_ig_report_column(
 p_id=>wwv_flow_imp.id(wwv_flow_id.next_val)
,p_view_id=>wwv_flow_imp.id(l_irgrid_rep_view_id)
,p_display_seq=>0
,p_column_id=>wwv_flow_imp.id(l_rowact_id)
,p_is_visible=>true
,p_is_frozen=>false
);
wwv_flow_imp_page.create_ig_report_column(
 p_id=>wwv_flow_imp.id(wwv_flow_id.next_val)
,p_view_id=>wwv_flow_imp.id(l_irgrid_rep_view_id)
,p_display_seq=>1
,p_column_id=>wwv_flow_imp.id(l_rowid_id)
,p_is_visible=>true
,p_is_frozen=>false
);

-- each created RegionColumn needs an IG Report Column
-- here comes the array in the game ;-)
 for i in l_array.first..l_array.last loop
    wwv_flow_imp_page.create_ig_report_column(
      p_id          => wwv_flow_imp.id(wwv_flow_id.next_val),
      p_view_id     => wwv_flow_imp.id(l_irgrid_rep_view_id),
      p_display_seq => i+2,
      p_column_id   => wwv_flow_imp.id(l_array(i)),
      p_is_visible  => true,
      p_is_frozen   => false
    );
  end loop;

Finish it with the PAGE Process:


-- now just add the Page Process for DML Handling within the Grid
wwv_flow_imp_page.create_page_process(
 p_id=>wwv_flow_imp.id(wwv_flow_id.next_val)
,p_process_sequence=>10
,p_process_point=>'AFTER_SUBMIT'
,p_region_id=>wwv_flow_imp.id(l_region_id)
,p_process_type=>'NATIVE_IG_DML'
,p_process_name=>'new - save interactive grid data'
,p_attribute_01=>'REGION_SOURCE'
,p_attribute_05=>'Y'
,p_attribute_06=>'Y'
,p_attribute_08=>'Y'
,p_error_display_location=>'INLINE_IN_NOTIFICATION'
,p_internal_uid=>wwv_flow_id.next_val
);
end;

That's it :-) Now add select Lists and an Button in your App with dynamic Action to call the procedure and your are fine..

But Enable: "Runtime API Usage" for this App also!

Happy trying!