Plug-ins, new in APEX 4.0, allow you to extend the functionality of item types, region types, dynamic actions, and process types. The plug-in architecture includes a declarative development environment that lets you create custom versions of these built-in objects. For example, instead of using a standard select list or check box, you could build a "star rating" item that allows your user to provide feedback using a one-to-five star graphic. This new item type could then be used across all your applications.
While it was always possible to create custom functionality using tools like custom Ajax, the code could be located in multiple different places: inside the database, in JavaScript files, and so forth. You scan still use all the customization tools you are familiar with, but turning that code into a plug-in makes it much easier to use and manage since all of the code is contained in one object, your new type.
This new architecture also allows you to create plug-ins based on any jQuery component. The open source jQuery library is widely accepted and is loaded with useful JavaScript and Ajax functionality. Basing your plug-ins on existing jQuery functionality is a great way to add advanced features to your applications without generating mountains of new code manually.
In this chapter, we will explore the new world of plug-ins by creating a few from scratch while also reviewing some popular plug-ins that are available from the following online repositories:
Oracle Applications Express Plug-ins page (www.oracle.com/technetwork/developer-tools/apex/application-express/apex-plug-ins-182042.html
)
APEX-PLUGIN.com
(www.apex-plugin.com
)
After seeing the number of plug-ins that are freely available, you may find yourself importing more plug-ins than you build. If you do build plug-ins of your own, you can use the downloadable ones as a guide and you can publish your own back to the online repositories. Think of this as a way to give a little back to your development community.
As with any development project, you first need to establish a goal; some new piece of functionality. Once the goal is defined, you can define the requirements to be mapped to your development framework. If your new functionality would be beneficial in other applications, you should consider creating it as a plug-in. Will it work as a plug-in? If your functionality is an extension of one of the standard types—item, region, process, dynamic action—then it is a candidate for making into a plug-in.
The interface used to create APEX plug-ins is accessed under the Shared Components menu of the Application Builder. The plug-in builder interface is accessed via the Plug-ins link on the User Interface section. When you first enter the Plug-ins builder, as shown in Figure 12-1, you will notice that APEX 4.0 is delivered with no plug-ins. There is, however, a button on the page titled "View Plug-in Repository," which will direct you to a location on Oracle's corporate web site that houses plug-ins that are freely downloadable. We will explore this site later in this chapter, but for now we will focus on the mechanics of building a plug-in from scratch.
We'll start by creating a simple item type plug-in, as it is the easiest to complete and illustrates most of the mechanics of the plug-in architecture. To create a plug-in of any kind, you have to first decide what its purpose will be. In this example, we'll create a text item that will require a value and will have a red background until a value is entered. After at least one character is entered in the field, the background will return to standard white. We'll call this plug-in requiredInColor
.
To get started, we need to take a look at the plug-in builder. When you click on the Create button, the plug-in builder page is displayed as shown in Figure 12-2. We filled in the Name, Internal Name, and Type fields in the figure to illustrate how plug-ins should be named. Following are guidelines on plug-in naming best practices:
The Name field will be used to identify the plug-in when adding them to pages using the standard builder pages. This name should be unique across your applications, but uniqueness is not required.
The Internal Name of a plug-in is used by APEX 4.0 to identify the plug-in and is never displayed. While the plug-in Name is not required to be unique, the Internal Name must be unique to your current application. Because plug-ins can be published to public repositories, it is recommended that their Internal Names be constructed so that they will be unique worldwide. For example: com.enkitec.requiredInColor
.
These guidelines guarantee that your plug-in's name will not be a duplicate of one that you download from another site. They do not ensure that names for plug-ins you create are unique within your organization, so you'll have to manage that on your own. You'll notice that any plug-ins you download from Oracle's plug-in repository have names that are prefixed with com.oracle.apex
.
In the Subscription section of the plug-in builder, the Reference Master Plug-in From field allows you to use a copy of an existing plug-in by either entering the name or selecting it from a pop-up list of values. If you leave this field blank, by default the plug-in is considered a master. We will leave this field blank since we are creating a new plug-in.
The File Prefix, #PLUGIN_PREFIX#
, is a virtual path that determines where any plug-in support files (like JavaScript files, CSS files, and so forth) will be stored. This prefix points to a location in the database. If you are using a separate HTTP server, you can get better performance if you store the supporting files there, as opposed to on the database. You can either create a new substitution string pointing to a new virtual directory or just use the #IMAGE_PREFIX#
in place of the #PLUGIN_PREFIX#
.
The next section, PL/SQL Code, is where the guts of the plug-in are built. This code is used to do the following:
You can write your code as an anonymous PL/SQL block in the PL/SQL Code section. You can also create your code as a database package and refer to that package from a block in this section. You'll get better performance from the package-based approach.
Each plug-in type must implement a specific interface, known as a render function, which was standardized so that future APEX enhancements do not break existing plug-ins. Since we are talking about item type plug-ins, we'll first take a look at the item type render function shown in Listing 12-1.
Example 12-1. Plug-in Render Function Interface
function <name of function> ( p_item in apex_plugin.t_page_item, p_plugin in apex_plugin.t_plugin, p_value in varchar2, p_is_readonly in boolean, p_is_printer_friendly in boolean ) return apex_plugin.t_page_item_render_result
The process for building plug-ins is not completely documented. In fact, you will not find the function definition from Listing 12-1 in any of the APEX 4.0 documentation. The only place to find information on how plug-ins are defined is by clicking on the "Render Function Name" in the Callbacks section of the Plug-in builder page.
The name of the function is not important, it just has to follow Listing 12-1's specification. The first two parameters in the render function are of type apex_plugin
. The name apex_plugin
is actually a synonym for the apex_040000.wwv_flow_plugin
package. This record type contains attributes that allow you to control the operation of the item on which the plug-in is being built. The ID attribute, for example, is the HTML ID of the item that can be used in JavaScript to manipulate the Document Object Model (DOM). Listing 12-2 shows an excerpt from the wwv_flow_plugin
package showing the definition of the t_page_item
and t_plugin
record types.
Example 12-2. Plug-in Record Type Definitions
type t_page_item is record ( id number, name varchar2(255), label varchar2(4000), plain_label varchar2(4000), format_mask varchar2(255), is_required boolean, lov_definition varchar2(4000), lov_display_extra boolean, lov_display_null boolean, lov_null_text varchar2(255), lov_null_value varchar2(255),
lov_cascade_parent_items varchar2(255), ajax_items_to_submit varchar2(255), ajax_optimize_refresh boolean, element_width number, element_max_length number, element_height number, element_attributes varchar2(2000), element_option_attributes varchar2(4000), escape_output boolean, attribute_01 varchar2(32767), attribute_02 varchar2(32767), attribute_03 varchar2(32767), attribute_04 varchar2(32767), attribute_05 varchar2(32767), attribute_06 varchar2(32767), attribute_07 varchar2(32767), attribute_08 varchar2(32767), attribute_09 varchar2(32767), attribute_10 varchar2(32767) ) type t_plugin is record ( name varchar2(45), file_prefix varchar2(4000), attribute_01 varchar2(32767), attribute_02 varchar2(32767), attribute_03 varchar2(32767), attribute_04 varchar2(32767), attribute_05 varchar2(32767), attribute_06 varchar2(32767), attribute_07 varchar2(32767), attribute_08 varchar2(32767), attribute_09 varchar2(32767), attribute_10 varchar2(32767) )
As you can see in the t_page_item
record, the name and ID of the item you are creating are available to you via this interface. When you want to operate on the value of your item type plug-in using JavaScript, you can pass the t_page_item.id
to a JavaScript function and manipulate the DOM to control the appearance and utility of the item. You can also see that there are ten fields in both record types that start with "attribute." These are custom attributes that you can assign to your plug-in, which can be used to further define your item.
To define the features of your plug-in, you must construct a render function and, optionally, a validation function. The render function defines what the item will do and how it will be displayed. To protect against SQL injection attacks, a validation function should also be added, although it is not required. Listing 12-3 shows the contents of the PL/SQL code that implements our requiredInColor
plug-in.
Example 12-3. Plug-in PL/SQL Code
FUNCTION requiredInColor ( p_item in apex_plugin.t_page_item, p_plugin in apex_plugin.t_plugin, p_value in varchar2, p_is_readonly in boolean, p_is_printer_friendly in boolean ) RETURN apex_plugin.t_page_item_render_result IS l_name varchar2(30); l_result apex_plugin.t_page_item_render_result; BEGIN IF p_is_readonly OR p_is_printer_friendly THEN APEX_PLUGIN_UTIL.print_hidden_if_readonly ( p_item_name => p_item.name, p_value => p_value, p_is_readonly => p_is_readonly, p_is_printer_friendly => p_is_printer_friendly ); APEX_PLUGIN_UTIL.print_display_only ( p_item_name => p_item.name, p_display_value => p_value, p_show_line_breaks => false, p_escape => true, p_attributes => p_item.element_attributes ); ELSE l_name := APEX_PLUGIN.get_input_name_for_page_item(p_is_multi_value=>false); HTP.P('<input type="text" name="'||l_name||'" id="'||p_item.name||'" '||'value="'||htf.escape_sc(p_value)||'" size="'||p_item.element_width||'" '||'maxlength="'||p_item.element_max_length||'" '||p_item.element_attributes||' onKeyUp="fldRequired('''||p_item.name||''')" />'), APEX_JAVASCRIPT.add_library (p_name=> 'requiredInColor',p_directory => p_plugin.file_prefix,p_version => null ); END IF; RETURN l_result; END requiredInColor;
The code in Listing 12-3 adheres to the requirement shown in Listing 12-2. Following the declaration section, the first thing all plug-ins should do is detect whether the item has been identified as being read-only or printer-friendly. The l_name
variable is used to store the name of the item on which the plug-in is based. To get the item name, the following function is called:
l_name := APEX_PLUGIN.get_input_name_for_page_item(p_is_multi_value=>false);
The call to this function includes a parameter, p_is_multi_value=>false
. That parameter identifies the page item on which the plug-in is based as a single-value item. If the page item were a select list, for example, the value of the parameter would be set to true. The next two lines of code constitute most of the functionality of the plug-in. The line which begins with HTP.P
actually renders the item as an HTML input item.
Because of the way single and double quote marks are used in PL/SQL, reading the code in Listing 12-3 is difficult at best. To make writing this code a little easier, use an HTML editor to build the code that you want and paste it in to the PL/SQL section of the plug-in builder. You can then modify the code one piece at a time replacing literals with the appropriate variables.
Notice that the value of the item, p_value
, is being "escaped" using the htf.escape_sc()
function, which changes values like "<
" to "<."
This function should always be used when passing values from an HTML page, as a malicious user can easily modify input values to submit SQL injection commands.
Paste Listing 12-3's code into the PL/SQL Code section of the plug-in builder and set the Render Function Name in the Callbacks section to the name of the render function from the PL/SQL code. If you forget this step, you will get an error saying that there is no render function for the plug-in when you run a page that contains the item-based plug-in. Figure 12-3 shows the populated PL/SQL Code section along with the correct setting for the Render Function Name.
Since we want to change the background color of the field as soon as a character is entered, the onKeyUp
JavaScript function is called. You can see that the HTP.P
function uses onKeyUp
to call a JavaScript program called fldRequired
. So where is this JavaScript? It's contained within an "included file" called requiredInColor.js
that has to be uploaded to a location where APEX can find it.
To keep things simple, we used the #PLUGIN_PREFIX#
substitution string so that uploaded files are stored in the database instead of on the HTTP server. The contents of the requiredInColor.js
file are shown in Listing 12-4. The file has but one simple function: to change the color of the background of the item ID passed to it depending on whether any text has been entered. The background is set to #FEA5AD
, which is a light red color when empty and white when populated. This file is uploaded using the Files section of the plug-in builder, as shown in Figure 12-4.
Example 12-4. requiredInColor.js JavaScript File
function fldRequired(fldName) { vValue = document.getElementById(fldName).value; if (!vValue) document.getElementById(fldName).style.background="#FEA5AD"; else document.getElementById(fldName).style.background="#FFFFFF"; }
The "Files" section referred to in Figure 12-4 will not be visible until you create the plug-in.
Once the JavaScript file is uploaded, the plug-in is ready to be used. Before moving on to using the new plug-in, it is important to discuss how you make changes to the uploaded JavaScript file. It is not as simple as making changes to the file and uploading it again, because APEX 4.0 does not allow the same file to be uploaded more than once. Instead, you have to delete the uploaded file and then re-upload it. While not insurmountable, the process is nothing short of annoying, as you must repeat the change/delete/upload cycle over and over while you're developing and testing your plug-in.
Since you are building standard JavaScript to support your plug-in, you can build a very simple HTML page that uses your JavaScript so that you can more easily troubleshoot the code. When you have it working, you can then go through the delete/upload cycle in the plug-in builder.
The last step in preparing the plug-in for use is to set the Standard Attributes. In this example, we only need to set the Is Visible Widget attribute, which tells APEX to display items based on this plug-in. If you want any plug-in to be visible, this attribute must be set. Figure 12-5 shows the Standard Attribute settings for this plug-in and the verification that our JavaScript file has been uploaded.
Now that your plug-in has been created, you can include it in any page in your application. It is important to note at this point that the plug-in you just created will only show up on the plug-in home page of the application where it was created. As discussed earlier in this chapter, existing plug-ins can be referenced in the Subscription section of the plug-in builder as opposed to being imported or re-created from scratch. Later in this chapter, we will cover sharing plug-ins by using the export and import features of the plug-in builder.
To test your new requiredInColor
plug-in, you need to add it to a page. We modified an update form in the Buglist application. To add the new item type plug-in, edit the page (page 28 in our example) and create a new item. In Figure 12-6, the new item type is set to Plug-in.
In the next step of the process, the installed plug-ins are displayed as shown in Figure 12-7. In our environment, you can see there are several available plug-ins and we have selected Required In Color. This is the name we assigned to our plug-in at the time it was created. The internal name (com.enkitec.requiredInColor
) will never show up in a list of available items.
From this point on, the item creation process is the same as it would be if you were adding a standard, APEX text item. The only deviation, as we will see shortly, is if you have added custom attributes to your plug-in. In that case, you will have the opportunity to populate those new attributes during the item creation process. Our first plug-in does not contain custom attributes, so the creation process will be very familiar. In Figure 12-8, notice that we are naming the item as normal. The field name is defaulted with a value like P28_X
and we modify it to read P28_REQUIRED
.
To actually make this new plug-in-based item useful, we need to associate it with some database column. In the final step of the create item wizard, you would normally choose Database Column as the source type and some existing column as the source. Let's say we want to use this new item for the cost column to show that this column is required. If we assign it to the cost column now, we will get an error at runtime since there is already another item assigned to that column. So for now, leave the Source Type set to the default (Static Assignment), as shown in Figure 12-9. The item will be added to the page and will operate the way we want it to, but the values entered in this item will not be stored in the database.
When you finish the creation process, run your page. In our case, this means executing a report and clicking the update icon to access the update form that contains our new item type plug-in. The update form, shown in Figure 12-10, contains our new, unpopulated field with a red background (you'll have to take our word for it since this page is monochrome).
The intention of this plug-in was to show the user that there is a field on the page that must contain a value. This is done visually by coloring the background of the page when the field is blank. When we enter a value, as shown in Figure 12-11, the characters show up and the background is changed back to standard white. Remember that our code only checks for a value, any value, and then changes the background color of the field. To make this plug-in more useful, you could further validate the input to ensure it meets the specifications of the system.
The intention of this plug-in was to show the user that there is a field on the page that must contain a value. That is done visually by coloring the background of the page item when it is blank. When we enter a value, as shown in Figure 12-11, the characters show up and the background is changed back to standard white.
Remember that this new item doesn't really do anything other than rotate the color of the background. To make this new item actually useful, we need to associate it with a database column so that we can use it just like the current Cost item. To make this happen, a few simple steps are required:
Delete the current cost item from the page
Modify the P28_REQUIRED
item, changing its name to P28_COST
Change the label to "Cost"
Change the Source Type to Database Column
Set the Item Source Value to the cost
column
Re-position the new item, if you like
Now when you run the application and put a value in the Cost item, the value will be written to the database when you submit the form. If you do a little testing, you will notice that when the form containing the plug-in-based item displays a record that has a cost value, the item is displayed with a red background. The reason for this is simple. The render function we wrote executes the fldRequired
JavaScript function using the onKeyUp
event. This event will not be fired when the form is displayed, so we need to add some code that will execute our JavaScript function. Listing 12-5 shows the addition of the APEX_JAVASCRIPT.on_load_code
function, which does just what you'd expect it to. The nice thing about this function is that it operates in conjunction with an onLoad
script you may be using in the HTML Body of the form.
Example 12-5. requiredInColor.js JavaScript File
function requiredInColor ( p_item in apex_plugin.t_page_item, p_plugin in apex_plugin.t_plugin, p_value in varchar2, p_is_readonly in boolean, p_is_printer_friendly in boolean ) return apex_plugin.t_page_item_render_result is l_name varchar2(30); l_result apex_plugin.t_page_item_render_result; BEGIN IF p_is_readonly OR p_is_printer_friendly THEN APEX_PLUGIN_UTIL.print_hidden_if_readonly ( p_item_name => p_item.name, p_value => p_value, p_is_readonly => p_is_readonly, p_is_printer_friendly => p_is_printer_friendly ); APEX_PLUGIN_UTIL.print_display_only ( p_item_name => p_item.name, p_display_value => p_value, p_show_line_breaks => false, p_escape => true, p_attributes => p_item.element_attributes ); ELSE l_name := APEX_PLUGIN.get_input_name_for_page_item(p_is_multi_value=>false); htp.p('<input type="text" name="'||l_name||'" id="'||p_item.name||'" '||'value="'||htf.escape_sc(p_value)||'" size="'||p_item.element_width||'" '||'maxlength="'||p_item.element_max_length||'" '||p_item.element_attributes||' onKeyUp="fldRequired('''||p_item.name||''')"/>'), APEX_JAVASCRIPT.add_library (p_name=> 'requiredInColor',p_directory => p_plugin.file_prefix,p_version => null ); APEX_JAVASCRIPT.add_onload_code (p_code => ' fldRequired('''||p_item.name||''')'), END IF; RETURN l_result; END requiredInColor;
Replace the code in the PL/SQL section of the Required In Color plug-in and execute the page again. You can see that the Cost item is displayed with a white background for records that contain a Cost value. There is still much more you could do with this item. Adding real validation code to ensure that a value is entered and it is within specifications, for example, would be easy to do and you should be able to do that on your own.
Custom attributes are used to store metadata that can be added to any plug-in type. When adding a plug-in-based item to a page, you will be presented a prompt for each custom attribute attached to the type. You can assign up to ten custom attributes per plug-in, which allows for a great deal of flexibility. To keep things simple, we will assign a custom attribute to the Required In Color plug-in, which allows the developer to choose the color of the background of this item. We could add two attributes to control both the populated and unpopulated colors, but the mechanics are identical so we will stick with one in the interest of brevity.
The process of adding attributes is very simple. You navigate to the plug-in home page and edit the Required In Color plug-in. You'll notice that the Custom Attributes section reports that there are no attributes defined for this plug-in. Clicking the Add Attribute button displays the Edit Attribute page, as shown in Figure 12-12.
Starting at the top of the Attribute definition, the Scope of the attribute refers to how the attribute can be used once the plug-in has been created. The Scope has two settings: Component and Application. If we set it to Application, then the developer using this plug-in would only be able to set the value of this attribute one time for the entire application. Using the Component settings allows for this attribute to be defined each time the plug-in is used.
The Attribute field is set to a numeric value that will be used to identify the attribute in code. The Display Sequence determines the order in which the Attributes are displayed in the APEX Builder and the Label is used to describe the attribute. We set the label to "Required Field Color."
The Settings section allows you to define the method the developer will use to set the value of the attribute. As you can see in Figure 12-13, there are several options from which to choose. For this example, we will use the Text type, as we want the developer to be able to enter a color value. We set the default value to #FEA5AD
, so the text item's background will be rendered in color even if the developer forgets to set the value. You can see the definitions of the other settings by clicking on their labels.
The Conditions and Help Text settings are the same as with other APEX objects. For this example, we left them blank and clicked the Create button to finish the Attribute creation process. So now we have a custom attribute, but it doesn't change anything because our code doesn't know about it. You can set the value of this attribute by editing the item that is based on this plug-in, as shown in Figure 12-14.
Your plug-in's custom attribute (or attributes) now show up in their own section on the item edit page called "Settings." For plug-ins with no custom attributes, the Settings section will not be displayed. You can see the default value that was set when the custom attribute was created, but you can change it to any value you like (just make sure it is a valid HTML color). To include our new attribute in the action, we have to make some code changes. First, we need to change the plug-in's PL/SQL code so that it has access to the attribute's value. Then we will modify the requiredInColor.js
JavaScript file to use the new attribute value to paint the background of the item. Listing 12-6 shows the changes we made to the PL/SQL code. A new variable, l_background
, is used to store the value of the single, custom attribute we added to the plug-in. We used the following declaration to gain access to the attribute's value:
l_background apex_application_page_items.attribute_01%type := p_item.attribute_01
When the l_background
variable was defined, we had to know that the attribute used to store the background color was attribute number 1 because we can't refer to it by name. This was easy in our example since we only have one, but if you had multiple custom attributes on the plug-in, you would have to be sure about which ones you were using.
We also had to change the calls to htp.p
and the APEX_JAVASCRIPT.add_onload_code
to include the l_background
parameter in when the fldRequired
JavaScript function is called.
When you're making changes to HTML code within the PL/SQL section, it can be excruciatingly difficult to get the single and double quotes right. A trick we use is to cut a piece of the code out and modify it slightly so that it can be executed in a simple, "select <some HTTP> from dual" statement in SQL*Plus. Once you see what the output looks like, you can change it a little at a time to make sure you have it right.
Example 12-6. Plug-in PL/SQL Code Modified to Use Custom Attributes
function requiredInColor ( p_item in apex_plugin.t_page_item, p_plugin in apex_plugin.t_plugin, p_value in varchar2, p_is_readonly in boolean, p_is_printer_friendly in boolean ) return apex_plugin.t_page_item_render_result is l_name varchar2(30); l_result apex_plugin.t_page_item_render_result; l_background apex_application_page_items.attribute_01%type := p_item.attribute_01; BEGIN IF p_is_readonly OR p_is_printer_friendly THEN APEX_PLUGIN_UTIL.print_hidden_if_readonly ( p_item_name => p_item.name, p_value => p_value, p_is_readonly => p_is_readonly, p_is_printer_friendly => p_is_printer_friendly ); APEX_PLUGIN_UTIL.print_display_only (
p_item_name => p_item.name, p_display_value => p_value, p_show_line_breaks => false, p_escape => true, p_attributes => p_item.element_attributes ); ELSE l_name := APEX_PLUGIN.get_input_name_for_page_item(p_is_multi_value=>false); htp.p('<input type="text" name="'||l_name||'" id="'||p_item.name||'" '||'value="'||htf.escape_sc(p_value)||'" size="'||p_item.element_width||'" '||'maxlength="'||p_item.element_max_length||'" '||p_item.element_attributes||' onKeyUp="fldRequired('''||p_item.name||''','''||l_background||''')"/>'), APEX_JAVASCRIPT.add_library (p_name=> 'requiredInColor',p_directory => p_plugin.file_prefix,p_version => null ); APEX_JAVASCRIPT.add_onload_code (p_code => ' fldRequired("'||p_item.name||'","'||l_background||'")') END IF; RETURN l_result; END requiredInColor;
To round out the changes related to our custom attribute, we need to modify the requiredInColor.js
file, as it needs to receive two parameters instead of one. All we need to do is add a parameter, bkgColor
, in the declaration of the fldRequired
function and then use that variable name to color the item's background when it's empty. The new code is shown in Listing 12-7.
Example 12-7. requiredInColor JavaScript Modified to Use Custom Attributes
function fldRequired(fldName,bkgColor) { vValue = document.getElementById(fldName).value; if (!vValue) { document.getElementById(fldName).style.background=bkgColor; } else { document.getElementById(fldName).style.background="#FFFFFF"; } }
We're not done just yet, however. Remember that we have to upload the requiredInColor.js
file so the plug-in has access to our changes. If a file with this name is already associated with the plug-in (which it is), we must first delete it as APEX 4.0 does not allow you implicitly replace an uploaded file. Using the edit plug-in page as shown in Figure 12-15, you must edit the existing requiredInColor
file so you can delete it; then upload it again so our new changes are available.
Once you apply the changes to the plug-in, you can execute your form. It should look just like it did before because the default value of the plug-in's attribute is the same. Try modifying the value of the "Required Field Color" setting on the P28_COST_REQUIRED
item to some other color. Figure 12-16 shows our attribute being changed to yellow.
When you execute the page now, as shown in Figure 12-17, the Cost item's background is yellow when the value is null. You may have to back out the existing value to see the color change. Since we're talking in black and white, you'll have to take our word for it.
The functionality we created with this plug-in was pretty simple, but it took quite a bit of work to get it done. Now that it's done, though, it can be used everywhere and made available to fellow APEX developers worldwide.
Once you create an object that is based on a plug-in, you will not be able to delete the plug-in. This makes sense since the items using the plug-in would cease to operate if you delete it. This can be confusing, however, since the Delete button does not show up on the Edit Plug-in page if the plug-in is in use. To quickly locate the where the plug-in is use, you can view the plug-in Utilization report.
When plug-ins are created, they are visible only to the application in which they were created. To make your plug-ins available to other applications, they must be exported and subsequently imported into other applications. If you choose to share your plug-ins with the APEX developer community, you would export them and upload them to an APEX plug-in repository, like the following:
Oracle Applications Express Plug-ins page (www.oracle.com/technetwork/developer-tools/apex/application-express/apex-plug-ins-182042.html
)
APEX-PLUGIN.com
(www.apex-plugin.com
)
There are three ways to export plug-ins, but only one of them works. Following is the one approach that works:
However, the following two approaches do not work:
Exporting a plug-in from the Application Builder home page
Exporting a plug-in from an application other than where it was created
That only one approach works can be confusing since all three processes force you to select the application from which to export the plug-in (as we will see shortly), even if you are in the application where the plug-in was created. All three of the plug-in export processes will tell you that your plug-in was successfully exported, but you may not realize that the promise is false until you try to import the plug-in. The two processes that don't work generate an invalid export file. This is likely a bug that will be corrected in a later version of APEX 4.0. For now, just use the export plug-in feature inside your application.
So, to perform a successful plug-in export, navigate to the Plug-ins home page in the Shared Components section of the application in which you created the plug-in. Figure 12-18 shows the plug-ins home page for Application 101 and the "Export Plug-in" link in the Tasks region.
Notice that the plug-in we want to export, Required in Color, exists in this application. To start the actual export process, you click on the Export Plug-in link in the Tasks window. In Figure 12-19, you can see that even though you are editing the application that contains the Required in Color plug-in, you are still required to select the application that contains the plug-in you want to export.
Clicking the Set Application button takes you to the next step in the process where you can select the plug-in. Figure 12-20 shows that you can select both the plug-in to export as well as the file format (UNIX or DOS) to create. We selected the requiredInColor
plug-in and set the File Format to UNIX. The character set of the export file defaults to that of the application.
To complete the export plug-in process, press the Export Plug-in button. The interesting thing about this step is that APEX does not store an output file for you. Instead, it creates an export file on the fly, which is downloaded to your browser, as shown in Figure 12-21. You can either open the file or save it to your file system. The point of this exercise is to make the plug-in available to other applications, so you should save it. Listing 12-8 shows the valid file created during the export process. Notice that it is a SQL file that you could execute in SQL*Plus.
Example 12-8. Exported Plug-in File
set define off set verify off set serveroutput on size 1000000 set feedback off WHENEVER SQLERROR EXIT SQL.SQLCODE ROLLBACK begin wwv_flow.g_import_in_progress := true; end; / -- AAAA PPPPP EEEEEE XX XX -- AA AA PP PP EE XX XX -- AA AA PP PP EE XX XX -- AAAAAAAAAA PPPPP EEEE XXXX -- AA AA PP EE XX XX -- AA AA PP EE XX XX -- AA AA PP EEEEEE XX XX prompt Set Credentials... begin -- Assumes you are running the script connected to SQL*Plus as the Oracle user APEX_040000 or as the owner (parsing schema) of the application. wwv_flow_api.set_security_group_id(p_security_group_id=>nvl(wwv_flow_application_install.get_w orkspace_id,1280317158978593)); end; / begin wwv_flow.g_import_in_progress := true; end; / begin
select value into wwv_flow_api.g_nls_numeric_chars from nls_session_parameters where parameter='NLS_NUMERIC_CHARACTERS'; end; / begin execute immediate 'alter session set nls_numeric_characters=''.,'''; end; / begin wwv_flow.g_browser_language := 'en'; end; / prompt Check Compatibility... begin -- This date identifies the minimum version required to import this file. wwv_flow_api.set_version(p_version_yyyy_mm_dd=>'2010.05.13') end; / prompt Set Application ID... begin -- SET APPLICATION ID wwv_flow.g_flow_id := nvl(wwv_flow_application_install.get_application_id,101); wwv_flow_api.g_id_offset := nvl(wwv_flow_application_install.get_offset,0); null; end; / prompt ...plugins -- --application/shared_components/plugins/item_type/com_enkitec_requiredincolor begin wwv_flow_api.create_plugin ( p_id => 9050220872125872 + wwv_flow_api.g_id_offset ,p_flow_id => wwv_flow.g_flow_id ,p_plugin_type => 'ITEM TYPE' ,p_name => 'COM.ENKITEC.REQUIREDINCOLOR' ,p_display_name => 'Required In Color' ,p_image_prefix => '#PLUGIN_PREFIX#' ,p_plsql_code => 'function requiredInColor ('||chr(10)|| ' p_item in apex_plugin.t_page_item,'||chr(10)|| ' p_plugin in apex_plugin.t_plugin,'||chr(10)|| ' p_value in varchar2,'||chr(10)|| ' p_is_readonly in boolean,'||chr(10)|| ' p_is_printer_friendly in boolean )'||chr(10)|| ' return apex_plugin.t_page_item_render_result'||chr(10)|| ''||chr(10)|| 'is'||chr(10)|| ''||chr(10)|| ' l_name varchar2(30);'||chr(10)|| ''||chr(10)|| ' l_result apex_plugin.t_page_item_render_result;'||chr(10)|| ''||chr(10)|| ' l_background apex_applicati'|| 'on_page_items.attribute_01%type := p_item.attribute_01;'||chr(10)|| ''||chr(10)||
'BEGIN'||chr(10)|| ''||chr(10)|| ' IF p_is_readonly OR p_is_printer_friendly THEN'||chr(10)|| ' '||chr(10)|| ' APEX_PLUGIN_UTIL.print_hidden_if_readonly ('||chr(10)|| ' p_item_name => p_item.name,'||chr(10)|| ' p_value => p_value,'||chr(10)|| ' p_is_readonly => p_is_readonly,'||chr(10)|| ' p_is_printer_friendly => p_is_printer_friendly );'||chr(10)|| ''||chr(10)|| ' APEX_PLUGIN_UTIL.print_display_only ('||chr(10)|| ' '|| ' p_item_name => p_item.name,'||chr(10)|| ' p_display_value => p_value,'||chr(10)|| ' p_show_line_breaks => false,'||chr(10)|| ' p_escape => true,'||chr(10)|| ' p_attributes => p_item.element_attributes );'||chr(10)|| ' '||chr(10)|| ' ELSE'||chr(10)|| ''||chr(10)|| ' l_name := APEX_PLUGIN.get_input_name_for_page_item(p_is_multi_value=>false);'||chr(10)|| ''||chr(10)|| ' htp.p(''<input type="text" name="''||l_name||''" id="''||p_item.name||''" ''||''value="''||htf.escape_sc('|| 'p_value)||''" size="''||p_item.element_width||''" ''||''maxlength="''||p_item.element_max_length||''" ''||p_item.element_attributes||'' onKeyUp="fldRequired(''''''||p_item.name||'''''',''''''||l_background||'''''')"/>''),'||chr(10)|| ''||chr(10)|| ' APEX_JAVASCRIPT.add_library (p_name=> ''requiredInColor'',p_directory => p_plugin.file_prefix,p_version => null );'||chr(10)|| ''||chr(10)|| ' APEX_JAVASCRIPT.add_onload_code (p_code => '' fldRequired("''||p_item.name||''",'|| '"''||l_background||''")''),'||chr(10)|| ' '||chr(10)|| ''||chr(10)|| ' END IF;'||chr(10)|| ''||chr(10)|| ' RETURN l_result;'||chr(10)|| ''||chr(10)|| 'END requiredInColor;' , p_render_function => 'requiredInColor' , p_standard_attributes => 'VISIBLE' , p_help_text => '<br />'||chr(10)|| '' , p_version_identifier => '1.0' ); wwv_flow_api.create_plugin_attribute ( p_id => 9198214807522510 + wwv_flow_api.g_id_offset ,p_flow_id => wwv_flow.g_flow_id ,p_plugin_id => 9050220872125872 + wwv_flow_api.g_id_offset
,p_attribute_scope => 'COMPONENT' ,p_attribute_sequence => 1 ,p_display_sequence => 10 ,p_prompt => 'Required Field Color' ,p_attribute_type => 'TEXT' ,p_is_required => false ,p_default_value => '#FEA5AD' ,p_display_length => 7 ,p_max_length => 7 ,p_is_translatable => false ); null; end; / begin wwv_flow_api.g_varchar2_table := wwv_flow_api.empty_varchar2_table; wwv_flow_api.g_varchar2_table(1) := '66756E6374696F6E20666C64526571756972656428666C644E616D652C626B67436F6C6F72290D0A7B0D0A0D0A616 C65727428626B67436F6C6F72293B0D0A0D0A20207656616C7565203D20646F63756D656E742E676574456C656D656 E744279496428'; wwv_flow_api.g_varchar2_table(2) := '666C644E616D65292E76616C75653B0D0A0D0A202069662028217656616C7565290D0A20207B0D0A20202020646F6 3756D656E742E676574456C656D656E744279496428666C644E616D65292E7374796C652E6261636B67726F756E643 D626B67436F6C'; wwv_flow_api.g_varchar2_table(3) := '6F723B0D0A20207D0D0A2020656C73650D0A20207B0D0A20202020646F63756D656E742E676574456C656D656E744 279496428666C644E616D65292E7374796C652E6261636B67726F756E643D2223464646464646223B0D0A20207D0D0 A0D0A7D0D0A'; null; end; / begin wwv_flow_api.create_plugin_file ( p_id => 9186432599158222 + wwv_flow_api.g_id_offset ,p_flow_id => wwv_flow.g_flow_id ,p_plugin_id => 9050220872125872 + wwv_flow_api.g_id_offset ,p_file_name => 'requiredInColor.js' ,p_mime_type => 'application/x-javascript' ,p_file_content => wwv_flow_api.g_varchar2_table ); null; end; / commit; begin execute immediate 'begin dbms_session.set_nls( param => ''NLS_NUMERIC_CHARACTERS'', value => '''''''' || replace(wwv_flow_api.g_nls_numeric_chars,'''''''','''''''''''') || ''''''''), end;'; end; /
set verify on set feedback on prompt ...done
As you can see in Listing 12-8, the plug-in export file contains everything required to rebuild your plug-in in another application, including your JavaScript function. Now, all you have to do is import this plug-in into another application (discussed in the next section) or push it up to one of the public, plug-in repositories.
One of us had a high school chemistry teacher who would not let students use their TI-30 calculators until they could pass a test with a slide rule. While at first this seemed ridiculous, in the end, the additional knowledge was a tremendous help in problem solving. What we just went through (building a plug-in from scratch) was the equivalent of using a slide rule in chemistry class. Although we didn't cover every variation of plug-in development, you've seen the steps required to build one and you have a working example.
When you start thinking about building plug-ins, we highly recommend that you first search the plug-in repositories to see if the capability you need has already been created. While plug-ins are relatively new, there is already a developer community that is building plug-ins and publishing them for anyone to use, free of charge. In many cases, plug-in developers are wrapping jQuery features in APEX plug-in code, taking advantage of jQuery's vast array of features. If you don't already know about jQuery, it's worth a serious look. In general, jQuery is an open source, JavaScript library that enables an amazing array of client-side features with very little coding. Although jQuery is based on JavaScript, it has its own scripting language so there is a learning curve involved. Thanks to the APEX development community, many of the most popular jQuery features have been used to create APEX plug-ins and published so you can use them in your applications. The two main repositories can be found at these locations:
Oracle Applications Express Plug-ins page (www.oracle.com/technetwork/developer-tools/apex/application-express/apex-plug-ins-182042.html
)
APEX-PLUGIN.com
(www.apex-plugin.com
)
Now, instead of building JavaScript, jQuery, or Ajax into your pages manually, you can simply import existing plug-ins that utilize the standardized APEX plug-in interface. If you find a plug-in that almost fits your requirements but not exactly, you can extend an imported plug-in to get exactly what you need. As an added bonus, you can potentially save a lot of time and you may learn a great deal by reviewing code that someone else wrote. We will explore these capabilities by downloading and importing a plug-in from Oracle's repository on the Internet.
In keeping with the Buglist application development process, I wanted to demonstrate a useful jQuery plug-in that would benefit the application. I settled on using the jQuery Star Rating plug-in to allow user feedback on the Edit Bug page. Although I've never seen a bug tracking system that allows the user to rate the development team's ability to address issues, I think it's a good idea.
What this control does is allow the user to hover the mouse over a line of stars and click one of them to represent a 1 out of 9 rating. By default, this control produces a 9-star rating, but by using one of the plug-in's custom attributes, you can choose how many stars should be displayed. The result of using the star rating is an integer between 0 and the number of stars selected. To make this plug-in useful to our application, we'll have to add a column to the Buglist table to store the rating value.
apexdemo@10gR2> alter table buglist add (rating number); Table altered.
We'll start the import process by downloading the Star Rating plug-in from Oracle's Plug-in Repository. You can quickly navigate to this page using the "View Plug-in Repository" button on the Plug-in home page. Figure 12-22 shows the repository page from which you can download the Star Rating plug-in.
The plug-in is downloaded as a ZIP file, the contents of which are shown in Figure 12-23. The file ending in .sql
contains the APEX code related to the exported plug-in. The sub-directories, server, and source contain supporting files like Cascading Style Sheets, JavaScript, jQuery code, and so forth. Not all plug-ins will have this structure, but all of them will have a ".sql" file.
To import the plug-in, you can use the Import feature either at the Application Builder home page or via the Plug-in button on the Plug-in home page. The import process is the same regardless of where you start. In Figure 12-24, the downloaded Star Rating plug-in is selected in the Import wizard. If you start from the Plug-in home page, the file type will be set to Plug-in by default, otherwise you need to set it. The character sets defaults to that of the exported plug-in, but it is recommended that Unicode UTF-8 is used, as this will cover the use of both single and multi-byte character sets.
All that's left to do in the import process is to identify in which application this plug-in will be used. In our example, we chose application 101 BUGLIST.
The result of the installation step is that you are presented with the Edit Plug-ins page populated with the details of the Star Rating, as shown in Figure 12-25. You still have to Apply Changes to finalize the import process.
Your imported plug-in is now ready to use. As discussed previously, we will add the Star Rating plug-in to the Edit Bug page so that the user can rate the developers (what a concept). You add the Star Rating to your page as an Item of type Plug-in. Aside from the normal item attributes, you get to set the number of stars you want in your rating, as shown in Figure 12-26. The default is 9, but of course you can change the plug-in so that it defaults to whatever number you want. For this example, we set the value to 5.
The final step in implementing the Star Rating plug-in is to identify the source for the P29_RATING
item. We created a column in the Buglist table called RATING, so we set the source type to database column and the Database Column Name to RATING, leaving all other attributes at their default value. Now when we run the Edit Bug page, the Rating (or Star Rating to be exact) is shown below the Cost item (our built-from-scratch plug-in). As you can see in Figure 12-27, the user can easily set and view the rating given to this particular bug.
That was pretty easy, especially when compared to creating a simple plug-in from scratch. The simplicity of using this plug-in is a little deceiving in that the developer of the APEX plug-in had to know how to use the jQuery Star Rating feature and also how to wrap it in an APEX plug-in. We didn't show the text of the PL/SQL Code used to build this plug-in, as there just isn't enough space in this book to cover everything that's going on—including how jQuery itself works. Since the source of this plug-in is freely available, we encourage you to evaluate it, download, it and get to know it better my making some changes to it. If you can improve it significantly, maybe you can upload it to the repository so others can get the benefit as well.
You can also test the import process using the Required in Color plug-in we exported in the last section. The process will be the same as the Star Rating plug-in, except that the Required in Color plug-in does not have some of the attributes used in the Star Rating. Simply follow the steps in the import process and the import will be successful.
Plug-ins are a powerful new tool that should be exploited to the fullest. The modularization and standardization created by the plug-in architecture allow, for the first time, easy sharing of standard APEX building blocks. While it was always possible to build client-side code using JavaScript, sharing that code between pages or applications was a much more manual process.
The capabilities afforded by plug-ins is really only limited by a developer's desire (and maybe time and money). We didn't get into details on how to create process type, dynamic action, or region type plug-ins given that we only have one chapter to devote to the subject. We suggest that you go to Oracle's Plug-in repository and try them all out. If you find something useful, use it. If you can improve on it, do so and then give it back to the community.
In our description of the relative difficulty in creating plug-ins from scratch, we were certainly not suggesting that you never do it. What we were saying is that before you write some new, custom functionality, it may be well worth your time to scan the public, plug-in resources to see if what you are planning to do has already been done. Given the popularity and tremendous capability of the jQuery package, you will see more and more jQuery-based plug-ins pop up in the future.