Skip to Main Content

Breadcrumb

Examples

Based on a SQL Query

In most cases the file(s) will come from a SQL query, which should look like this:


select file_name    as file_name
     , mime_type    as file_mime_type
     , blob_content as file_content_blob
  from some_table

If there is only one file in the result set, by default it will be downloaded as is. You can however enforce zipping also on single files. Take the following example with the 'Always Zip' option turned off and on.


select 'sample_image.png' as file_name
     , mime_type          as file_mime_type
     , file_content       as file_content_blob
  from apex_application_static_files
 where application_id = :APP_ID
   and file_name = 'images/avatar2.png'

Multiple files will always be zipped.


select file_name    as file_name
     , mime_type    as file_mime_type
     , file_content as file_content_blob
  from apex_application_static_files
 where application_id = :APP_ID

BLOBs *and* CLOBs

Sometimes you have files stored as VARCHAR2 or CLOB, and not as binary data. This plug-in can handle both. Instead of providing a file_content_blob column, provide a file_content_clob column. In such cases you can simply define the mime_type as text/plain.


select 'test.txt'     as file_name
     , 'text/plain'   as file_mime_type
     , 'Hello World!' as file_content_clob
  from dual

You can also combine BLOBs and CLOBs! Simply provide both columns, while only populating one of them. The plug-in will always choose the non-empty column.


select 'sample_image.png' as file_name
     , mime_type          as file_mime_type
     , file_content       as file_content_blob
     , null               as file_content_clob
  from apex_application_static_files
 where application_id = :APP_ID
   and file_name = 'images/avatar2.png'

 union all

select 'test.txt'
     , 'text/plain'
     , null
     , 'Hello World!'
  from dual

Based on PL/SQL Code

The files can also be compiled procedurally by adding them to a collection in a PL/SQL code block.

Simply add all files to the UC_DOWNLOAD_FILES collection. This special collection will be created and removed accordingly by the plug-in. Just make sure to provide parameters p_c001 for the file name, p_c002 for the mime type, and p_blob001 or p_clob001 for the content.


apex_collection.add_member
    ( p_collection_name => 'UC_DOWNLOAD_FILES'
    , p_c001            => 'README.md'
    , p_c002            => 'text/plain'
    , p_clob001         => 'This zip contains *all* application files!'
    );

for f in (
    select *
      from apex_application_static_files
     where application_id = :APP_ID
) loop
    apex_collection.add_member
        ( p_collection_name => 'UC_DOWNLOAD_FILES'
        , p_c001            => f.file_name
        , p_c002            => f.mime_type
        , p_blob001         => f.file_content
        );
end loop;

-- pro tip: you can override the zip file name by assigning it to the apex_application.g_x01 global variable
apex_application.g_x01 := 'all_files.zip';

Creating Subdirectories

To create subdirectories in your zip structure, prepend them to the file name as follows:


apex_collection.add_member
    ( p_collection_name => 'UC_DOWNLOAD_FILES'
    , p_c001            => 'file1.txt'
    , p_c002            => 'text/plain'
    , p_clob001         => 'I''m in the root.'
    );

apex_collection.add_member
    ( p_collection_name => 'UC_DOWNLOAD_FILES'
    , p_c001            => 'hello/world/file2.txt'
    , p_c002            => 'text/plain'
    , p_clob001         => 'I''m two levels down.'
    );

apex_collection.add_member
    ( p_collection_name => 'UC_DOWNLOAD_FILES'
    , p_c001            => '1/2/3/4/5/6/7/8/9/file3.txt'
    , p_c002            => 'text/plain'
    , p_clob001         => 'Hello? Anybody there?'
    );

Advanced: Download Application Components

This plug-in works great in combination with the apex_export API. In the following example we create on export of all the plug-ins included in this application.


declare
    l_files      apex_t_export_files;
    l_components apex_t_varchar2;
    
    l_set_env varchar2(32767);
    l_end_env varchar2(32767);
    
    CRLF constant varchar2(4) := chr(13) || chr(10);
begin
    select 'PLUGIN:' || id
      bulk collect into l_components
      from apex_appl_export_comps
     where application_id = :APP_ID
       and type_name = 'PLUGIN'
       and name like 'UC - %';

    -- export application
    -- we can speed things up by only exporting the needed components
    l_files := apex_export.get_application
        ( p_application_id => :APP_ID
        , p_split          => true
        , p_components     => l_components
        );
    
    -- individual component files do not contain the necessary start and end environment scripts
    -- so in this case we must fetch, prepend and append them ourselves
    select contents
      into l_set_env
      from table(l_files)
     where name like '%set_environment.sql';

    select contents
      into l_end_env
      from table(l_files)
     where name like '%end_environment.sql';
    
    for idx in 1 .. l_files.count
    loop
        if l_files(idx).name like '%/plugins/%'
        then
            -- file names are in the form application/shared_components/plugins/dynamic_action/...
            -- performing a substr to only use the actual plug-in name. i.e everything after the last slash
            apex_collection.add_member
                ( p_collection_name => 'UC_DOWNLOAD_FILES'
                , p_c001            => substr(l_files(idx).name, instr(l_files(idx).name, '/', -1) + 1)
                , p_c002            => 'text/plain'
                , p_clob001         => l_set_env || CRLF || l_files(idx).contents || CRLF || l_end_env
                );
        end if;
    end loop;
end;

FAQ

  • Why would I want to locally refresh my content, and not load it from the server?

    The reason you would do this is for performance and in the situation where any values that are referenced in the template are located on the page. So you actually don't need to contact the server. User profiles are one situation where this is useful, others are live previews of data that you are changing. By not contacting the server you are saving a lot of overhead and the refresh of content itself is much faster.