2. Calling out to JavaScript from Oracle Forms 11g

This tutorial introduces the PLSQL built-in web.javascript_eval_expr and shows how to use it to control two Javascript based widgets, a date picker with very rich functionality and an image scroller and viewer.

Approximately 2 hours

Topics

This tutorial covers the following topics:

 

Show all images

Place the cursor over this icon or hit Alt-a (Alt-Shift-a on Firefox) to load and view all the screenshots for this tutorial. Alternatively, you can place the cursor over an individual icon in the following steps to load and view only the screenshot associated with that step. You can hide an individual screenshot by clicking on it.

Overview

After having replaced the Forms menu we turn our attention to adding a few new UI widgets to Forms arsenal. Forms doesn't have a native datepicker. There are some PJC based ones. One is a part of the 10g demo bundle. There are a number of free and commercial Jacascript date pickers (or calendars) out on the market. We are using Dynarch's free version for this tutorial. It has very rich functionality, is free and if even more functionality is needed, a commercial version 2 is available. The screenshots below show some of the styles available. It can popup programmatically at an arbitrary location, automatically popup on almost any event (hover, click and so on) or be statically anchored on a page.

dat1 dat2 dat3

Back to Topic List

Before starting this tutorial, you should:

  1. Have complied with prerequisites stated in previous hands-on tutorials.

  2. Have completed the previous hands-on tutorials in this tutorial.

Back to Topic List

1. Controlling a dHTML date picker

To add the date picker to our page we need to bring in the style sheets and the Javascript code this is what we need to do:

  1. Add these lines inside the <head> tag in the jsdemo.html we created in the first tutorial:

    <link type="text/css" rel="stylesheet" href="JSCal2/src/css/jscal2.css" />
    <link type="text/css" rel="stylesheet" href="JSCal2/src/css/border-radius.css" />
    <link id="skin-win2k" title="Win 2K" type="text/css" rel="alternate stylesheet" href="JSCal2/src/css/win2k/win2k.css" />
    <link id="skin-steel" title="Steel" type="text/css" rel="alternate stylesheet" href="JSCal2/src/css/steel/steel.css" />	
    <link id="skin-gold" title="Gold" type="text/css" rel="alternate stylesheet" href="JSCal2/src/css/gold/gold.css" />
    <link id="skin-matrix" title="Matrix" type="text/css" rel="alternate stylesheet" href="JSCal2/src/css/matrix/matrix.css" />
    <script src="JSCal2/src/js/jscal2.js"></script>
    
    <script src="JSCal2/src/js/lang/en.js"></script>
    <script src="jquery.js"></script>
    <script src="jsdemo.js"></script>
    <script type="text/javascript">
      $(document).ready(function(){
    	   $('#cal_outer').hide();
      });
    </script>

    Lines 1 and 2 brings in core style sheets for the calendar. The next 4 lines makes the skins that we are using available. The 3 next lines incorporates the core Javascript libraries necessary and the last line adds a popular open source Javascript library that we will use to animate the showing and hiding of the calendar. We will also use this library for the next widget covered in this section. More about that later on. For now we just use it to initially hide the calendar with the hide() function available in the library.

  2. Then add this html:

    <div id="header">
    	<img src="img/header.png" alt="hdr"></div>
    <div id="sidebar1">
      <div id="cal_outer">
      </div>
      <script type="text/javascript">
        initCal();
      </script>
    </div>
    

    The addition creates a sidebar that we will move to the right of the screen with some CSS next. The empty <DIV> is the anchor point for the calendar and the call to initCal() initializes the calendar variable that we will use to reference it from Forms. The souirce of the initCal() function follows:

    var myCal;
    var links;
    var skins;
    function initCal () {
      myCal = Calendar.setup({
        cont: "cal_outer",
        weekNumbers: true,
        align: "Tl",
        selectionType: Calendar.SEL_SINGLE,
        titleFormat: "%B %Y",
        showOthers: true
      });
    };
    myCal.addEventListener("onSelect", 
      function () {
        aDate = this.selection.print("%Y-%m-%d").join("\n")
        frmEvent('date_selected', aDate);
      }
    );
    links = document.getElementsByTagName("link");
    skins = [];
    for (i = 0; i < links.length; i++) {
      if (/^skin-(.*)/.test(links[i].id)) {
        id = RegExp.$1;
        skins[id] = links[i];
      }
    };
    

    In addition to declaring three global variables this code initializes the calendar, adds what is often referred to as an event listener (basically a function that will be called when an event happens. In Forms parlance its basically a trigger.) and then numerates the different skins or styles we have declared in the <head> tag.

    Note how the variable cont is set to the id of the empty <div> we made earlier. Note also how the event listener calls the function frmEvent that we used when calling into Forms from the menu. When the user selects a date in the date picker object an event named "date_selected" is sent to he Forms applet bringing with it a payload of the date that was selected, formatted so that Forms can understand it. More on that shortly.

  3. To show the calendar when we open the form we create a WHEN-NEW-FORM-INSTANCE trigger and add this code:

    web.javascript_eval_expr(
      'parent.$("#cal_outer").show(200);'
    );
    

    This trigger code uses the jQuery function show() to reveal the calendar when the form is opened. The function slides the div called 'cal_outer', which is the div that the calendar is anchored to, in from the left and top edges with a duration of 200 ms.

  4. To hide the calendar when closing the the form add a WHEN-WINDOW-CLOSED trigger at the block level and enter the following code:

    web.javascript_eval_expr(
      'parent.$("#cal_outer").hide(200);',
      'PARENT'
    );
    

    This call uses the second parameter of web.javascript_eval_expr. It takes the name of a target window or frame. This suits our purpose fine since the intended target here is the parent frame.

  5. In Forms we are now going to add a trigger on the hiredate column so that the calendar flips to the date in the clicked record. If you haven't already done so, create a default block against the emp table, making it a multirecord block.

    scr

  6. As shown in the screenshot above we need a trigger on the hiredate column. Here is the source for that:

    declare
      expr varchar2(500);
    begin
      expr := '
        parent.myCal.moveTo(
          parent.Calendar.dateToInt(' ||
            to_char(:emp.hiredate, 'YYYYMMDD') ||
          '), true);';
      web.javascript_eval_expr(expr);
    end;
    

    It is unfortunate that Javascript and PLSQL (which this code is a mix of) both use a semicolon as the line ending character. It makes for a very complex looking example snippet. At least the syntax coloring used here colors the Javascript portion all blue. The call to to_char formats the date in the field to a format that the Javascript function dateToInt can understand and make a numeric date out of. The call to the Javascript function moveTo built into the calendar component will flip the calendar UI widget to the date in the Forms field and do it with a flare: it will animate the move, making it somewhat look like the calendar scrolls to the correct date.

  7. Conversely we need to handle the incoming event for when the user selects a date in the calendar. For this purpose we'll add to the WHEN-CUSTOM-JAVASCRIPT-EVENT trigger we created earlier to handle the event named date_selected:

    declare 
      eventName varchar2(30) := upper(:system.javascript_event_name);
      eventValue varchar2(1000) := :system.javascript_event_value;
    begin
      if eventName='COMMIT' then
        commit;
      elsif eventName='FIND' then
        findEmpByEname(eventValue);
      elsif eventName='DATE_SELECTED' then
        :emp.hiredate := to_date(eventValue, 'YYYY-MM-DD');
      else
        null;
      end if;
    end;
    

    The code on line 10 sets the hiredate field to the date selected in the date picker. It comes in as the format 'YYYY-MM-DD' thanks to the event listener code in step 2.

  8. This screenshot shows the user just having selected the date in the date picker and the date as it shows up in the hiredate column.

    scr

  9. The opposite case is true in the following screnshot:

    scr

    This screenshot also shows how the style of the calendar can be controlled using the same menu we control Forms with. The code in the menu definition that changes the style of the calendar to this golden look is this line:

    aI("text=Gold;url=javascript:parent.changeSkin2('gold' );");
    

    Note that in this case we use the JavaScript menu to call to another JavaScript based UI widget. Forms is not involved in this transaction.

Back to Topic List

Now lets interact with a Javascript based image viewer and scroller. Here are two screenshots of what we will build:

scr

The image scroller widget to the right shows the correct image for the department location when we scroll thru the records in the dept block.

scr

If the user clicks the thumbnails this larger view of the image is shown which includes a next and previous link and a darkened background that indicates that the users must close the image view before doing anything else.

The pattern is no doubt familiar by now: include the widget's Javascript files, any CSS needed, the HTML that creates the structure and the initializing, hiding and showing code and finally creating the interaction code both on the Javascript and the Forms side. The following steps will bring everything together.

  1. Add this to the <head> of the jsdemo.html file:

    <link rel="stylesheet" type="text/css" href="lightbox05/css/jquery.lightbox-0.5.css" media="screen" />
    <link rel="stylesheet" type="text/css" media="screen" href="transEffects/css/cycle.css" />
    <script type="text/javascript" src="transEffects/chili-1.7.pack.js"></script>
    <script type="text/javascript" src="transEffects/jquery.cycle.all.min.js?v2.23"></script>
    <script type="text/javascript" src="transEffects/jquery.easing.1.1.1.js"></script>
    <script type="text/javascript" src="lightbox05/js/jquery.lightbox-0.5.js"></script>
  2. Add to the ready state [learningjquery.com] that jQuery provides so that it looks like this:

    $(document).ready(function(){
      $("#s4").before('<ul id="nav">').cycle({ 
        fx: 'fade,scrollDown,scrollUp,scrollLeft,scrollRight',
        speed: 'slow', 
        timeout: 0, 
        pager:  '#nav',
        pagerEvent: 'mouseenter',
        slideExpr: 'img',
        /*Callback func that creates a thumbnail to use as pager anchor
          and ties the cycle plugin and the lightbox plugin together by
          creating a link to a larger version of the image */
        pagerAnchorBuilder: function(idx, slide) { return 
           '<li><a href="' + mkBgFileName(slide.src) + 
                '" title="' + slide.title + '">' +
               '<img src="' + slide.src + '"' +
                 'width="50" height="42">' 
               '</a>' +
           '</li>';
        }		
      });
      $("#cal_outer").hide();
      var event = "'find'"; //this value is specified here because
                            //there are too many contexts in the next call
      $("#slideshow img").wrap( /* Surround the img tags in the slide show */
        '<a href="#" ' +        /* with an anchor so clicking on it will query */
         'onclick="frmEvent(' + event + /* for the corresponding location */
         ', this.firstChild.title)"></a>' /* in the dept table */
      ); /* step 4 shows the resulting HTML with these <a> tags commented out */
      $("#slideshow").hide();
    });
    

    This initializes the image scroller and makes links to the larger images for the image viewer (in the pagerAnchorBuilder function). In this example the image scroller and viewer appears to be one and the same but they are in fact a duo. See here for the scroller and here for the image viewer.

  3. The source for the mkBgFileName function is below. Put it in the jsdemo.js file. It appends a "-big" suffix to the file name so the image viewer widget selects the big version rather than the half-size one shown in the page initially.

    function mkBgFileName (dt) {
    	var rtrn=dt.split(/\./);
    	return rtrn[0]+'.'+rtrn[1]+'.'+rtrn[2]+'.'+rtrn[3]+'-big.jpg';
    };
    
  4. The images themselves are referenced with this html snippet placed inside the <div id="sidebar">. Please ignore the commented code. It is inserted at runtime by the initializing code and doesn't have to be added here. See the .wrap() call in step 2.

    <div id="slideshow">
      <div id="s4" class="pics">
        <!--a href="#" onclick="frmEvent('find', this.firstChild.title)"-->
          <img src="img/newyork.jpg" title="New York">
        <!--/a-->
        <!--a href="#" onclick="frmEvent('find', this.firstChild.title)"-->
          <img src="img/dallas.jpg" title="Dallas">
        <!--/a-->		
        <a href="#" onclick="frmEvent('find', this.firstChild.title)"-->
          <img src="img/chicago.jpg" title="Chicago">
        <!--/a-->
        <!--a href="#" onclick="frmEvent('find', this.firstChild.title)"-->
          <img src="img/boston.jpg" title="Boston">
        <!--/a-->
        <!--a href="#" onclick="frmEvent('find', this.firstChild.title)"-->
          <img src="img/stockholm.jpg" title="Stockholm">
        <!--/a-->
      </div>
    </div>
    

    Clicking (accomplished with the <a> tag shown here as a comment because it is inserted as a part of the initialization. See line 24-28 in step 2.) on the image will send an event called 'find' with a payload of the name of the city it depicts, taken from the title attribute of the <img> tag the <a> tag surrounds (this.firstChild.title).

  5. The event handling code is next:

    declare 
      eventName varchar2(30) := upper(:system.javascript_event_name);
      eventValue varchar2(1000) := :system.javascript_event_value;
    begin
      if eventName='COMMIT' then
        commit;
      elsif eventName='FIND' then
        findDept(eventValue);
      else
        null;
      end if;
    end;
    
  6. The Forms procedure findDept is as follows:

    procedure findDept(theName varchar2) is
      whereClause varchar2(300);
      colmn varchar2(50);
      oper varchar2(2);
      literal varchar2(50);
      singleQuote varchar2(2) := '''';
    begin
      if theName is not null then 
        colmn := 'loc';
        oper := '=';
        literal := singleQuote || upper(theName) || singleQoute;
        whereClause := colmn || oper || literal;
      else 
        whereClause := 'deptno is not null';		
      end if;
      clear_form(no_validate);
      go_block('dept'); 
      set_block_property('dept', default_where, whereClause);
      execute_query;
    end;
    
  7. To change image when we scroll records in Forms, create a WHEN-NEW-RECORD-INSTANCE trigger with this code:

    begin
      if :dept.deptno is not null then
        web.javascript_eval_expr(
          'parent.$("#s4").cycle('||
             to_char((:dept.deptno/10)-1)||
          ');'
        );
      else
        null;
      end if;
    end;
    

    This code calls out to the image cycler and makes it show the image in the sequence corresponding with the deptno in the record.

  8. To hide the widget when the user closes the form, add a WHEN-WINDOW-CLOSED trigger with this code:

    begin
      web.javascript_eval_expr(
        'parent.$("#s4").hide(300);'||
        'parent.$("#nav").hide(300);'
      );
      clear_form(no_validate);
      close_form('dept');
    end;
    

    The second call to the hide function is due to the fact that the scroller widget creates an object at runtime with an id of "nav". See the chained call .before() on line two of the initialization code in step 2. (jQuery makes good use of chained function calls which, in the author's opinion, makes jQuery code very easy to read and understand).

  9. Before we can try clicking on the thumnail row above the image scroller we need to alter the lightbox JS file thus:

  10. Find the lines that look like these two (they are in two different parts of the jquery.lightbox-0.5.js file):

    $('embed, object, select').css({ 'visibility' : 'visible' });
    $('embed, object, select').css({ 'visibility' : 'hidden' });
    

    and make them look like these, respectively:

    $('embed, object, select, iframe').css({ 'visibility' : 'visible' });
    $('embed, object, select, iframe').css({ 'visibility' : 'hidden' });
    

    The two lines are basically an attempt to workaround a bug in IE that makes certain HTML elements (among them our iFrame) not adhere to the Z-ordering, making them be drawn on top of all other elements. Out of the box the lightbox05 code does not handle iframes so these additions make it work with our application.

    Now you can try the demo out. Click on the thumbnail and watch the large version of the city image. Hover you mouse close to the right or left image border to have a "Next" and "Prev" button appear, making it possible to scroll through the large images as well. What is left to do, as an exercise for you, the reader, is to synchronize the underlying scroller and the form record as the user click these buttons.

What we have at the end of this tutorial is a simple application with two forms and a external menu that can control both the Forms part of the application and the Javascript portion.

The first form is a block against the emp table with a date column that is conncted to a very rich featured dHTML calendar. The second form runs against the dept table and can show images relevant to the current record in two different ways: as large thumbnails and as a full size image set in a lightbox like environment.

Find an online demoreel on OTN of the functionality we have just built.

Back to Topic List

Let's make it possible to toggle the JS menu from Forms. We'll do it by creating a runtime function with PLSQL code that we can then call from Forms.

  1. Create a button and enter this PLSQL code in the WHEN-BUTTON-PRESSED trigger:

    web.javascript_eval_expr(
      'function toggleFormsMenu(apltdiv){
         el=document.getElementById(apltdiv);
         if (el.className == "nomenu")
           el.removeAttribute("className");
         else
           el.className = "nomenu";
       }'
    );
    

    This PLSQL code creates a new function, one that did not exist until the invokation of this code, in the page that the forms applet was started from (or to be exact, the parent of that page). This is possible because Javascript is interpreted or, as in some browsers, compiled on demand.

    The possibilities are endless and this toggle function is just a small example of what is possible. Your Forms application could potentially create all the code needed for an entire browser based application as its starts up and later on interact with the application it just created.

  2. To toggle the Forms menu all you have to do now is this:

    web.javascript_eval_expr ( 'toggleFormsMenu("aplt");' );
    

    The literal we pass into the function is the id of the div that we hide with CSS. See line 13 in point 7 in section 2.1 of the first page of this tutorial.

In this section we have explored using Javascript based UI widgets as extensions to Forms widget set. We saw how we can make use of these widgets in a seemless way. We also had a look at how we can create Javascript code from a Forms based application.

Back to Topic List

Hide all images Place the cursor over this icon or press Shift-Alt-h to hide all screenshots.