Creating Sencha AJAX Portlets and Widgets

This how-to document guides you through the process of developing GUI elements using the SENCHA EXTJS library. It will show you how to extend ]project-open[ without having to know the TCL language.


  • Introduction
  • Prerequisites
  • Four simple steps to build your first AJAX Portlet
    • Step 1: Creating the Portlet Frame
    • Step 2: Loading the SENCHA libraries
    • Step 3: Building the JS code
    • Step 4: Deploy your Widget
  • Working with custom Data Sources 
  • Using KPI Gauges


The ]po[ decided to use Sencha ExtJS to build enterprise-grade, feature-rich user interfaces. You can learn more about ExtJS here .

There are several ways how ExtJS applications are integrated into ]project-open[:

  • ExtJS Component Portlets
  • ExtJS Page Elements
  • ExtJS Pages


The following ]po[ packages need to be installed on your server:

  • ]project-open[ REST Web Service Interface (intranet-rest)
  • ]project-open[ Sencha Core (sencha-core)
  • ]project-open[ Sencha V4.2.1 (sencha-extjs-v421)
  • ]project-open[ Sencha V4.2.1 Development (sencha-extjs-v421-dev - only required in development environment)
  • A symbolic link to the SENCHA resources folder: /web/projop/www/resources -> ../packages/intranet-sencha/www/resources/

Four simple steps to create your first ExtJS Portlet

Step 1: Creating the Portlet Frame

The code for the portlet's frame should be placed in the Package Library:




ad_proc -public im_list_rfqs_component {} {
    Returns a component that list all current RFQ together with their status
    and action options, such as "Accept/Deny Quote".

} {
    set user_id [ad_get_user_id]
    set html_output "<div id='gridRFQ'></div><br>"

    append html_output "<script language='javascript'>"
    append html_output [ad_parse_template "/packages/intranet-customer-portal/www/resources/js/rfq-list.js"]
    append html_output "</script>"

    return $html_output
  • This file only contains the frame
  • JS code is swaped out to another file to facilitate char encoding 
  • Please note that each component should have its own 'namespace'

Step 2: Loading the ExtJS libs

This step is required if you plan to use the SENCHA ExtJS libs.

# Loading ExtJS libs

Step 3: Building the JS code



// General Settings

    enabled: true

// set local blank image
Ext.BLANK_IMAGE_URL = '/intranet/images/cleardot.gif';


// create namespace
Ext.namespace('RFQPortlet'); = function() {
   return {
    // public properties, e.g. strings to translate

    // public methods
    init: function() {

        etc. ............... [see original file]



Step 4: Deploy your Widget

SELECT  im_component_plugin__new (
        null,                        -- plugin_id
        'acs_object',                -- object_type
        now(),                       -- creation_date
        null,                        -- creation_user
        null,                        -- creation_ip
        null,                           -- context_id
        'Requests for Quote',           -- plugin_name
        'intranet-customer-portal',     -- package_name
        'right',                        -- location
        '/intranet/index',              -- page_url
        null,                           -- view_name
        1,                              -- sort_order
        'im_list_rfqs_component'        -- component_tcl

Working with Custom Data Sources

We start off by defining a SQL query that provides us with the data that we want to show.

select  p.project_status_id,
        im_category_from_id(p.project_status_id) as project_status,
        sum(coalesce(p.presales_value, p.cost_quotes_cache) * coalesce(p.presales_probability, 0.01)) as value
from    im_projects p
where   p.parent_id is null and
        p.project_status_id in (select * from im_sub_categories(71))
group by p.project_status_id
order by p.project_status_id

In this query we are looking at the project values "presales_value" and "presales_probability" in order to calculate the value of the portfolio:

  • coalesce(...): We also can take the quoted amount of the project if the presales values is not defined and "1%"
    if the probability is not defined. "coalesce" returns the first of its arguments that is not null.
  • p.parent_id is null: We only want to look at main projects (exclude sub-projects that have parent_id set).
  • p.project_status in (select * from im_sub_categories(71)): "71" represents the project status "potential".
    Below "potential" there can also be additional states which im_sub_categories() will return.

We will now "deploy" this SQL as a dynamic report in the Reporting package:

  • Go to Reporting -> New Report and enter the SQL into the Report SQL field.
  • Other fields: Name = "REST Presales Pipeline", Report Code = "rest_presales_pipeline",
    Report Group = "REST System Reports", Sort Order = 10,
    Description = "Returns the value and probability of potential projects."
  • Go to Reporting and click on the "REST Presales Pipeline" link so check the results of the report.
    Please go to Projects and create a number of potential projects with presales values and presales probability in order to create reasonable sample data for your data source.

Now we will check the JSON data of this report. Please observe the URL of the page showing the report results, where we are not going to add a URL parameter "format=json".

  • http://<server>/intranet-reporting/view?report_id=64012&format=json
As an alternative to report_id we can also use the report code that we entered during the creation of the report. This is the recommended way to reference a report, because the report_id will be different across different ]po[ systems:
  • http://<server>/intranet-reporting/view?report_code=rest_presales_pipeline&format=json

Using KPI Gauges

The following extension is used to create KPI Gauges:
To remove value (field), apply the following patch:

[efficientip@aachen ~]$ cvs diff --context ~/packages/sencha-core/www/ux/chart/series/KPIGauge.js
Index: ./www/ux/chart/series/KPIGauge.js
RCS file: /home/cvsroot/sencha-core/www/ux/chart/series/KPIGauge.js,v
retrieving revision 1.1
diff --context -r1.1 KPIGauge.js
*** ./www/ux/chart/series/KPIGauge.js   30 Jun 2014 12:52:06 -0000      1.1
--- ./www/ux/chart/series/KPIGauge.js   18 Dec 2015 09:50:48 -0000
*** 134,147 ****
                                        'stroke-width': me.needle.width || 2,
                                        'stroke': me.needle.pivotFill || '#222'
!                               me.valueSprite = me.chart.surface.add({
!                                       type: 'text',
!                                       text: value,
!                                       fill: me.needle.pivotFill || '#222',
!                                       font: '18px Arial',
!                                       x: centerX - (pivotRadius),
!                                       y: centerY - (me.radius / 2)
!                               });
                        } else {
                                if (animate) {
                                        me.onAnimate(me.needlePivotSprite, {
--- 134,147 ----
                                        'stroke-width': me.needle.width || 2,
                                        'stroke': me.needle.pivotFill || '#222'
!                               // me.valueSprite = me.chart.surface.add({
!                               //      type: 'text',
!                               //      text: value,
!                               //      fill: me.needle.pivotFill || '#222',
!                               //      font: '18px Arial',
!                               //      x: centerX - (pivotRadius),
!                               //      y: centerY - (me.radius / 2)
!                               // });
                        } else {
                                if (animate) {
                                        me.onAnimate(me.needlePivotSprite, {

  Contact Us
  Project Open Business Solutions S.L.

Calle Aprestadora 19, 12o-2a

E-08907 Hospitalet de Llobregat (Barcelona)

 Tel Europe: +34 932 202 088
 Tel US: +1 415 429 5995