Aug 21

Following on from our previous post about (ab)using on-demand processes, we had a requirement to capture download anchor link clicks, but we needed these to be stored in our own table so we could easily report off it. Essentially we needed our own customized version of “Z?p_url=” (long hand “apex_util.count_click”).

Our page which presents the download link is public and we had created our custom procedure to capture the count clicks in a shared package so we didn’t like the idea of stripping it out and putting it in it’s own package and granting it to the apex_public_user. Since we were already “abusing” on-demand processes we thought that it was the perfect candidate to do the job. The key ingredients are…..

On-Demand process

apr_utils.plugin_clickcount
( p_plugin_id => wwv_flow.g_x01
, p_type      => wwv_flow.g_x02
, p_url       => wwv_flow.g_x03
);

Our query then uses the APEX package call to generate the URL download link (see the following URL for more details), we then encode the link so we can pass it as a parameter in our application process call using x03

SELECT apr_utils.get_plugin_comments(p.plugin_id) comments
,      pc.name category
,      p.name  plugin_name
,      CASE WHEN NVL(dbms_lob.getlength(p.image),0) = 0
            THEN NULL
            ELSE '<img height="115" src="'||apex_util.get_blob_file_src('P7_IMAGE',p.plugin_id)||'" />'
        END plugin_image
,      CASE WHEN NVL(dbms_lob.getlength(p.contents),0) = 0
            THEN NULL
            ELSE CASE WHEN p.mime_type like 'image%'
                      THEN '<img src="'||apex_util.get_blob_file_src('P7_IMAGE',p.plugin_id)||'" />'
                      ELSE '<a href="wwv_flow.show?p_flow_id='||v('APP_ID')||'&p_flow_step_id='||v('APP_PAGE_ID')||'&p_instance='||v('APP_SESSION')||' &p_request=APPLICATION_PROCESS=apr_utils.plugin_clickcount&x01='||p.plugin_id||'&x02=DOWNLOAD&x03='||wwv_flow_utilities.url_encode2(apex_util.get_blob_file_src('P7_CONTENTS',p.plugin_id))||'" alt="'||round(dbms_lob.getlength(p.contents)/1024)||'KB">[ Download ]</a>'
                      END
        END download
FROM   apr_plugins           p
,      apr_plugin_categories pc
,      apr_users             u
WHERE  p.category_id = pc.category_id
AND    p.author_id   = u.user_id

The SQL for storing the count clicks “apr_utils.plugin_clickcount” is very simple, we just need to decode our URL at the end before redirecting to it….

  PROCEDURE plugin_clickcount
  ( p_plugin_id IN apr_plugins.plugin_id%TYPE
  , p_type      IN VARCHAR2
  , p_url       IN VARCHAR2
  )
  AS
  BEGIN
    CASE p_type
      WHEN 'DOWNLOAD' THEN
        UPDATE apr_plugins
        SET    downloads_total = nvl(downloads_total,0) + 1
        WHERE  plugin_id      = p_plugin_id;
        COMMIT;
      WHEN 'REVIEW' THEN
        UPDATE apr_plugins
        SET    reviews_total = nvl(reviews_total,0) + 1
        WHERE  plugin_id      = p_plugin_id;
        COMMIT;
      ELSE
        NULL;
    END CASE;
    wwv_flow_utilities.redirect_url(wwv_flow_utilities.url_decode2(p_url));
  END plugin_clickcount;

The end result is a simple and effective way to have your own PLSQL wrapper procedure that runs before calling a URL and is seamless to the end user. The wrapper procedure can also be secured as you have the ability to set conditions for “On-Demand” processes so it does not pose a gaping security hole. Here’s a screenshot of how we are presenting the download link….

APEX Plugin Registry - Download Link

Aug 17

We found both Ext JS & jQuery to be quite sociable, even when combining both in a single function call. We’ve currently used the star rating in our APR application, and we’ve added an onclick event which will popup an Ext window to add a rating comment (see our previou posts for more details on the Star rating plugin). We only save the rating if someone posts a comment. As a reference we adopted the comment form code from Mark Lancaster’s sample application and made a few tweaks (see the following demo for Mark’s details).

You can see from the source below that we use jQuery to initialize the plugin and add an onclick event, we then use Ext to launch the window, save the data back to Oracle, and then reload the calling grid.

Ext.app.starRatingInit = function (pRegionId) {
   try {
      $('#' + pRegionId + ' input.star').rating();
      $('#' + pRegionId + ' .star').click(function (pThis) {
         gRating = $(this).children().attr("title");
         gRatingId = $(this).attr("id");
         if (!gCommentForm) {
            gCommentForm = new Ext.app.commentForm();
            gCommentForm.on('submit', function () {
               var formValues = gCommentForm.getValues();
               Ext.app.saveStarRating(gRatingId, gRating, formValues.comment, formValues.author);
               Ext.getCmp(pRegionId).getStore().reload();
            });
         } else {
            gCommentForm.reset();
            gCommentForm.show();
         }
      });
   } catch(e) { alert('jQuery might be missing from this page'); }
}
Ext.app.saveStarRating = function (pID, pRating, pComments, pAuthor) {
   var lRequest = new apex.ajax.ondemand('jQuery.widget', function () {
      var l_s = p.readyState;
      if (l_s == 1 || l_s == 2 || l_s == 3) {} else if (l_s == 4) {
         gReturn = p.responseText;
         return gReturn;
      } else {
         return 0;
      }
   });
   lRequest.ajax.addParam('p_widget_name', 'jQuery.pluginRating');
   lRequest.ajax.addParam('x01', pID);
   lRequest.ajax.addParam('x02', pRating);
   lRequest.ajax.addParam('x03', pComments);
   lRequest.ajax.addParam('x04', pAuthor);
   lRequest._get();
}

Note: we put a try catch block around the code in case the jQuery plugin is missing from our APEX page template (it’s not ideal but the quickest solution for now).

The productivity benefits from using both Ext JS & jQuery is unbelievable and the end result is really professional, and from what we’ve found so far is that you don’t need to give up functionality from either by combining them, which means you have even more functionality at your fingertips and no need to reinvent any wheels. Reuse, reuse, reuse!!

APEX Plugin Registry - Plugin Rating Comment

Tagged with:
Aug 13

This post focuses on adding onclick events to anchor links in our sidebar report/list(s) that will set our search page item and reload our grid with the new filter condition via AJAX.

The required ingredients are:

  1. We create our sidebar report using the “Sidebar Region” template and set the report template to “One column unordered list”
  2. The sidebar report contains the following SQL and CSS class setting for identifying the anchor link to transform…
    SELECT '<a href="#" class="ext-grid-reload">'||
           name ||'</a><br />Downloaded '||nvl(downloads_total,0)||' times' plugin
    FROM apr_plugins
    
  3. We create a htmldb_get call to update session state, as when we perform the grid reload, it will execute our report query and filter based on our search item e.g. “P4_REPORT_SEARCH”
    // Used for setting application level items
    Ext.app.apexHttpPost = function (pAppProcess, pItems, pItemValues) {
       var get = new htmldb_Get(null, $v('pFlowId'), 'APPLICATION_PROCESS=' + pAppProcess, $v('pFlowStepId'));
       if (typeof(pItems) == 'object') {
          for (var i = 0, len = pItems.length; i < len; ++i) {
             try {
                get.add(pItems[i], pItemValues[i]);
             } catch(e) {}
          }
       } else { if (pItems) {
             try {
                get.add(pItems, pItemValues);
             } catch(e) {}
          }
       }
       gReturn = get.get();
       get = null;
       return gReturn;
    }
    
  4. We create our javascript transform function….
    Ext.app.anchorGridReloadTransformById = function (pRegionId, pGridId, pItem) {
       Ext.get(pRegionId).select("a[class*=ext-grid-reload]").on('click', function (e) {
          e.stopEvent();
          var el = Ext.get(e.getTarget('a'))
          if (pItem) Ext.get(pItem).dom.value = el.dom.firstChild.data;
          Ext.app.apexHttpPost('Do Nothing', pItem, el.dom.firstChild.data);
          Ext.getCmp(pGridId).getStore().reload();
       });
    }
    
  5. We then update our Grid events “load” and “columnmove” to apply our jQuery star rating transform “Ext.app.starRatingInit(‘grid’ + pRegionID);”
  6. Finally we add to our sidebar report region footer the following….
    <script>
    Ext.onReady(function() {Ext.app.anchorGridReloadTransformById('#REGION_ID#','gridR4352428247890296','P&APP_PAGE_ID._REPORT_SEARCH');});
    </script>
    

The only limitation we have is that we need to hard code the grid ID in our sidebar report region footer, however as this is very customized functionality and implemented only for specific components it’s of no major concern, of course unless your grid ID changes. There’s probably a better solution than to hard code it but for now its staying.

The end result is that we can filter our Grid simply by clicking on anchor links on the page and we negate the need to submit/refresh the page so we get an improved response time and improved usability.

APEX Plugin Registry

preload preload preload