Region plug-ins, as you may guess, allow you to create your own region types in APEX. Before APEX 4.0, if you wanted to create a “custom” region you would need to create a PL/SQL type region that would generate all the content for your custom region. Region plug-ins take a similar approach, and also provide an excellent declarative and supported framework for managing custom region types.
This chapter explains what region plug-ins do and don't cover, helps you build a region plug-in, and introduces AJAX functionalities. The plug-ins covered in previous chapters (item and dynamic action) also support AJAX functionality, so the AJAX content in this chapter is relevant to the previous two plug-in types as well.
Before developing a region plug-in with AJAX support, it's important to cover two things: the architecture of a region (i.e., the difference between the region itself and a region template) and AJAX in APEX. It is important to understand these topics before developing the region plug-in and adding in AJAX functionality.
The easiest way to explain the architecture of a region is by analyzing one. Figure 5-1 shows the My Form region that you created in the previous chapter. The content inside the dotted line is part of the region's body. The content outside the dotted line is part of the region template. Region plug-ins only generate content for the region body and they don't need to be concerned with the region template. The region template will wrap the region body with the standard display code, which is determined by the APEX developer.
Figure 5-1. Region body outline
Another way to view the difference between the region content and the template is to examine the region template. Region templates define the look and feel of the region. To view the region template
Figure 5-2. Templates link in Shared Components
In Figure 5-4, you'll notice that there are some HTML tags that wrap the content of the region. These are some special tags that APEX will replace with the appropriate region attributes such as #TITLE# and the button tags (#CLOSE#, #PREVIOUS#, … ,#HELP#). The #BODY# substitution string is what will be replaced with the content from the region plug-in.
Because the region plug-in handles only the “meat” of the region, region plug-ins need only to focus on what is necessary to be displayed as part of the region. You don't need to be concerned about styling the region, displaying the region buttons, and so on, since the region template handles all these elements.
At a very high level, AJAX allows the browser to send data to the server and receive a response back from it without having to submit the page. With respect to APEX, this means that you can send some data to APEX, run a block of PL/SQL code, and send data back to the browser without having to submit the page.
Note Wikipedia has an excellent description of AJAX at the following link: http://en.wikipedia.org/wiki/Ajax_(programming).
Prior to APEX 4.0, AJAX functions were handled through an On Demand application process. Though it is not required, building an AJAX function using the old (pre-APEX 4.0) method may help you understand and appreciate how the new plug-in AJAX function works. The following steps cover how to build and use a simple AJAX function the “old way.” This function will send the user's current value to APEX and return the value multiplied by two.
Note With APEX 4.0, manually creating AJAX functions is not required because the same functionality can easily be provided by a dynamic action of type PL/SQL. This example is solely to demonstrate how things work and is not a recommended practice for making AJAX calls in APEX 4.0 and higher.
Page Number: 30
Name:AJAX (Old)
HTML Region 1: My Region
Tab: AJAX(Old)
Item Type: Number Field
Item Name: P30_X
Button Name: Run AJAX
Button Alignment: Left
Action: Defined by Dynamic Action
If you run Page 30, it should now look like Figure 5-5.
In the code, apex_application.g_x01
is a value that will be passed from the AJAX JavaScript call to APEX. APEX supports up to ten values (apex_application.g_x01 .. apex_application.g_x10
) along with one clob value (apex_application.g_clob_01
).
The PL/SQL code “sends” a value back to the client's browser by printing the value with the htp.p
call. JavaScript will interpret this value as a string that will need to be converted accordingly.
In the JavaScript code below, you'll notice that the first line of code references the AJAX_DEMO application process.
The function addParam
defines the x01..x10
values that will set the PL/SQL apex_application.g_x01 .. apex_application.g_x10
variables as part of the AJAX request.
// Demo code for AJAX calls
var ajax = new htmldb_Get(null,$v('pFlowId'), 'APPLICATION_PROCESS=AJAX_DEMO',0);
// Value to send to PL/SQL code
// Note: this does not "submit" P30_X (that can be done but in another way)
ajax.addParam('x01', $v('P30_X'));
// Trigger AJAX call (will send POST to APEX)
var ajaxResult = ajax.get();
// Display the result in an alert window
window.alert(ajaxResult);
a.
b.
Note apex_application.g_x01..x10
variables are not associated with plugin.attribute_01..15
variables. They are submitted as part of every APEX request and a globally accessible (i.e., they're not specific to any particular object).
If you open the Console window, you'll see the POST request that the browser made to the server. Figure 5-10 shows the result from the POST request. From the console result, you can clearly see how the values are sent to the server. Figure 5-11 shows the response back from the server. As previously mentioned, this result is a simple string in JavaScript. If you need to do anything with it, you'll need to explicitly convert it to the correct data type. In this example, you'd need to convert it to a number.
Implementing AJAX functionality via a plug-in is much easier to do than the process just described. In the region plug-in example to follow, you will create an AJAX function and you should be able to see the common features with this example.
As with the other plug-ins, the first thing you should do is state your business requirements. This region plug-in should display the titles from an RSS feed. The plug-in should also implement some additional related functionality. The following is the complete list of requirements:
www.blogger.com
).The plug-in you are going to build will use some of the UTL_HTTP features in Oracle to obtain RSS feeds. HTTP access and other network services are potential security vulnerabilities, so their access is restricted in Oracle Database 11g and higher. You will need to enable network services and grant access to them in order to create the plug-in described next.
Note If running Oracle Database 10g or lower, you can skip this section. HTTP access is not restricted in those lower releases. If you are using apex.oracle.com as your server, then you won't be able to create this chapter's plug-in at all. That's because apex.oracle.com restricts UTL_HTTP access.
Think twice about enabling UTL_HTTP access in a production environment. Discuss such changes with your database administrator. Be sure that you do not violate any security policies. The example to follow is meant for demonstration purposes and should be thoroughly reviewed before implementation in a production environment.
The following are the assumptions made in describing the process of enabling the UTL_HTTP access needed by this chapter's example plug-in:
All the scripts are in the files included with this book. If your environment is not as described in the preceding list, you will need to modify the scripts before executing them. For example, you may need to substitute in the user name that you are using.
When you have the scripts ready for your environment, follow these steps to enable UTL_HTTP access:
v_user
to your username.
-- Run as SYSTEM or SYS
-- Creates a ACL with access to all domains and ports
-- Or leverages one that already exists
DECLARE
v_acl dba_network_acls.acl%TYPE;
v_user VARCHAR2(30) := 'APRESS'; -- *** CHANGE TO YOUR USER
v_cnt pls_integer;
BEGIN
v_user := upper(v_user);
-- Get current ACL (if it exists)
SELECT max(acl)
INTO v_acl
FROM dba_network_acls
WHERE host = '*'
AND lower_port IS NULL
AND upper_port IS NULL;
IF v_acl IS NULL THEN
-- No ACL exists. Create one
v_acl := 'apress_full_access.xml';
-- Create ACL with access to your user
dbms_network_acl_admin.create_acl (
acl => v_acl,
description => 'ACL Access for Apress demo',
principal => v_user,
is_grant => TRUE,
privilege => 'connect',
start_date => NULL,
end_date => NULL);
-- Grant access to ACL to all ports and ports
dbms_network_acl_admin.assign_acl (
acl => v_acl,
host => '*', -- This is the network that you have access to.
lower_port => NULL,
upper_port => NULL);
ELSE
-- ACL Exists, just need to give access to user (if applicable)
SELECT count(acl)
INTO v_cnt
FROM dba_network_acl_privileges
WHERE acl = v_acl
and principal = v_user;
IF v_cnt = 0 THEN
-- User needs to be granted
dbms_network_acl_admin.add_privilege(
acl => v_acl,
principal => v_user,
is_grant => true,
PRIVILEGE => 'connect'),
ELSE
-- User has access to network
-- Nothing to be done
NULL;
END IF;
END IF;
COMMIT;
END;
/
-- Test that user has network access now
-- Run as APRESS user
-- Determines if current user has access to external connections
-- Makes a simple connection to www.google.com on port 80
-- Result will be in DBMS_OUTPUT
DECLARE
v_connection utl_tcp.connection;
BEGIN
v_connection := utl_tcp.open_connection(remote_host => 'www.google.com', remote_port => 80);
utl_tcp.close_connection(v_connection);
dbms_output.put_line('Ok: Have Access'),
EXCEPTION
WHEN others THEN
IF sqlcode = -24247 THEN
-- ORA-24247: network access denied by access control list (ACL)
dbms_output.put_line('No ACL network access.'),
ELSE
dbms_output.put_line('Unknown Error: ' || sqlerrm);
END IF;
END;
/
If you see the message “Ok. Have access,” then all is well. Proceed with creating the example plug-in.
Now that the business requirements have been defined, you can start creating the region plug-in. Similar to the dynamic action plug-in example in the preceding chapter, some steps will not be covered on a step-by-step basis as they have already been covered previously.
To start building a region plug-in, create a new plug-in with the attributes listed below. Once you are finished, click the Create button to save the plug-in.
Internal Name: COM.CLARIFIT.APEXPLUGIN.RSS_READER
Type: Region
Next, create a directory to store and reference external web files. This will allow you to easily make modifications as you build your plug-in. Here are the steps:
Scroll down to the Standard Attributes section and check off the has "Escape Special Characters" Attribute option as shown in Figure 5-12. Click the Apply Changes button to save your change. In this plug-in, the Escape Special Characters attribute will be used to escape the RSS feed content (if enabled in region). The RSS feed URL will be defined by a custom attribute rather than the region source.
Since this plug-in does not require a traditional “Region Source,” such as an SQL query or some plain text, you do not need to check those boxes off. If you create a plug-in that requires the user to enter a SQL query as the region source, you can parse the query using the APEX_PLUGIN_UTIL.GET_DATA and APEX_PLUGIN_UTIL.GET_DATA2 function. For more information, please refer to the APEX API documentation.
The following are the custom attributes that the plug-in will use. These attributes are based on the requirements. Specify the attributes for the region using the dialog shown in Figure 5-14, following the list.
Attribute:1
Label: RSS Type
Type: Select List
Required: Yes
Default Value: Blogger
Display Value: Blogger
Return Value: Blogger
For now, Blogger will be the only support RSS feed. If you want to support additional RSS feeds, you can modify this list and the code accordingly.
Attribute: 2
Label: RSS URL
Type: Text
Required: Yes
Display Width: 50
Attribute: 3
Label: Max Rows
Type: Select List
Required: Yes
Default: 5
Display Value: 5
Return Value: 5
Display Value: 10
Return Value: 10
Display Value: 15
Return Value: 15
Attribute: 4
Label: Modal Width(controls width of the modal window)
Type: Integer
Required: Yes
Display Width: 3
Maximum Width: 4
Default Value: 700
Attribute: 5
Label: Modal Height(controls height of the modal window)
Type: Integer
Required: Yes
Display Width: 3
Maximum Width: 4
Default Value: 400
You should feel comfortable with and understand the impacts of adding and modifying custom attributes by now. The only difference that you'll experience with region plug-in custom attributes is how they are displayed on the region's edit page.
Instead of being displayed in the usual Settings section, as shown in Figure 5-13, a new tab called Region Attributes contains the custom attributes. Figure 5-14 shows this new tab when modifying a region that is a plug-in region with custom attributes.
Before creating the render and AJAX functions, you should set up a test page to help view your changes. The following steps create a test page and a region that uses the new region plug-in:
Just to clarify, there's no such thing as a Page plug-in. In this case, plug-in refers to a region plug-in.
Page Number: 40
Page Name: RSS Reader
Region Name: RSS Reader
(Note: You can use any Blogger URL by replacing www.talkapex.com
with your URL.)
At this point, if you run Page 40, it will display an error message since you have not defined or created a render function, as shown in Figure 5-16. The next section will define and create the region render PL/SQL function.
Just like the other plug-ins, this plug-in will store its PL/SQL code in the pkg_apress_plugins package. To start, get the region plug-in render function template and paste it into pkg_apress_plugins spec. Name the render function as f_render_rss_reader. Your pkg_apress_plugins package specification should now look like Figure 5-17.
Hint To get the render function spec template, click render function label and view the help file.
The next thing you'll need to do is register the render function with the plug-in. Edit the plug-in and scroll down to the Callbacks region. Set the Render Function Name to pkg_apress_plugins.f_render_rss_reader, as shown in Figure 5-18.
Modify the package body for pkg_apress_plugins and add the following code at the bottom of code.
341 …
342
343 FUNCTION f_render_rss_reader(
344 p_region IN apex_plugin.t_region,
345 p_plugin IN apex_plugin.t_plugin,
346 p_is_printer_friendly IN boolean )
347 RETURN apex_plugin.t_region_render_result
348 AS
349 -- Region Plugin Attributes
350 v_rss_type apex_application_page_regions.attribute_01%type := p_region.attribute_01; --
blogger (can add more types)
351 v_rss_url apex_application_page_regions.attribute_01%type := p_region.attribute_02;
352 v_max_row_nums pls_integer := to_number(p_region.attribute_03);
353 v_dialog_width apex_application_page_regions.attribute_01%type := p_region.attribute_04;
354 v_dialog_height apex_application_page_regions.attribute_01%type :=
p_region.attribute_05;
355
356 -- Other
357 v_html VARCHAR2(4000); -- Used for temp HTML
358 v_div_id VARCHAR2(255) := 'clarifitRSSReader_' || p_region.id; -- Used for dialog window
placeholder
359 v_rss_xml_namespace VARCHAR2(255);
360
361 -- Return
362 v_return apex_plugin.t_region_render_result;
363
364 -- Procedures
365 PROCEDURE sp_display_rss_title(
366 p_rss_id IN VARCHAR2,
367 p_rss_title IN VARCHAR2,
368 p_rn IN pls_integer, -- Current row number
369 p_row_cnt IN pls_integer -- Total number of rows in the query
370 )
371 AS
372 BEGIN
373 -- Handle first row items
374 IF p_rn = 1 THEN
375 sys.htp.p('<table>'),
376 END IF; -- First row
377
378 v_html := ('<tr><td><a
href="javascript:$.clarifitRssReader.showContentModal(''%RSS_ID%'',
clarifitRssReaderVals.R%REGION_ID%);">%TITLE%</a></td></tr>'),
379 v_html := REPLACE(v_html, '%TITLE%', p_rss_title);
380 v_html := replace(v_html, '%RSS_ID%', p_rss_id);
381 v_html := REPLACE(v_html, '%REGION_ID%', p_region.id);
382
383 sys.htp.p(v_html);
384
385 -- If Last row close table
386 IF p_rn = p_row_cnt THEN
387 sys.htp.p('</table>'),
388 END IF;
389
390 END sp_display_rss_title;
391
392 BEGIN
393
394 -- Debug information (if app is being run in debug mode)
395 IF apex_application.g_debug THEN
396 apex_plugin_util.debug_region (
397 p_plugin => p_plugin,
398 p_region => p_region,
399 p_is_printer_friendly => p_is_printer_friendly);
400 END IF;
401
402 IF NOT p_is_printer_friendly THEN
403 -- Load JavaSript Files
404 apex_javascript.add_library (p_name => '$console_wrapper', p_directory =>
p_plugin.file_prefix, p_version=> '_1.0.3'), -- Load Console Wrapper for debugging
405 apex_javascript.add_library (p_name => 'clarifitRSSReader', p_directory =>
p_plugin.file_prefix, p_version=> '_1.0.0'), -- Load Console Wrapper for debugging
406
407 -- CSS Properties
408 apex_css.add (
409 p_css => '
410 .clarifitRssReader-label {font-weight: bold}
411 .clarifitRssReader-author {font-style: italic}
412 .clarifitRssReader-link {font-style: italic}
413 ',
414 p_key => 'clarifitRssReader'),
415
416 -- Initial JS. Only run if not in printer friendly mode
417 sys.htp.p('<div id="' || v_div_id || '"></div>'), -- Used for dialog placeholder
418
419 -- Set JavaScript global variables that will be used to handle display options
420 sys.htp.p('<script type="text/javascript">(function($){'),
421 -- Only run this code once so as not to overwrite the global variable
422 apex_javascript.add_inline_code (
423 p_code => 'var clarifitRssReaderVals = {}',
424 p_key => 'clarifitRssReaderVals'),
425
426 -- Extend feature allows you to append variables to JSON object
427 v_html := '
428 $.extend(clarifitRssReaderVals,
429 {"R%REGION_ID%" : {
430 %AJAX_IDENTIFIER%
431 %RSS_TYPE%
432 %IMAGE_PREFIX%
433 %DIALOG_WIDTH%
434 %DIALOG_HEIGHT%
435 %DIV_ID_END_ELEMENT%
436 }});';
437
438 v_html := REPLACE(v_html, '%REGION_ID%', p_region.id);
439 v_html := REPLACE (v_html, '%AJAX_IDENTIFIER%',
apex_javascript.add_attribute('ajaxIdentifier', apex_plugin.get_ajax_identifier));
440 v_html := REPLACE (v_html, '%RSS_TYPE%', apex_javascript.add_attribute('rssType',
v_rss_type));
441 v_html := REPLACE (v_html, '%IMAGE_PREFIX%',
apex_javascript.add_attribute('imagePrefix', apex_application.g_image_prefix));
442 v_html := REPLACE (v_html, '%DIALOG_WIDTH%',
apex_javascript.add_attribute('dialogWidth', sys.htf.escape_sc(v_dialog_width)));
443 v_html := REPLACE (v_html, '%DIALOG_HEIGHT%',
apex_javascript.add_attribute('dialogHeight', sys.htf.escape_sc(v_dialog_height)));
444 v_html := REPLACE (v_html, '%DIV_ID_END_ELEMENT%',
apex_javascript.add_attribute('divId', v_div_id, FALSE, FALSE));
445
446 apex_javascript.add_inline_code (p_code => v_html);
447
448 sys.htp.p('})(apex.jQuery);</script>'),
449 END IF; -- printer friendly
450
451 -- For each type
452 IF v_rss_type = 'blogger' THEN
453 v_rss_xml_namespace := 'http://www.w3.org/2005/Atom';
454
455 FOR x IN (
456 SELECT id, title, rownum rn, count(1) over() row_cnt
457 FROM xmltable(
458 XMLNAMESPACES(DEFAULT 'http://www.w3.org/2005/Atom'),
459 '*' passing httpuritype
(v_rss_url).getxml().EXTRACT('//feed/entry','xmlns="http://www.w3.org/2005/Atom"')
460 COLUMNS id VARCHAR2(4000) PATH 'id',
461 title VARCHAR2(48) PATH 'title',
462 author VARCHAR2(1000) path 'author/name'
463 )
464 WHERE ROWNUM <= v_max_row_nums
465 ) loop
466
467 sp_display_rss_title(
468 p_rss_id => x.ID,
469 p_rss_title => x.title,
470 p_rn => x.rn,
471 p_row_cnt => x.row_cnt);
472 END loop;
473
474 -- Add additional support for RSS feeds here.
475 ELSE
476 -- Unknown RSS type
477 sys.htp.p('Error: unknown RSS type'),
478 END IF;
479
480 -- Return
481 RETURN v_return;
482
483 END f_render_rss_reader;
484
485 …
The following is a description of the preceding code that is keyed to the line numbers:
349-354: Define the plug-in's attributes and use meaningful variables. All the attributes are strings that need to be explicitly converted to appropriate PL/SQL data types when applicable.
365-390: Reusable internal procedure to display each of the RSS titles in a table format. The titles include links that will trigger some JavaScript code to display the RSS content in a modal window. This will be used to make an AJAX call
395-400: Minimal debug information. Always include some debug information in your plug-in.
402-449: Contains code that is only applicable in normal display mode. The code is there to support the display of the RSS content in a modal window. If the page is run in print mode, then this code is not required.
Line 404 references a JavaScript file that has yet to be created. You will create this file when implementing the AJAX support.
When developing plug-ins with AJAX support, you may tend to initially focus on getting the region to display what you want to display and then add the additional code.439: This line is very important for AJAX calls. The key component is the reference to the
apex_plugin.get_ajax_identifier
function.
In the AJAX example at the beginning of the chapter, the JavaScript code referenced the PL/SQL code to run by identifying the application process AJAX_DEMO. Plug-ins need a similar identifier/reference. The APEX plug-in APIs make things simple so that you don't need to worry about naming this identifier. Theapex_plugin.get_ajax_identifier
function provides a unique name.451-472: Handle RSS type-specific code to obtain the RSS title and some meta data. Lines 475-476 handle what happens if an unknown RSS type is defined. In this case, it displays a simple error message. How you handle errors is entirely up to you, depending on your business needs. You can use a “soft” error message (as in this case) or a more “harsh” error messages (i.e., raise an application error).
If you run Page 40, it should look like Figure 5-19. Note the values for the RSS feed may vary depending on the URL. If you click on any of the titles, nothing happens as the JavaScript still hasn't been added and the AJAX function has not been defined. This will be covered in the next section.
If you discard the JavaScript specific code, the code for this plug-in is pretty simple and straight forward. The primary responsibility for a region plug-in is to display some content. You don't need to be concerned about items and buttons attached to the region as they are handled as part of the standard APEX region process.
This section will add AJAX support to the plug-in. Though this example is for a region plug-in, the concept is the same for other plug-ins that support AJAX calls. Because AJAX involves both server side code (handled by PL/SQL for APEX) and JavaScript code, this section will cover both sets of code.
When working with code that links PL/SQL with JavaScript (and vice versa), it can be difficult to determine whether to start with the client side code (JavaScript) or the server side code (PL/SQL). When dealing with AJAX functions, it is sometimes easier to start with the JavaScript portion. Once the base JavaScript code is working, you start working on the server code. This is usually an iterative process, modifying both JavaScript and the PL/SQL code.
The JavaScript code will use the Console Wrapper JavaScript instrumentation package. To that end, copy $console_wrapper_1.0.3.js
into in c:wwwRSSReader
. Then create an empty file named clarifitRSSReader_1.0.0.js
in c:wwwRSSReader
. This is the same name that was used in the render function. Open the file and paste in the following code:
Note This JavaScript code is slightly different from the code used for the previous plug-ins. It does not use the jQuery UI Widget Factory framework.
01 (function($){
02
03 $.clarifitRssReader = (function(){
04 var that = {};
05
06 /**
07 * Display the RSS feed's content in a Dialog window
08 */
09 that.showContentModal = function(pRssId, pObj){
10 var scope = '$.clarifitRssReader.showContentModal';
11 $.console.groupCollapsed(scope);
12 $.console.logParams();
13
14 var $elem = $('#' + pObj.divId);
15
16 //Ensure default options
17 var defaultOptions = {
18 dialogWidth: 700,
19 dialogHeight: 400,
20 modal: true
21 };
22
23 pObj = $.extend(defaultOptions, pObj);
24
25 //Display Loading Message
26 $elem.html('<div style="text-align:center;"><img src="' + pObj.imagePrefix + 'ws/ajax-
loader.gif" style="display: block;margin-left: auto;margin-right: auto"></div>'),
27 $elem.dialog({
28 title: 'Loading...',
29 modal: pObj.modal
30 });
31
32 //Prep AJAX call to get HTML content
33 var ajax = new htmldb_Get(null,$v('pFlowId'), 'PLUGIN=' + pObj.ajaxIdentifier,0);
34 ajax.addParam('x01', pObj.rssType);
35 ajax.addParam('x02', pRssId);
36 var ajaxResult = ajax.get();
37
38 var json = $.parseJSON(ajaxResult);
39 $.console.log('json: ', json);
40
41 if (json.errorMsg == ''){
42 //No Error message, display content
43 //Modify content to include some additional information about the rss post
44 json.content = '<span class="clarifitRssReader-label">By</span>:<span
class="clarifitRssReader-author">' + json.author + '</span><br>' + '<span
class="clarifitRssReader-label">Link</span>: ' + '<a href="' + json.link + '" target="blank"
class="clarifitRssReader-link">' + json.link + '</a><br><br>' + json.content;
45
46 //Display in Modal window
47 $elem.dialog('close'), //close Loading messsage
48 $elem.html(json.content);
49 $elem.dialog({
50 title: json.title,
51 width: pObj.dialogWidth,
52 height: pObj.dialogHeight,
53 modal: pObj.modal
54 });
55 $.console.groupEnd(scope);
56 }
57 else {
58 //Error occurred
59 $elem.dialog('close'), //close Loading messsage
60 $elem.html('An error occurred loading RSS feed'),
61 $elem.dialog({
62 title: 'Error',
63 width: pObj.dialogWidth,
64 height: pObj.dialogHeight,
65 modal: pObj.modal
66 });
67 }//error message
68
69 };//showContentModal
70
71 return that;
72 })();//$clarifitRssReader
73
74 })(apex.jQuery);
The following is a description of the preceding code that is keyed to the line numbers:
1 & 74: Like other JavaScript code, this block of JavaScript explicitly defines the jQuery namespace but leverages the version of jQuery that is part of APEX.
26-30: Displays a “Loading...” message when the user clicks on a RSS title and is waiting for the server to respond.
32-36: This is what actually makes the AJAX call. This code is very similar to the example code covered in the Background > AJAX section at the beginning of this chapter. The major difference is that on line 33, instead of referencing an application process by APPLICATION_PROCESS, it references the plug-in process by PLUGIN. The name of the plug-in AJAX identifier is defined in the region's render function with a call to
apex_plugin.get_ajax_identifier
(refer back to the render function).38: The response from the server comes back as a string. Since it is really a JSON object, you need to explicitly convert it to a JavaScript JSON object. If you are expecting a non-string value, you will always need to explicitly convert it.
41-67: Displays the RSS content (received from the AJAX call) in a modal window or the error message accordingly.
Refresh Page 40 and click on one of the RSS links. Look in the console window and you should see the POST request, as shown in Figure 5-20. You can see the values that are passed to APEX (x01 and x02) that will be used in your PL/SQL AJAX render function.
If you click on the Response tab, you'll see that a bunch of HTML was returned. Scanning through the HTML, you'll notice that the content is essentially a standard error page in APEX. The error message is “No AJAX function has been defined for plug-in PLUGIN_COM.CLARIFIT.APEXPLUGIN.RSS_READER,” as shown in Figure 5-21. This makes sense as you still have not defined an AJAX function for the plug-in. If you ever get a similar response while developing AJAX support for a plug-in, it helps to scan through the returned HTML to see what is going wrong.
Now that the JavaScript code is complete and “talking” to APEX, it's time to define the server side code. The first thing is to do is create the function specification and register it with the plug-in:
Figure 5-22. AJAX function help
pkg_apress_plugins
. Name the function f_ajax_rss_reader
and compile the package spec.The final step is to enter the code for the package body. Edit the package body for pkg_apress_plugins
and copy the following code at the bottom of the package:
484 …
485
486 FUNCTION f_ajax_rss_reader (
487 p_region IN apex_plugin.t_region,
488 p_plugin IN apex_plugin.t_plugin )
489 RETURN apex_plugin.t_region_ajax_result
490 AS
491 -- APEX Application Variables (x01..x10)
492 v_rss_type VARCHAR2(255) := LOWER(apex_application.g_x01);
493 v_rss_id VARCHAR2(255) := apex_application.g_x02;
494
495 -- Region Plugin Attributes
496 v_rss_url apex_application_page_regions.attribute_01%TYPE := p_region.attribute_02;
497
498 -- Other Variables
499 v_author VARCHAR2(255);
500 v_title VARCHAR2(255);
501 v_link VARCHAR2(1000);
502 v_content CLOB;
503 v_cnt pls_integer;
504
505 -- Return
506 v_return apex_plugin.t_region_ajax_result;
507
508 -- Functions
509
510 -- Prints HTML JSON object for page to process
511 PROCEDURE sp_print_json(
512 p_author IN VARCHAR2,
513 p_title IN VARCHAR,
514 p_content IN CLOB,
515 p_link IN VARCHAR2,
516 p_error_msg IN VARCHAR2 DEFAULT NULL)
517
518 AS
519 v_html CLOB;
520 v_content clob;
521 BEGIN
522 v_content := p_content;
523
524 -- Escape HTML if required
525 IF p_region.escape_output THEN
526 v_content := sys.htf.escape_sc(v_content);
527 END IF;
528
529 v_html := '{
530 %AUTHOR%
531 %TITLE%
532 %CONTENT%
533 %LINK%
534 %ERROR_MSG_END_ELEMENT%
535 }';
536
537 v_html := REPLACE(v_html, '%AUTHOR%', apex_javascript.add_attribute('author',
sys.htf.escape_sc(p_author), FALSE));
538 v_html := REPLACE(v_html, '%TITLE%', apex_javascript.add_attribute('title',
sys.htf.escape_sc(p_title), FALSE));
539 v_html := REPLACE(v_html, '%CONTENT%', apex_javascript.add_attribute('content', v_content, FALSE));
540 v_html := REPLACE(v_html, '%LINK%', apex_javascript.add_attribute('link',
sys.htf.escape_sc(p_link), FALSE));
541 v_html := REPLACE(v_html, '%ERROR_MSG_END_ELEMENT%',
apex_javascript.add_attribute('errorMsg', sys.htf.escape_sc(p_error_msg), FALSE, FALSE));
542
543 sys.htp.p(v_html);
544 END sp_print_json;
545
546 -- Wrapper for error message
547 PROCEDURE sp_print_error_msg(
548 p_error_msg IN VARCHAR2)
549 AS
550 BEGIN
551 sp_print_json(
552 p_author => NULL,
553 p_title => NULL,
554 p_content => NULL,
555 p_link => null,
556 p_error_msg => p_error_msg);
557 END sp_print_error_msg;
558
559 BEGIN
560
561 IF v_rss_type = 'blogger' THEN
562 -- Get blog details
563 DECLARE
564 http_request_failed EXCEPTION;
565 PRAGMA EXCEPTION_INIT(http_request_failed, -29273);
566 BEGIN
567 SELECT author, title, CONTENT, LINK
568 INTO v_author, v_title, v_content, v_link
569 FROM xmltable(
570 XMLNAMESPACES(DEFAULT 'http://www.w3.org/2005/Atom'),
571 '*' passing httpuritype
(v_rss_url).getxml().EXTRACT('//feed/entry','xmlns="http://www.w3.org/2005/Atom"')
572 COLUMNS ID VARCHAR2(4000) path 'id',
573 title VARCHAR2(48) path 'title',
574 link VARCHAR2(1000) path 'link[@rel="alternate"]/@href',
575 author VARCHAR2(1000) path 'author/name',
576 content CLOB PATH 'content')
577 WHERE ID = v_rss_id;
578
579 sp_print_json(
580 p_author => v_author,
581 p_title => v_title,
582 p_content => v_content,
583 p_link => v_link);
584
585 EXCEPTION
586 WHEN NO_DATA_FOUND THEN
587 sp_print_error_msg(p_error_msg => 'Invalid RSS ID'),
588 WHEN TOO_MANY_ROWS THEN
589 sp_print_error_msg(p_error_msg => 'RSS ID returned multiple matches'),
590 WHEN http_request_failed THEN
591 sp_print_error_msg(p_error_msg => 'HTTP Connection Error'),
592 WHEN OTHERS THEN
593 sp_print_error_msg(p_error_msg => 'Unknown Error'),
594 END; -- Select
595
596 -- Add more RSS type support here
597 ELSE
598 -- Return error message
599 sp_print_error_msg(p_error_msg => 'Unknown RSS Type'),
600 END IF; -- v_rss_type
601
602 RETURN v_return;
603
604 EXCEPTION
605 WHEN OTHERS THEN
606 sp_print_error_msg(p_error_msg => 'Unknown Error'),
607 END f_ajax_rss_reader;
608 …
The following is a description of the preceding code that is keyed to the line numbers:
491-493: Meaningful variable names for values passed in as part of POST request. Remember these variables have nothing to do with plug-in specific attributes.
495-496: Plug-in specific variables.
505: The return object contains a dummy attribute. The only thing to really “return” is what is printed back using
htp.p
calls.510-544: Reusable function to print out a JSON object with all the applicable data that the JavaScript function will use to display the RSS content in a modal window. This function is essentially what sends data back to the client's browser.
524-527: Escapes special characters from the RSS content based on the region's escape option. This will play a factor in how the content is displayed to the user. You will see exactly how this impacts the application when testing the plug-in.
546-557: Wrapper function to handle errors. In this example, errors will be handled by the JavaScript function. How you handle AJAX errors is your decision; however, you should make sure that that the application is still useable.
559-607: Code to obtain the contents of an RSS feed.
The final thing to do is test the final product. Refresh Page 40. It should look like it did in Figure 5-19. If you click on one of the RSS titles, a modal window will appear and display the content from the RSS feed, as shown in Figure 5-23.
The first thing that stands out in Figure 5-23 is that the content shows all the HTML tags rather than being in a human readable format. This is because of the region's Escape Special Characters option, which is currently set to its default value: Yes.
To change this option (and you should only do so if the RSS feed is from a trusted source), edit the region and scroll down to the Security section. Set the Escape Special Characters to No, as shown in Figure 5-24. Run Page 40 and click on the same link as before. The content is a lot more human readable now, as shown in Figure 5-25.
This chapter provided some background information to help you understand what is required for a region plug-in and how AJAX works in APEX. You also built a region plug-in that contained an AJAX function.
Note This plug-in did not embed the code or the external files into the plug-in. If you want to do so, please refer to the Item plug-in chapter.
18.188.10.1