Oracle Forms Look & Feel Project: Developer Guide
Oracle Forms Look & Feel Project: Developer Guide
Developer guide
Francois Degrelle
http://fdegrelle.over-blog.com/
forms.pjc.bean@free.fr
Content
Introduction..........................................................................................4
Warning................................................................................................5
LAF Project presentation..........................................................................6
System configuration..............................................................................9
Understanding the components...............................................................10
The CSS file......................................................................................10
The laf.pll PL/SQL library....................................................................11
The Java Beans and the Pluggable Java Components..............................21
The DrawLaf Java Bean...................................................................21
The ImageViewer Java Bean............................................................23
The LAF_LOV Java Bean..................................................................25
The LAF_Map Java Bean..................................................................26
The Pluggable Java Components (PJCs).............................................27
Implementation in the Forms modules.....................................................30
Download the LAF Project zip file............................................................31
Examples............................................................................................35
Acknowledgements...............................................................................41
Developer list.......................................................................................42
Introduction
When you are using a product to develop a piece of software, you often
(always?) have to push it to its limits. Sometimes, these limits are not enough,
and you are stuck, then it requires a lot of imagination to find workarounds.
Even though your application is just fine, answering the end-user questions
perfectly and providing the right functions, there is often (always?) a weak
point, and sometimes, the weak point is the “design”, the “look”.
As is well known, you can not please everyone, so that you will probably have
to manage different feelings ranging from “Whow, beautiful!” to “Arg, it is so
ugly!”
Mixing all these points is a good way for introducing the Forms Look and Feel
Project.
What better way to please everyone by letting them select their own look ?
Is it the “raison d'être” of the Cascading Style Sheets, so that, what about
using the CSS system in an Oracle Forms application?
This is exactly what the Forms Look and Feel Project does. Using a CSS file to
adapt the look of a Forms Application at runtime.
Warning
This tool does not come from Oracle and is not supported by the Oracle
Support.
Do not open Service Request on Metalink to ask questions about it.
Instead, send emails to the following email address:
forms.pjc.bean@free.fr
Use also the email or the forum to report any problem you could encounter
during installation, configuration, using or simply understanding the tool.
There is no license attached to this project. It is open source, and you can use,
modify and distribute it as you want without any authorisation of any kind.
LAF Project presentation
The Look and Feel Project is a set of tools that permits decorating a Forms
module at runtime.
All the decoration information is stored and read from an external CSS file,
then applied to the Forms elements at runtime.
The tool is made up of a PL/SQL library (laf.pll), a set of Java Beans and PJCs
grouped in a jar file (laf_xxx.jar), and an external file containing the
information tags (CSS).
Another goal is to give the table blocks a more “HTML” look. Each block is
divided in three sections, that can be decorated separately:
Go to the CSS file section to see a complete description of all the tags you can
use.
System configuration
The zip file of the project contains a /Java/JAR sub-folder with four files:
Depending of the Forms version you use, copy the corresponding JAR file to
your /forms/java folder.
Note:
the provided JAR files are already signed.
If you rebuild them, you will have to sign them.
You also have to add the JAR file name to the archive tag of your
/forms/server/formsweb.cfg configuration file.
...
archive=frmall.jar,laf_1012.jar
...
note:
We update the archive tag and not the archive_jini because this tool uses
methods available in the 1.4 JRE, so it won't run with the Jinitiator.
Note:
If you intend to play MP3 sound files, add also the jl1.0.jar file in your
/forms/java folder and to the archive tag.
It can be loaded from the following URL:
http://prdownloads.sourceforge.net/javalayer/jlayer1.0.zip?download
To achieve the goal of separating the decoration information from the Forms
module, each element's decoration attributes are stored in an external file,
allowing the developer to change the Look and Feel at any time without any
module recompilation.
It looks like a “real” CSS file, even if the tags are Forms dedicated.
It contains sections of five different types:
The tags included in sections of type canvas are read by the Paint_Canvas()
procedure stored in the laf.pll PL/SQL library.
The tags included in sections of type title, header and body are read by the
Paint_Block() procedure stored in the laf.pll PL/SQL library.
The tags included in sections of type gui are read by the Set_GUI_Properties()
procedure stored in the laf.pll PL/SQL library.
Doc: The description of all available tags can be read from the CSS file
properties documentation.
The laf.pll PL/SQL library
It contains the functions and the procedures needed to open the CSS file,
read the tags from it, then perform the drawing job through the
associated Java Bean’s methods.
Here are the four main procedures the developer will use in the Forms triggers:
PKG_LOOK_AND_FEEL.Open_Css()
PKG_LOOK_AND_FEEL.Paint_Canvas()
PKG_LOOK_AND_FEEL.Paint_Block()
PKG_LOOK_AND_FEEL.Set_GUI_Properties()
e.g.
If PKG_Look_And_Feel.Open_Css(:PARAMETER.PM$CSS_FILENAME) Then
-- ok, we can use the painting methods --
...
End if;
● Paint the canvas
Procedure Paint_Canevas
(
PC$Class IN Varchar2, -- Canevas CSS class name
PC$BeanName IN Varchar2 -- associated bean area
) ;
This procedure is used to paint the given canvas where the PC$BeanName
Bean Area is located. Every tag contained in the PC$Class CSS section name is
applied on the canvas. The CSS class name given must be of type: canvas
e.g.
-- paint the canevas that supports the :CTRL.BEAN Bean Area --
-- with the content of the .maincanvasOracle CSS section
PKG_LOOK_AND_FEEL.Paint_Canevas('.maincanvasOracle', 'CTRL.BEAN' ) ;
Procedure Paint_Block
(
PC$Block IN Varchar2, -- the block name to decorate
PC$BeanName IN Varchar2, -- the associated bean area
PC$VA_Name IN Varchar2, -- the visual attribute associated
PC$HeadClass IN Varchar2, -- the table header CSS class name
PC$BodyClass IN Varchar2, -- the table body CSS class name
PC$TitleClass IN Varchar2 Default Null, -- the table title CSS class name
PC$Title IN Varchar2 Default Null, -- the block title
PB$ScrollBar IN Boolean Default True, -- scrollbar exists on block
PB$SortBlock IN Boolean Default Null -- can sort the table_block
) ;
The PC$VA_Name parameter (*) is the name of the Visual Attribute used to
colour each other row.
The PC$HeadClass parameter indicates which CSS section to use to paint the
table header. It must be of type: header
The PC$BodyClass parameter indicates which CSS section to use to paint the
table body. It must be of type: body
The PC$TitleClass parameter indicates which CSS section to use to paint the
table title. It must be of type: title and is not required.
If indicated, the PC$Title contains the title text you want to draw.
The PB$ScrollBar parameter indicates if you allow the procedure to move the
scrollbar. Sometimes, the procedure may have to move the items between
each other to draw the lines, then the addition of these moves can need to
push the scrollbar away. If this parameter is set to FALSE, the scrollbar won't
be moved.
The PB$SortBlock parameter indicates if the user can sort the block by clicking
the table header.
Since the 1.3.8 version, PC$VA_Name argument is obsolete, as you can
(*)
e.g.
PKG_LOOK_AND_FEEL.Paint_Block
(
PC$Block => 'EMP'
,PC$BeanName => 'CTRL.BEANTAB'
,PC$VA_Name => :PARAMETER.PM$VA
,PC$HeadClass => '.tableHeaderOracle'
,PC$BodyClass => '.tableBodyOracle'
,PC$TitleClass => '.tableTitleOracle'
,PC$Title => 'Oracle BLAF Look and Feel'
,PB$ScrollBar => True
) ;
Procedure Set_GUI_Properties
(
PC$Class IN Varchar2, -- GUIs CSS class name
PC$BeanName IN Varchar2 -- the associated bean area
) ;
This procedure is used to decorate the other Forms elements, like Window
caption, menu bar, status bar, elements and so on.
It need the CSS section name and the Bean Area name.
The CSS class name must be of type: gui
e.g.
-- set the global GUI properties --
PKG_LOOK_AND_FEEL.Set_GUI_Properties( '.GUIPropertiesOracle',
'CTRL.BEAN' ) ;
Function Get_Tag_Value
(
PC$Section IN Varchar2, -- CSS section name
PC$TagName IN Varchar2, -- CSS tag name
PC$Default IN Varchar2 Default 'none' -- default value
) Return Varchar2 ;
It is used to return the value of the given section/tag name. If the tag is not
found, it returns the PC$Default value if given, else it returns NULL.
e.g.
-- get the value of the font-family tag in the .tableHeaderOracle section --
LC$Value := Get_Tag_Value( '.tableHeaderOracle', 'font-family');
Procedure Set_Tag_Value
(
PC$Section IN Varchar2, -- CSS section name
PC$TagName IN Varchar2, -- CSS tag name
PC$TagValue IN Varchar2 -- CSS tag value
);
e.g.
-- update the value of the tag --
Set_Tag_Value( '.tableHeaderOracle', 'font-family', 'Arial' );
Procedure Add_Tag_Value
(
PC$Section IN Varchar2, -- CSS section name
PC$TagName IN Varchar2, -- CSS tag name
PC$TagValue IN Varchar2 -- CSS tag value
);
Procedure Remove_Tag_Value
(
PC$Section IN Varchar2, -- CSS section name
PC$TagName IN Varchar2 -- CSS tag name
);
This function returns a collection of every tags read in the CSS file.
The return type is a collection of records.
● Get all tags of a given section
Procedure Set_Section_Tags
(
PC$Section IN Varchar2, -- CSS section name
PT$TTags IN TYP_TAB_TAG -- Array of tags found
) ;
The visual attribute used to paint the record is the one given in the
Paint_Block() method.
Function Split
(
PC$Chaine IN VARCHAR2, -- input string
PN$Pos IN PLS_INTEGER, -- token number
PC$Sep IN VARCHAR2 DEFAULT ',' -- separator character
) Return Varchar2 ;
This function returns the nth token in a delimited string.
Given the original string is: 'one,two,free,four' and you want to get the second
element, proceed as follows:
Declare
LC$Value Varchar2(100);
LN$I Pls_Integer := 1 ;
Begin
Loop
LC$Value := Split( 'one,two,free,four', LN$I ) ;
Exit When LC$Value Is Null ;
...
LN$I := LN$I + 1 ;
End loop;
End;
● Get the pixel value corresponding to the current coordinate system given
value
Function To_Pixel
(
PN$Coord1 In Number,
PN$Coord2 In Number,
PC$Separator In Varchar2 Default '|'
)
Return Varchar2 ;
Function To_Current_Coord
(
PN$Coord1 In Number,
PN$Coord2 In Number,
PC$Separator In Varchar2 Default '|'
)
Return Varchar2 ;
PROCEDURE init_laf_blocks
(
PC$Blk1 in varchar2 default null
,PC$Blk2 in varchar2 default null
,PC$Blk3 in varchar2 default null
) ;
PROCEDURE Copy_From_block
(
PC$Block in varchar2
,PC$Bean in varchar2
,PB$Header in boolean default FALSE
,PN$From in pls_integer default 1
,PC$Items in varchar2 default null
,PC$FieldSep in varchar2 default CHR(9)
) ;
e.g.
-- export all columns plus header --
Pkg_Tools.Copy_from_block('USR_TABLES','LAF_BLOCK.LAF_BEAN');
–- export 3 columns without header –
Pkg_Tools.Copy_from_block('USR_TABLES','LAF_BLOCK.LAF_BEAN',false,1,
,'EMPNO,SAL,COMM');
PROCEDURE Paste_to_block
(
PC$Block in varchar2
,PC$Bean in varchar2
,PB$Header in boolean default FALSE
,PN$From in pls_integer default 1
,PC$Items in varchar2 default null
,PC$FieldSep in varchar2 default CHR(9)
) ;
e.g.
-- get the clipboard content --
Set_Custom_Property( 'LAF_BLOCK.LAF_BEAN', 1, 'PASTE_FROM_CLIPBOARD', '');
It is used to highlight the current record when its Items Implementation class
is set to oracle.forms.fd.LAF_XP_TextField or oracle.forms.fd.LAF_XP_TextArea,
and a gradient background is given via the SET_GRADIENT method.
Call this procedure from the block's When-New-Record-Instance trigger:
PKG_TOOLS.highlight_record(:system.current_block);
When a record is selected, its visual properties are updated to render the
selection to the screen.
It uses a Visual Attribute, and its properties can be read from the CSS file
multi-select:VA_LAF_MTSELECT,Tahoma,I,10,r0g185b90,r255g255b150
multi-select-modifier:Ctrl
multi-select tag defines the Visual Attribute and its properties used to
colourize the selected records.
va_name[,font_name[,font_weight[,font_size[,foreground[,background]]]]
• P (plain)
• B (bold)
• I italic)
• PI (plain+italic)
• BI (bold+italic)
If you don't provide all element values, put a minus (-) instead.
multi-select:VA_LAF_MTSELECT,Tahoma,-,10,-,r255g255b150
As this tag indicates the Visual Attribute used, it must exist at runtime in the
Forms module.
If all properties are already defined in this VA, you don't need to provide them
in the tag:
multi-select:VA_LAF_MTSELECT
• - (none)
• Shift
• Ctrl
• Alt
• Shift+ctrl
If not provided, the tag default is nothing.
In order to use the multi-select feature in your table-block, you have to add
some code in the following triggers:
form-level:
POST-FORM
block-level:
POST-QUERY
-- set initial value to unchecked --
pkg_multiselect.set_state(get_block_property(:system.trigger_block,
CURRENT_RECORD), 0);
KEY-EXEQRY
KEY-DELREC
pkg_multiselect.delete_record(get_block_property(:system.trigger_block,
CURRENT_RECORD));
delete_record;
WHEN-CREATE-RECORD
pkg_multiselect.create_record(get_block_property(:system.trigger_block,
CURRENT_RECORD));
Used to synchronize the in-memory collection while inserting a record
WHEN-MOUSE-[DOUBLE]CLICK
pkg_multiselect.change_state(:system.cursor_record);
Those triggers are grouped in an Object Group in the laf.olb Object Library
The group name is : GRP_MULTISELECT
Drop this group name in your current module then drag the triggers in your
final block.
At the moment you want to get the selected record list, use the laf.pll
pkg_multiselect.get_checked_list() function:
Declare
t pkg_multiselect.TAB_SEL;
Begin
t := pkg_multiselect.get_checked_list('EMP');
if t.count > 0 then
for i in 1 .. t.last loop
message('selected record:' || t(i));
end loop;
else
message('no record selected') ;
end if ;
End;
At any time, within the current record, you can know its state
(selected/unselected) by using the pkg_multiselect.get_state() function
Begin
first_record;
loop
message('rec:' || :SYSTEM.CURSOR_RECORD || ' ->'
|| pkg_multiselect.get_state(:SYSTEM.CURSOR_RECORD));
exit when :system.last_record = 'TRUE';
next_record;
end loop;
End;
The function returns 1 for selected record and 0 for unselected record.
The Java Beans and the Pluggable Java Components
All the graphic operations not in relation with a specific Forms item are done
through the methods included in the DrawLaf Java Bean.
In order to call these methods, you have to add a Bean Area on the canvas,
then set its Implementation Class property to:
oracle.forms.fd.DrawLAF
This class is stored in a JAR file, whose name depends on the version of Forms
you use:
Doc: To see the complete list of available methods on this bean, read the
DrawLAF Java Bean documentation.
The ImageViewer Java Bean
You can attach it to four different locations from the bean area:
● NORTH (current screen shot)
● SOUTH
● EST
● WEST
It offers more than 20 methods to set-up and display your images in a
scrolling bar. Each small icon can display an HTML tool tip, and the main
image will send a message back to Forms when you click it, allowing the
developer to attach any functions of his own to the image.
This feature needs its own screen area to display the image viewer, so
that you have to add another Bean Area to your canvas with the
following Implementation Class property:
oracle.forms.fd.ImageViewer
To test it, you would find the test_laf_image_viewer.fmb sample dialogue in
the /fmb folder.
It also uses the /fmb/icons folder that contains the images.
Without any modification, the sample dialogue expects to find this folder in the
c:/ root. If you want to copy the /icons folder anywhere else, indicate it in the
When-New-Form-Instance trigger:
:GLOBAL.IMAGE_DIR := 'c:/other_place/icons/' ;
Doc: To see the complete list of available methods on this bean, read the
Carousel Java Bean properties.
The LAF_LOV Java Bean
It allows the developer to show a List of Values (LOV) in a Swing JTable object.
It is available since the 1.3.9 version.
The LOV is decorated in the same way as the table-blocks, and supports the
following features:
Doc: To see the complete list of available methods on this bean, read the LAF
LOV Bean documentation.
The LAF_Map Java Bean
As any HTML map, it needs an image and zone coordinates to describe the
different Map areas.
When a Map zone is clicked, a message is sent back to the forms module via
the Set_Custom_Item_Event trigger associated to the Bean Area.
Doc: To see the complete list of available methods on this bean, read the LAF
Map Bean documentation.
The Pluggable Java Components (PJCs)
Some of the Standard Forms Widgets can be overloaded to change their look
and extend their functionalities.
● Push Button
Doc: To see the complete list of available methods on this PJC, read the
LAF_XP_Button properties documentation.
● Check-box
Doc: To see the complete list of available methods on this PJC, read the
LAF_CheckBox properties documentation.
● Radio Button
Doc: To see the complete list of available methods on this PJC, read the
LAF_XP_Button properties documentation.
● Single-line Text Item
Doc: To see the complete list of available methods on this PJC, read the
LAF_XP_TextField properties documentation.
Doc: To see the complete list of available methods on this PJC, read the
LAF_XP_TextArea properties documentation.
● Poplist item
Doc: To see the complete list of available methods on this PJC, read the
LAF_XP_PopList properties documentation.
● Tlist item
Doc: To see the complete list of available methods on this PJC, read the
LAF_XP_TList properties documentation.
Implementation in the Forms modules
In the /fmb folder of the zip file, you would find a template named :
LAF_TEMPLATE.fmb.
While you are building a brand new module from scratch, it is best to
create the new module from this template, by using the File → New →
Forms using template... Forms Builder menu option.
You can also drag the GRP_LAF laf.olb Object Library's group to the
Objects Groups node of an empty Forms module.
While you want to update existing modules, you have to use the
LAF_JDAPI tool.
You can download the last version from the Look and Feel Project home page.
Here is a description of the content of the zip file:
● The /css sub-folder contains the current forms.css template CSS file.
Without any modification, this file is generally expected in the c:/ root
directory.
There are several places you can indicate the location of this file.
The PM$CSS_FILENAME Forms parameter is one of them.
If you create a new module from the LAF_TEMPLATE.fmb file or if you
use the GRP_LAF laf.olb Object Library group, it would be present in
your module.
You can also indicate the full path directly in the
PKG_Look_And_Feel.Open_Css() laf.pll's function.
If you are updating existing Forms module via the LAF_JDAPI tool, you
can also indicate the location of the CSS file in the XML configuration file.
● The /fmb sub-folder contains the Forms sample dialogues, the Object
Library, the demo icons and images, and two batch files to compile the
modules.
Module compilation
Since, you have decompressed the zip file, and copy the Forms samples
and the PL/SQL library, you have to compile the modules.
Two batch files are provided to achieve that task:
Since the laf.pll has been compiled, move a copy of both laf.pll and
laf.plx files in one of the folders pointed by the FORMS_PATH environment
variable.
● laf_902.jar
● laf_1012.jar
● laf_10123.jar
● laf_11112.jar
It is not required to attach this library if you do not want to use the CSS
features, like decorating the blocks or tuning the general GUI settings.
Actually, almost every CSS tag feature has an equivalent
Set_Custom_Property() associated method, that you can call “manually”
from anywhere in the Forms code.
● The /scripts folder contains some scripts to maintain the Database
objects.
PKG_LAF.sql
This PL/SQL package is needed to transfer LOB chunks between the
Database and the Java Bean.
It is particularly used by the Read_Image_Base() ImageViewer's Bean
and the Set_Sound_Base() DrawLAF's methods.
PKG_DB_LAF_LOV.sql
This PL/SQL package is needed to use the Swing JTable LOVs.
Examples
Here is a basic PL/SQL code you would use when the forms starts:
-------------------------------------
-- form main initializations --
-------------------------------------
If PKG_Look_And_Feel.Open_Css(:PARAMETER.PM$CSS_FILENAME) Then
End if ;
The CSS file is loaded in memory, then the GUI properties are setted,
finally, the main canvas and the table-block(s) are painted.
Notice, that, if you have more that one block on the canvas that supports
the bean, you have to call the Paint_Block() procedure for each block.
Warning:
Because a Forms Bean Area supports a Java Bean, it has to be initialized
before you can use its methods. It is the reason why it is not
recommended to use the Set_Custom_Property() and
Get_Custom_Property() built-ins in the very starting phases of a Forms
module life, and those starting phases include the When-New-Form-
Instance and New-Block-Instance triggers.
The common tip, generally, is to introduce a short delay in the When-
New-Form-Instance trigger.
For this purpose, you can use, at least, two different methods:
● Use a timer
All you have to do is to create a non-repeating timer, then move
the specific LAF code to the When-Timer-Expired trigger:
When-New-Form-Instance trigger:
Declare
timer_id Timer ;
Begin
-- need a while before beans are initialized --
timer_id := Create_Timer( 'laf_timer', 50, NO_REPEAT ) ;
End ;
When-Timer-Expired trigger:
If lower(Get_Application_Property( TIMER_NAME )) = 'laf_timer' Then
-------------------------------------
-- form main initializations --
-------------------------------------
If PKG_Look_And_Feel.Open_Css(:PARAMETER.PM$CSS_FILENAME) Then
...
End if ;
End if;
● Use the DBMS_LOCK.Sleep Database procedure
Another solution, when you are sure that the Forms module is connected
to the Database, is to introduce a short delay by using the
DBMS_LOCK.Sleep() procedure before setting the custom properties.
When-New-Form-Instance trigger:
dbms_lock.sleep(2/10);
If PKG_Look_And_Feel.Open_Css(:PARAMETER.PM$CSS_FILENAME) Then
...
End if ;
● Multi-canvases module.
If the module contains more than one canvas, and you want to use the
LAF features on each of them, you need to put a Bean Area on, at least,
one block for each different canvas.
Warning:
You cannot use the methods of a Java Bean while it has not been
initialized, and a Java Bean is initialized only when it is on a visible
canvas, and this canvas is displayed.
Go_Block('EMP');
-- populate the block --
Execute_Query ;
--
-- hidden canvases that supports PJCs must be displayed once
-- to initialize the bean areas and PJCs implementation classes
--
Show_View('CV2');
...
End if ;
Here is a procedure you can call from the When-Timer-Expired trigger, that
does the job:
PROCEDURE init_laf_blocks
(
PC$Blk1 in varchar2 default null
,PC$Blk2 in varchar2 default null
,PC$Blk3 in varchar2 default null
)
IS
LC$blockDeb varchar2(60); -- start block
LC$block varchar2(60); -- current block name
LC$item varchar2(60); -- current item
LC$itemdeb varchar2(60); -- first item
BEGIN
LC$BlockDeb := get_form_property( NAME_IN('System.Current_Form')
, FIRST_BLOCK ) ;
LC$Block := LC$BlockDeb ;
Loop -- For each block of the form
If LC$Block != Upper( Nvl( PC$Blk1, ' '))
And LC$Block != Upper( Nvl( PC$Blk2, ' '))
And LC$Block != Upper( Nvl( PC$Blk3, ' ')) Then
LC$itemdeb := get_block_property(LC$BLOCK, FIRST_ITEM) ;
LC$item := LC$BLOCK || '.' || LC$itemdeb ;
While LC$itemdeb is not null loop -- For each item
-- navigable item ? –
IF GET_ITEM_PROPERTY(LC$Item , NAVIGABLE) = 'TRUE' Then
Go_Block(LC$Block);
Synchronize;
exit;
END IF;
LC$itemdeb := get_item_property(LC$item, NEXTITEM );
LC$item := LC$BLOCK || '.' || LC$itemdeb ;
End loop ;
End if ;
LC$Block := get_block_property( LC$Block, NEXTBLOCK ) ;
exit when LC$Block is null ;
End loop ;
END init_laf_blocks;
When-Timer-Expired trigger:
...
PKG_TOOLS.init_laf_blocks('LAF_BLOCK', 'WEBUTIL');
-– there, you can use the Set_Custom_Property() on every block/item.
...
Acknowledgements
This tool would probably not exist without the support of Grant Ronald, who,
first, gave me the idea, that introduced the need.
Create something is always great, but without the original idea, nothing can
exists ;o)
Many thanks to the people from the Oracle Forms Managers Grant Ronald,
Frank Nimphius and Duncan Mills for their support all along the past years.
Special thanks to the Oracle Forms Development Team in general who created
and maintained this fabulous product, allowing the developer to mix with as
many Java code as we want.
Special thanks for the people that tried, used, tested, raised bugs and also
provided code snippets and enhancements.
Developer list