C H A P T E R  5

Image

Region Plug-Ins

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.

Background on Regions and AJAX

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.

Regions

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.

Image

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

  1. Go to the application's Shared Components.
  2. Under the User Interface, click on the Templates link as shown in Figure 5-2.
    Image

    Figure 5-2. Templates link in Shared Components

  3. Filter the templates report by setting the Type list to Region.
  4. Click on the default region. The default region has a check mark in the Default column. Figure 5-3 shows the default region template in the application as Reports Region.
    Image

    Figure 5-3. Default region template

  5. On the Edit Region Template page, scroll down to the Definition region. Figure 5-4 shows the content of the Template text area.
    Image

    Figure 5-4. Region template definition

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.

AJAX

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.

Image 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.

Image 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.

  1. Create a new blank page with the following attributes:

    Page Number: 30
    Name:AJAX (Old)
    HTML Region 1: My Region
    Tab: AJAX(Old)

  2. Edit the new page, Page 30, and create a new page item in My Region with the following attributes:

    Item Type: Number Field
    Item Name: P30_X

  3. Create a region button with the following attributes:

    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.

    Image

    Figure 5-5. AJAX demo page

  4. Go to Shared Components and click on the Application Processes link as shown in Figure 5-6.
    Image

    Figure 5-6. Application Processes link in Shared Components

  5. In the Application Processes window, click the Create button.
  6. On the Identification wizard page, enter the values as shown in Figure 5-7. Setting the Point to On Demand… allows this process to be accessible via an AJAX call. Click the Next button to go to the next step.
    Image

    Figure 5-7. Create Application Process > Identification

  7. On the Source page, enter the following PL/SQL code in the Process Text text area. This is the code that will be run when the AJAX function is triggered by the browser. Click the Next button to continue.

    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.

    -- Takes value and multiplies it by 2
    -- No error handling etc, as this is a demo
    DECLARE
      v_num pls_integer;
    BEGIN
      v_num := to_number(apex_application.g_x01);

      v_num := v_num * 2;

      htp.p (v_num);
    END;
  8. On the Conditionality page, leave the default options (i.e., no condition) and click the Create Process to complete the wizard.
  9. The final thing to do is to write the JavaScript code that will trigger the AJAX call. Edit Page 30 and right click on the RUN_AJAX button. Select the Create Dynamic Action option from the context menu.
  10. In the Identification section, set the Name to Run AJAX Function. Click the Next button to continue.
  11. Figure 5-8 shows the default options on the When page. Leave these settings and click the Next button.
    Image

    Figure 5-8. Create Dynamic Action > When

  12. On the True Action page, set the Action to Execute JavaScript Code. Uncheck the Fire On Page Load check box. Enter the following JavaScript code in the Code text area. Click the Next button to continue.

    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.

  13. On the Affected Elements page, click the Create button to finish the wizard.
  14. Run Page 30. Enter 2 in the text box and click the Run Ajax button. An alert window should pop up with the value 4, as shown in Figure 5-9.
    Image

    Figure 5-9. Run AJAX result

Image 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.

Image

Figure 5-10. AJAX POST request

Image

Figure 5-11. AJAX POST response

Example Business Problem

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:

  • Display a list of RSS feed titles.
  • Configure the maximum number of RSS feeds in the region, ranging from 5, 10, and 15 items.
  • Needs to currently support RSS feeds from Blogger (www.blogger.com).
  • When a user clicks on an RSS title, a modal window appears and displays the content of the RSS feed. The default modal window size will be configurable.

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.

Image 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:

  • The current user that the APEX application is being run as (i.e., parsing schema) is the APRESS user. In the scripts and queries below, substitute APRESS for your user.
  • Unless explicitly specified, scripts and queries will be run as the SYSTEM or SYS user. You may need to ask your DBA to run these scripts for you.

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:

  1. Run the following, which will grant access to external networks for your current user. Don't forget to change the value of 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;
    /
  2. Execute the following queries to confirm that the ACL setup worked. You should see your user associated to the network ACL with full access.
    -- All ACLs
    SELECT host, lower_port, upper_port, acl
    FROM dba_network_acls;

    -- Privileges for ACLs
    -- Lists which users have access to which ACL
    SELECT acl, principal, privilege, is_grant, invert, start_date, end_date
    FROM dba_network_acl_privileges;
  3. Confirm that your user now has network access by running the following script, with DBMS_OUTPUT enabled as your current user. You should see “Ok. Have access” as part of the output.
    -- 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.

Building the Region 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.

Initial Configuration and Setup

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.

Name: ClariFit RSS Reader

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:

  1. Create a directory called c:wwwRSSReader
  2. Modify the plug-in. In the Settings region set the File Prefix to http://localhost/RSSReader/
  3. Click the Apply Changes button to save your changes.

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.

Image

Figure 5-12. Region plug-in standard attributes

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.

Custom Attributes

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.

  • Scope: Component

    Attribute:1
    Label: RSS Type
    Type: Select List
    Required: Yes
    Default Value: Blogger

  • LOVs:

    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.

  • Scope: Component

    Attribute: 2
    Label: RSS URL
    Type: Text
    Required: Yes
    Display Width: 50

  • Scope: Component

    Attribute: 3
    Label: Max Rows
    Type: Select List
    Required: Yes
    Default: 5

  • LOVs:

    Display Value: 5
    Return Value: 5

    Display Value: 10
    Return Value: 10

    Display Value: 15
    Return Value: 15

  • Scope: Component

    Attribute: 4
    Label: Modal Width(controls width of the modal window)
    Type: Integer
    Required: Yes
    Display Width: 3
    Maximum Width: 4
    Default Value: 700

  • Scope: Component

    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.

Image

Figure 5-13. Dynamic action plug-in custom attribute settings

Image

Figure 5-14. Region plug-in custom attribute settings

Creating a Test Page

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:

  1. Create a new page with Page Type of Plug-ins. Click the Next button.

    Just to clarify, there's no such thing as a Page plug-in. In this case, plug-in refers to a region plug-in.


  2. In the Type page, select ClariFit RSS Reader as the Plug-in. Click the Next button.
  3. On the Page and Region Attributes page, set the following values, then click the Next button to continue.

    Page Number: 40
    Page Name: RSS Reader
    Region Name: RSS Reader

  4. On the Tab Options page, click the Use an existing tab set and create a new tab within the existing tab set radio button. In the New Tab Label field, enter RSS Reader. Click the Next button to continue.
  5. On the Settings page, leave the default options but set the RSS URL to http://www.talkapex.com/feeds/posts/default, as shown in Figure 5-15. Click the Next button to continue.

    (Note: You can use any Blogger URL by replacing www.talkapex.com with your URL.)

    Image

    Figure 5-15. RSS Reader settings

  6. On the confirmation page, click the Finish button to create the page.

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.

Image

Figure 5-16. No render function error message

Creating the Render 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.

Image Hint To get the render function spec template, click render function label and view the help file.

Image

Figure 5-17. pkg_apress_plugins.f_render_rss_reader Package Spec

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.

Image

Figure 5-18. Region plug-in callbacks

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. The apex_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.

Image

Figure 5-19. RSS Reader rendered

Creating the AJAX Function

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.

JavaScript

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:

Image 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.

Image

Figure 5-20. AJAX POST request

Image

Figure 5-21. AJAX error message

Writing the AJAX Callback Function

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:

  1. Edit the plug-in and scroll down to the Callbacks region. Click on the AJAX Function Name label to view the help text. Similar to render functions, AJAX function header templates are provided in the help text. Copy the function header for region type plug-ins as shown in Figure 5-22.
    Image

    Figure 5-22. AJAX function help

  2. Paste the function template into the package spec for pkg_apress_plugins. Name the function f_ajax_rss_reader and compile the package spec.
  3. To register the function with the plug-in, edit the plug-in and scroll down to the Callbacks section. Enter pkg_apress_plugins.f_ajax_rss_reader in the AJAX Function Name field. Click the Apply Changes button to save your change.

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.

Testing the Plug-in

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.

Image

Figure 5-23. RSS feed content

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.

Image

Figure 5-24. Region security settings

Image

Figure 5-25. RSS feed content (unescaped)

Summary

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.

Image 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.188.10.1