Skip to main content

Creating an APEX plugin for an Oracle JET component - Part 2

In my previous blogpost I showed how you can embed an Oracle JET component in your APEX application. Now it is time to make a plugin out of the wisdom we gained doing so.
First of all a disclaimer. My intention is to make this plugin and the inner workings as simple as possible. So you can add a lot more functionality, checks etc and therefore add complexity. But this is intended to be as simple as possible.

The plugin consists of three parts: a PL/SQL render function, a snippet of JavaScript and a PL/SQL ajax function.

The render function is defined as :

function render 
( p_region                in  apex_plugin.t_region
, p_plugin                in  apex_plugin.t_plugin
, p_is_printer_friendly   in  boolean 
) return apex_plugin.t_region_render_result 
is

  c_region_static_id      constant varchar2(255)  := apex_escape.html_attribute( p_region.static_id );

begin
  -- Add placeholder div
  sys.htp.p (
     '<div class="a-JET-PictoChart" id="' || c_region_static_id || '_region">' ||
       '<div class="a-JET-PictoChart-container" id="' || c_region_static_id || '_chart"></div>' ||
     '</div>' );
     
  -- Load the JavaScript library   
  apex_javascript.add_library 
  ( p_name      => 'pictoChart'
  , p_directory => p_plugin.file_prefix
  );
  
  -- Initialize the chart
  apex_javascript.add_onload_code
  ( p_code => 'jet.picto.init('||
                  '"#'||c_region_static_id||'_chart", '          || -- pRegionId
                  '"' || apex_plugin.get_ajax_identifier ||'"'   || -- pApexAjaxIdentifier
                 ')'
  );
  
  return null;
end render;

So what it does in these three steps is :
1. Generate a DIV placeholder, just as we saw in that previous post
2. Load the JavaScript file "pictoChart.js",
3. Execute the JavaScript function "jet.picto.init" providing two parameters, the regionId of the DIV and the (internal) identifier of the PL/SQL ajax function.

The contents of the JavaScript file is :

! function (jet, $, server, util, debug) {
    "use strict";
    requirejs.config({
        baseUrl: apex_img_dir + "oraclejet/js/libs",
        paths: {
            "jquery": "jquery/jquery-2.1.3.min",
            "jqueryui-amd": "jquery/jqueryui-amd-1.11.4.min",
            "ojs": "oj/v2.0.0/min",
            "ojL10n": "oj/v2.0.0/ojL10n",
            "ojtranslations": "oj/v2.0.0/resources",
            "promise": "es6-promise/promise-1.0.0.min"
        },
        shim: {
            jquery: {
                exports: ["jQuery", "$"]
            }
        }
    }), jet.picto = {
        init: function (pRegionId, pApexAjaxIdentifier) {
            require(["ojs/ojcore", "jquery", "ojs/ojpictochart"], function (oj, $) {
                server.plugin(pApexAjaxIdentifier, {}, {
                    success: function (pData) {
                        $(pRegionId)
                            .ojPictoChart(pData);
                    }
                });
            });
        }
    }
}(window.jet = window.jet || {}, apex.jQuery, apex.server, apex.util, apex.debug);
// To keep ThemeRoller working properly:
define("jquery", [], function () {
    return apex.jQuery
});

The first function call (requirejs.config) is again the same is we did earlier. Please note that the paths mentioned in here might differ in your environment. You could define those paths as (Application level) Component Settings, but for simplicity I keep them hardcoded in this example.
Notice there is a "jet" namespace defined and within that namespace a nested "picto" namespace. So we could easily extend this file (after renaming it) to other chart types. The "init" method defines the required files and then calls apex.server.plugin passing the PL/SQL ajax function as a parameter. After this ajax function is called, the result - a JSON object - is passed to the ojPictoChart function.
On the last line, as Kyle Hu correctly commented in my previous post, we have to define jquery in order to make ThemeRoller work (again).
So in fact, the JavaScript is very straightforward: in the end just an ajax call passing the result into the ojPictoChart function.

So what is the magic of the last piece, the ajax PL/SQL function? Here it is::

function ajax
( p_region    in  apex_plugin.t_region
, p_plugin    in  apex_plugin.t_plugin 
) return apex_plugin.t_region_ajax_result
is
  c       sys_refcursor;
  l_query varchar2(32767);
begin  
  l_query := p_region.source;
  open c for l_query;
  apex_json.open_object;
  apex_json.write('items', c);

  -- add settings
  apex_json.write('animationOnDisplay' , p_region.attribute_01);
  apex_json.write('columnCount'        , p_region.attribute_02);
  apex_json.write('layout'             , p_region.attribute_03);

  apex_json.close_object;
  return null;
end ajax;

Thus it just takes the region source - a SQL statement -, executes it returning a JSON object. And to these results three of the available options, defined as plugin attributes, are added. Again, as simple as possible. So no validation on the correctness of the SQL (as all APEX Developers can write a correct SQL statement, right). And just a minor part of the available options are exposed in the plugin.

When you create a region based on this plugin you have to provide it with a correct SQL statement - one that will return a valid JSON object according to the pictoChart docs. For example:

select ename||' - '||job||'@'||dname "name"
,      'human' "shape"
,      1 "count"
,      case job
       when 'PRESIDENT' then 'black'
       when 'ANALYST'   then 'blue'
       when 'CLERK'     then 'green'
       when 'MANAGER'   then 'red'
       when 'SALESMAN'  then 'yellow'
       end "color"
from   emp
       join dept on emp.deptno = dept.deptno
order by 2 desc, 4

And if you set the settings as :


You'll get as a beautiful result:
So knowing this, it wouldn't be hard to rebuild the plugin by yourself. But for the lazy readers out there, you can download it here too.
Or probably build another plugin for another JET component!

Comments

Popular posts from this blog

How to create neatly formatted Excel documents using PL/SQL?

If there is a requirement to produce output from an application into Excel, you would probably create a CSV (Comma Separated File) with the data and start Excel to show the data - at least that's what I did...until now. The drawback of this solution is that you could only produce data and no nice layout. But Excel is also capable of opening HTML-files and using this you could create Excel files with data and magnificent layout! Let me give an example: 1. Create a procedure to show the data in formatted in an HTML table. CREATE OR REPLACE PROCEDURE display_emp_list IS v_emp_count NUMBER(5); v_empno NUMBER(8); v_ename VARCHAR2(50); v_job emp.job%TYPE; v_sal emp.sal%TYPE; v_bg_color VARCHAR2(10) := ''; CURSOR c_emp IS SELECT empno, initcap(ename), job, sal FROM emp ORDER BY ename; BEGIN SELECT COUNT(*) INTO v_emp_count FROM emp; owa_util.mime_header('application/ms-excel', FALSE); htp.p('Content...

Refresh selected row(s) in an Interactive Grid

In my previous post I blogged about pushing changed rows from the dabatase into an Interactive Grid . The use case I'll cover right here is probably more common - and therefore more useful! Until we had the IG, we showed the data in a report (Interactive or Classic). Changes to the data where made by popping up a form page, making changes, saving and refreshing the report upon closing the dialog. Or by clicking an icon / button / link in your report that makes some changes to the data (like changing a status) and ... refresh the report.  That all works fine, but the downsides are: The whole dataset is returned from the server to the client - again and again. And if your pagination size is large, that does lead to more and more network traffic, more interpretation by the browser and more waiting time for the end user. The "current record" might be out of focus after the refresh, especially by larger pagination sizes, as the first rows will be shown. Or (even wors...

APEX ReadOnly Pages - The easy way

If your Oracle APEX Application requires different types of access - full access or readonly - for different types of users, you can specify a Read Only Condition on Page level (or Region, Item, Button, etc.).  You can set an Authorization Scheme on Application level, so it'll be applied to all pages. So if you have an Authorization Scheme named 'User Can Access Page' defined by a PL/SQL function like this: return apex_authorization.user_can_access_page ( p_app_id  => :APP_ID , p_page_id => :APP_PAGE_ID , p_user    => :APP_USER );  then you can code all the logic in the database using the APEX Repository, your own tables or a combination to define whether a user has access to that page or not. But alas it is not possible to define something similar Application wide for a Read Only condition. You can specify an Authorization Scheme 'User has Read Only Access' using a similar signature as the one above and use that on each and e...