My first rule of APEX development is put as much into database stored procedures as possible. This keeps your application code cleaner, more efficient and makes maintenance easier. As mentioned earlier, this project is built on a manual tabular form based on a collection. The SQL to create the collection is actually very simple, but the SQL to generate the tabular form itself is not. Rather than trying to write the statement for the tabular form within the region, I opted to create a database function that returns a SQL statement string the region can use. This function is contained within a package that the application uses.
When an instance of the questionnaire is generated, I automatically build all the records in the answers table for the questionnaire. I then created a view to join the questionnaire table with the answers table, which is what the collection is generated from.
FUNCTION get_tabular_form_sql RETURN VARCHAR2 IS
v_sql VARCHAR2(10000);
BEGIN
v_sql := v_sql || 'select apex_item.display_and_save(1, C002) SEQUENCE_NO, ';
v_sql := v_sql || 'case ';
v_sql := v_sql || 'when C014 = '||''''||'Y'||''''||' then ';
v_sql := v_sql || ' apex_item.textarea(2, C004, 4, 100, '||''''||'readonly="readonly" style="border: none"'||''''||','||''''||'f02_'||''''||'||C013, NULL) ';
v_sql := v_sql || 'else ';
v_sql := v_sql || ' apex_item.display_and_save(2, C004) ';
v_sql := v_sql || 'end as QUESTION_TEXT, ';
v_sql := v_sql || 'case ';
v_sql := v_sql || 'when C007 is null then ';
v_sql := v_sql || ' case C006';
v_sql := v_sql || ' when '||''''||'D'||''''||' then ';
v_sql := v_sql || ' apex_item.hidden (3, C012, NULL, '||''''||'f03_'||''''||'||C013) || apex_item.date_popup(4, NULL, C012, '||''''||'DD-MON-RRRR'||''''||', 20, 2000, '||''''||'onChange="dateField(this)"'||''''||', '||''''||'f04_'||''''||'||C013, NULL) ';
v_sql := v_sql || ' when '||''''||'N'||''''||' then ';
v_sql := v_sql || ' apex_item.hidden (3, C012, NULL, '||''''||'f03_'||''''||'||C013) || apex_item.text(4, C012, NULL, 1500, '||''''||'onChange="Text(this)"'||''''||', '||''''||'f04_'||''''||'||C013, NULL) ';
v_sql := v_sql || ' else ';
v_sql := v_sql || ' apex_item.hidden (3, C012, NULL, '||''''||'f03_'||''''||'||C013) || apex_item.textarea(4, C012, 2, 30, '||''''||'maxlength="1500" onChange="Text(this)"'||''''||', '||''''||'f04_'||''''||'||C013, NULL) ';
v_sql := v_sql || ' end ';
v_sql := v_sql || 'else ';
v_sql := v_sql || ' case ';
v_sql := v_sql || ' when nvl(C009, '||''''||'N'||''''||') = '||''''||'Y'||''''||' then ';
v_sql := v_sql || ' apex_item.hidden (3, C012, NULL, '||''''||'f03_'||''''||'||C013) || apex_item.select_list_from_query(4, NULL, C007, '||''''||'multiple="multiple" onChange="selectList(this);"'||''''||', '||''''||'NO'||''''||', NULL, NULL, '||''''||'f04_'||''''||'||C013, NULL, '||''''||'NO'||''''||') ';
v_sql := v_sql || ' when nvl(C008, '||''''||'N'||''''||') = '||''''||'Y'||''''||' then ';
v_sql := v_sql || ' apex_item.hidden (3, C012, NULL, '||''''||'f03_'||''''||'||C013) || apex_item.popup_from_query(4, C012, C007, NULL, NULL, NULL, NULL, NULL, '||''''||'readonly="readonly" onChange="popUp(this);"'||''''||', '||''''||'YES'||''''||', NULL, '||''''||'f04_'||''''||'||C013) ';
v_sql := v_sql || ' else ';
v_sql := v_sql || ' apex_item.hidden (3, C012, NULL, '||''''||'f03_'||''''||'||C013) || apex_item.select_list_from_query(4, C012, C007, '||''''||'onChange="selectList(this)"'||''''||', '||''''||'YES'||''''||', NULL, '||''''||'- Select -'||''''||', '||''''||'f04_'||''''||'||C013, NULL, '||''''||'YES'||''''||') ';
v_sql := v_sql || ' end ';
v_sql := v_sql || 'end as ANSWER,';
v_sql := v_sql || 'case ';
v_sql := v_sql || 'when nvl(C009, '||''''||'N'||''''||') = '||''''||'Y'||''''||' then ';
v_sql := v_sql || ' apex_item.textarea(6, REPLACE(C012, '||''''||':'||''''||', CHR (13)||CHR (10)), 4, 40, '||''''||'readonly="readonly" style="border: none" class="selected"'||''''||') ';
v_sql := v_sql || 'else ';
v_sql := v_sql || ' apex_item.display_and_save(6, NULL) ';
v_sql := v_sql || 'end as SELECTED_VALUE, ';
v_sql := v_sql || 'apex_item.display_and_save(7, C005) NULLABLE, ';
v_sql := v_sql || 'apex_item.display_and_save(8, C013) ANSWER_ID ';
v_sql := v_sql || 'from apex_collections ';
v_sql := v_sql || 'where collection_name = '||''''||'QUESTIONNAIRE'||''''||' ';
v_sql := v_sql || 'order by seq_id ';
RETURN v_sql;
END get_tabular_form_sql;
Here are what the collection columns referenced in the SQL are:
- C002 – question sequence number
- C014 – this is a flag I calculated in the view that indicates whether the question text contains any carriage returns. If it does, we want to display the question in a read-only text area, otherwise display it in a text field.
- C004 – question text
- C007 – the LOV sql text
- C006 – the validation type
- C012 – the answer for the question from the answer table
- C013 – the primary key value from the answer table
- C009 – the multi-select LOV flag
- C008 – the large list flag
- C005 – the nullable flag
If you use APEX_COLLECTION.CREATE_COLLECTION_FROM_QUERY to build your collections, the order of the columns in your sql statement/view to create the collection should correspond to C0XX. However, a lot of times just to double check, I will create an interactive report region on my page to display the data in the collection that I later hide or delete from the page. This can be especially helpful if you generate a collection via APEX_COLLECTION.CREATE_COLLECTION_FROM_QUERY2 and have data in a mix of different column types.
In the next post, we will start working on the actual page itself and bring this all together.