M
Many times I create an interactive grid not just on a simple table, but on a more complex view with transformed rows/columns (pivot/unpivot) with the need to store data in multiple tables on submit. I face similar difficulties when handling complex forms. Then I have to create a custom procedure to handle the grid/form submit and then pass columns from grid (or items from form) to this procedure call. That is a lot of tedious work to write these handlers over and over again.
Check this GEN package, it will generate all of this code for you and with nice formatting. You can easily customize this to fit your needs and style.
Generating the procedure
Imagine you have this table and you have more complex view based on this table.
CREATE TABLE plan_certifications ( cert_id VARCHAR2(20) NOT NULL, cert_name VARCHAR2(100) NOT NULL, path_id VARCHAR2(30), -- exam_page_link VARCHAR2(256), credly_link VARCHAR2(256), study_link VARCHAR2(256), study_hours VARCHAR2(10), -- questions NUMBER(4), minutes NUMBER(4), pass_ratio NUMBER(4), price NUMBER(8), -- CONSTRAINT pk_plan_certifications PRIMARY KEY (cert_id), -- CONSTRAINT fk_plan_certifications_path FOREIGN KEY (path_id) REFERENCES plan_paths (path_id) );
When you save your grid or form you want to store submitted data back to this table. So you need that custom PL/SQL processing handler. From now on, you can run this:
DECLARE in_table_name user_tables.table_name%TYPE := 'PLAN_CERTIFICATIONS'; BEGIN gen.create_handler ( in_table_name => in_table_name, in_target_table => NULL, in_proc_prefix => 'save_' ); END; /
And it will generate this code for you:
PROCEDURE save_plan_certifications ( in_action CHAR, in_cert_id plan_certifications.cert_id%TYPE, in_cert_name plan_certifications.cert_name%TYPE, in_path_id plan_certifications.path_id%TYPE := NULL, in_exam_page_link plan_certifications.exam_page_link%TYPE := NULL, in_questions plan_certifications.questions%TYPE := NULL, in_minutes plan_certifications.minutes%TYPE := NULL, in_pass_ratio plan_certifications.pass_ratio%TYPE := NULL, in_price plan_certifications.price%TYPE := NULL, in_credly_link plan_certifications.credly_link%TYPE := NULL, in_study_link plan_certifications.study_link%TYPE := NULL, in_study_hours plan_certifications.study_hours%TYPE := NULL ) AS rec plan_certifications%ROWTYPE; BEGIN tree.log_module(); -- rec.cert_id := in_cert_id; rec.cert_name := in_cert_name; rec.path_id := in_path_id; rec.exam_page_link := in_exam_page_link; rec.questions := in_questions; rec.minutes := in_minutes; rec.pass_ratio := in_pass_ratio; rec.price := in_price; rec.credly_link := in_credly_link; rec.study_link := in_study_link; rec.study_hours := in_study_hours; -- DELETE FROM plan_certifications t WHERE t.cert_id = rec.cert_id; -- BEGIN INSERT INTO plan_certifications VALUES rec; EXCEPTION WHEN DUP_VAL_ON_INDEX THEN UPDATE plan_certifications t SET ROW = rec WHERE t.cert_id = rec.cert_id; END; -- tree.update_timer(); EXCEPTION WHEN tree.app_exception THEN RAISE; WHEN OTHERS THEN tree.raise_error(); END;
The in_target_table is very useful if you want to generate different inputs to the procedure (based on in_table_name object, typically view) but the DML itself is done on in_target_table object (typically target table or multiple tables).
You can and should customize this code, for example remove the tree package references I use for code instrumenting, logging and exception handling. Maybe you dont need the delete statement. Maybe you need this as a function returning assigned id from a sequence back to the grid. Maybe you dont need the action argument (for passing :APEX$ROW_STATUS)... This is up to you.
Generating the call
Run this script to generate caller for this procedure (you can pass package name too or even a table/view name):
DECLARE in_object_name user_objects.object_name%TYPE := 'SAVE_PLAN_CERTIFICATIONS'; BEGIN gen.handler_call ( in_procedure_name => in_object_name ); END; /
And check the generated code:
save_plan_certifications ( in_action => :APEX$ROW_STATUS in_cert_id => :CERT_ID, in_cert_name => :CERT_NAME, in_path_id => :PATH_ID, in_exam_page_link => :EXAM_PAGE_LINK, in_questions => :QUESTIONS, in_minutes => :MINUTES, in_pass_ratio => :PASS_RATIO, in_price => :PRICE, in_credly_link => :CREDLY_LINK, in_study_link => :STUDY_LINK, in_study_hours => :STUDY_HOURS );
If you pass page_id, you can generate call for processing form.
DECLARE in_object_name user_objects.object_name%TYPE := 'SAVE_PLAN_CERTIFICATIONS'; BEGIN gen.handler_call ( in_procedure_name => in_object_name, in_app_id => NULL, in_page_id => 100 -- NULL for grid, page_id for forms ); END; /
And check the generated code:
save_plan_certifications ( in_action => :APEX$ROW_STATUS in_cert_id => :P100_CERT_ID, in_cert_name => :P100_CERT_NAME, in_path_id => :P100_PATH_ID, in_exam_page_link => :P100_EXAM_PAGE_LINK, in_questions => :P100_QUESTIONS, in_minutes => :P100_MINUTES, in_pass_ratio => :P100_PASS_RATIO, in_price => :P100_PRICE, in_credly_link => :P100_CREDLY_LINK, in_study_link => :P100_STUDY_LINK, in_study_hours => :P100_STUDY_HOURS );
How to generate views you can check in View generator article.
Your life as APEX developer should be now a way easier. You are welcome.
This provides a nice template for those times when the standard Apex DML processes won't quite cut it.
ReplyDeleteThanks!