A card-like javabean
In some application i've seen a layout where the data is presented in a card-like style, having a picture to the left, a title on top and some text containing additional information. I liked the layout, so i decided that this should also be possible in forms (of course using a java-bean)
So what should such a component be capable of
- it should be used as a standard textitem
- the layout should be fully configurable
- it should allow showing images
- it should allow hyperlinks
- it should look somehow "modern"
And thats my approach.
- I created a javabean overwriting the standard VTextField. That allows usage in a multi-record block without having to deal with synchronizing content when scrolling
- The layout is done by an HTMLEditorkit placed in a JEditorPane.
- The layout is defined by creating a html-"template", containing placeholders to be replaced with the real data at runtime
- The value is set by simply assigning it to the Bean-Item
- WHEN-MOUSE-CLICKED-events are raised when clicking on the item or on a link, with a special GET_CUSTOM_PROPERTY you can get the link clicked in the bean (if any)
- For handling images i use a adjusted version from Francois Degrelles "HandleImage3"-Bean
Here's a first version of the java code
package forms;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.net.URL;
import java.util.Dictionary;
import java.util.EventListener;
import java.util.Hashtable;
import java.util.StringTokenizer;
import javax.swing.ImageIcon;
import javax.swing.JEditorPane;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.text.html.HTMLEditorKit;
import oracle.forms.properties.ID;
import oracle.forms.ui.VTextField;
import sun.misc.BASE64Decoder;
/**
This is just sample code, its free to use.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
It is tested against Forms 10.1.2.0.2, but may stop working with any patch or future version of forms
Sample code for a Javabean to implement a carditem
For usage-notes see the the package PK_CARDITEM, which is the counterpart for this code
on the forms-side
*/
public class CardTextfield extends VTextField implements MouseListener, HyperlinkListener
{
/** Constants for edge-style */
public static final String STYLE_EDGE="E";
public static final String STYLE_ROUNDED="R";
/** ID's for forms-events */
public static final ID TEMPLATE =ID.registerProperty("TEMPLATE");
public static final ID CLICKED =ID.registerProperty("CLICKED");
public static final ID LINK =ID.registerProperty("LINK");
public static final ID RECT_STYLE =ID.registerProperty("RECT_STYLE");
public static final ID READIMGBASE=ID.registerProperty("READIMGBASE");
public static final ID SHADOWCOLOR=ID.registerProperty("SHADOWCOLOR");
public static final ID HOVERCOLOR =ID.registerProperty("HOVERCOLOR");
/** Imagecache */
private static Hashtable C_IMAGECACHE=new Hashtable();
/** Constant for opacity */
private static float C_OPACITY= 0.99f;
/** Stringbuffer for uploading images */
private StringBuffer m_imageBuffer =new StringBuffer();
/** Size of shadow in pixels */
private int m_shadowSize=10;
/** Shadow-color */
private Color m_shadowColor=Color.black;
/** Shadow-Color when mouse-focused */
private Color m_shadowFocusColor=Color.red;
/** The html-editor-pane */
private JEditorPane m_htmlEditor=new JEditorPane();
/** html-template */
private String m_template="";
/** Flag, if mouse is on image */
private boolean m_hasFocus=false;
/** Last "Link" visited in html*/
private String m_event=null;
/** Initialization-flag */
private boolean m_first=true;
/** Current edge-style */
private String m_rectangleStyle=STYLE_EDGE;
/** Gridbag-Constraints for Layout */
private GridBagConstraints m_constraints = new GridBagConstraints();
/** Gridbag-Layout */
private GridBagLayout m_layout = new GridBagLayout();
/** Image for shadow */
private BufferedImage m_shadowImage=null;
/** Image for shadow when mouse-focused */
private BufferedImage m_hoverImage=null;
/**
* Constructor, set up the layout
*/
public CardTextfield()
{
this.setLayout(m_layout);
this.addMouseListener(this);
try
{
m_htmlEditor.setEditorKit(new HTMLEditorKit());
// Seems not available in JInitiator
//m_htmlEditor.setFocusable(false);
m_htmlEditor.addHyperlinkListener(this);
m_htmlEditor.setEditable(false);
m_constraints.fill = GridBagConstraints.BOTH;
m_constraints.weightx = 1;
m_constraints.weighty = 1;
m_constraints.insets = new Insets(8,8,12,12);
m_constraints.gridx = 0;
m_constraints.gridy = 0;
m_constraints.gridwidth = 0;
m_constraints.gridheight = 0;
m_layout.setConstraints(m_htmlEditor, m_constraints);
add(m_htmlEditor);
Dictionary cache=(Dictionary)m_htmlEditor.getDocument().getProperty("imageCache");
if (cache==null)
{
m_htmlEditor.getDocument().putProperty("imageCache",C_IMAGECACHE);
}
} catch (Exception e)
{
}
}
/**
* Create the rectangle-image and apply the shadow
* @return Image
* @param c Color for shadow
* @param height height of the image
* @param width width of the image
*/
private BufferedImage createShadowRectangle(int width, int height, Color c)
{
BufferedImage subject = new BufferedImage(width + m_shadowSize * 2,
height + m_shadowSize * 2,
BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = subject.createGraphics();
g2.setPaint(Color.black);
if (STYLE_EDGE.equals(m_rectangleStyle))
{
g2.fillRect(1, 1, width, height);
} else
{
g2.fillRoundRect(1, 1, width, height, 25, 25);
}
g2.dispose();
applyShadow(subject, c);
return subject;
}
/**
* applys a shadow to the given image, taken from
* Romain Guy's blog at http://jroller.com/gfx/entry/non_rectangular_shadow
* @param c color for the shadow
* @param image imgae to apply a shadow on
*/
private void applyShadow(BufferedImage image, Color c)
{
int dstWidth = image.getWidth();
int dstHeight = image.getHeight();
int left = (m_shadowSize - 1) >> 1;
int right = m_shadowSize - left;
int xStart = left;
int xStop = dstWidth - right;
int yStart = left;
int yStop = dstHeight - right;
int shadowRgb = c.getRGB() & 0x00FFFFFF;
int[] aHistory = new int[m_shadowSize];
int historyIdx = 0;
int aSum;
int[] dataBuffer = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
int lastPixelOffset = right * dstWidth;
float sumDivider = C_OPACITY/m_shadowSize;
// horizontal pass
for (int y = 0, bufferOffset = 0; y < dstHeight; y++, bufferOffset = y * dstWidth)
{
aSum = 0;
historyIdx = 0;
for (int x = 0; x < m_shadowSize; x++, bufferOffset++)
{
int a = dataBuffer[bufferOffset] >>> 24;
aHistory[x] = a;
aSum += a;
}
bufferOffset -= right;
for (int x = xStart; x < xStop; x++, bufferOffset++)
{
int a = (int) (aSum * sumDivider);
dataBuffer[bufferOffset] = a << 24 | shadowRgb;
// substract the oldest pixel from the sum
aSum -= aHistory[historyIdx];
// get the lastest pixel
a = dataBuffer[bufferOffset + right] >>> 24;
aHistory[historyIdx] = a;
aSum += a;
if (++historyIdx >= m_shadowSize)
{
historyIdx -= m_shadowSize;
}
}
}
// vertical pass
for (int x = 0, bufferOffset = 0; x < dstWidth; x++, bufferOffset = x)
{
aSum = 0;
historyIdx = 0;
for (int y = 0; y < m_shadowSize; y++, bufferOffset += dstWidth)
{
int a = dataBuffer[bufferOffset] >>> 24;
aHistory[y] = a;
aSum += a;
}
bufferOffset -= lastPixelOffset;
for (int y = yStart; y < yStop; y++, bufferOffset += dstWidth)
{
int a = (int) (aSum * sumDivider);
dataBuffer[bufferOffset] = a << 24 | shadowRgb;
// substract the oldest pixel from the sum
aSum -= aHistory[historyIdx];
// get the lastest pixel
a = dataBuffer[bufferOffset + lastPixelOffset] >>> 24;
aHistory[historyIdx] = a;
aSum += a;
if (++historyIdx >= m_shadowSize)
{
historyIdx -= m_shadowSize;
}
}
}
}
/**
* Paint the component. At first call, duplicate mouse-listeners from
* the item to the thml-editor
* @param g Graphics-Context
*/
public void paint(Graphics g)
{
if (m_first)
{
// Move Mouselisteners forward to html-editor
// Seems not available in JInitiator changed to generic getListeners
EventListener[] ml=getListeners(MouseListener.class);
for (int i=0;i<ml.length;i++)
{
m_htmlEditor.addMouseListener((MouseListener)ml[i]);
}
m_first=false;
}
Graphics2D g2=(Graphics2D)g;
g2.setPaint(getBackground());
g2.fillRect(0, 0, getBounds().width, getBounds().height);
if (m_hasFocus)
{
if (m_hoverImage==null)
{
m_hoverImage=createShadowRectangle(getBounds().width-10, getBounds().height-10, m_shadowFocusColor);
}
g2.drawImage(m_hoverImage, 3, 3, null);
} else
{
if (m_shadowImage==null)
{
m_shadowImage=createShadowRectangle(getBounds().width-10, getBounds().height-10, m_shadowColor);
}
g2.drawImage(m_shadowImage, 3, 3, null);
}
g2.setPaint(getForeground());
if (STYLE_EDGE.equals(m_rectangleStyle))
{
g2.fillRect(3, 3, getBounds().width-10, getBounds().height-10);
} else
{
g2.fillRoundRect(3, 3, getBounds().width-10, getBounds().height-10, 25, 25);
}
// Apply forground to html-editors background
m_htmlEditor.setBackground(getForeground());
// Sub-components
paintComponents(g);
}
/**
* Apply the given values to the template
* @param text values, concatenated
*/
public void setValue(String text)
{
String result="";
if (text!=null && !"".equals(text))
{
result=m_template;
StringTokenizer st=new StringTokenizer(text, "|");
while (st.hasMoreTokens())
{
String field=null;
String value=null;
if (st.hasMoreTokens())
{
field=st.nextToken();
}
if (st.hasMoreTokens())
{
value=st.nextToken();
}
if (field!=null && value!=null)
{
int pos=result.indexOf("#"+field+"#");
while (pos>-1)
{
result=result.substring(0, pos)+value+result.substring(pos+field.length()+2);
pos=result.indexOf("#"+field+"#");
}
}
}
} else
{
result="<html> </html>";
}
m_htmlEditor.setText(result);
}
/**
* Standard Method, overwritten to make the bean-specific properties from forms
* @return true
* @param value
* @param id
*/
public boolean setProperty(ID id, Object value)
{
if (id==TEMPLATE)
{
m_template=(String)value;
setValue(getText());
return true;
} else if (id==RECT_STYLE)
{
// Force shadow to be recalculated
m_shadowImage=null;
m_hoverImage=null;
m_rectangleStyle=(String)value;
if (STYLE_EDGE.equals(m_rectangleStyle))
{
m_constraints.insets = new Insets(2,2,6,6);
m_layout.setConstraints(m_htmlEditor, m_constraints);
} else
{
m_constraints.insets = new Insets(8,8,12,12);
m_layout.setConstraints(m_htmlEditor, m_constraints);
}
return true;
} else if (id==SHADOWCOLOR)
{
// R,G,B values, divided by |
StringTokenizer st=new StringTokenizer((String)value, "|");
int r=Integer.parseInt(st.nextToken());
int g=Integer.parseInt(st.nextToken());
int b=Integer.parseInt(st.nextToken());
m_shadowColor=new Color(r, g, b);
m_shadowImage=null;
return true;
} else if (id==HOVERCOLOR)
{
// R,G,B values, divided by |
StringTokenizer st=new StringTokenizer((String)value, "|");
int r=Integer.parseInt(st.nextToken());
int g=Integer.parseInt(st.nextToken());
int b=Integer.parseInt(st.nextToken());
m_shadowFocusColor=new Color(r, g, b);
m_hoverImage=null;
return true;
} else if (id==ID.VALUE)
{
super.setProperty(id, "");
setValue((String)value);
return true;
} else if (id == READIMGBASE)
{
String imageData = value.toString();
if(!imageData.startsWith("[END_IMAGE]"))
{
m_imageBuffer.append(imageData);
}
else
{
// extract name of icon
String name=imageData.substring(11);
BASE64Decoder decoder = new BASE64Decoder();
try
{
byte[] decodedStr = decoder.decodeBuffer(m_imageBuffer.toString());
ImageIcon ii = new ImageIcon(decodedStr);
URL u=new URL(name);
C_IMAGECACHE.put(u, ii.getImage());
} catch(Exception e)
{
}
finally
{
m_imageBuffer =new StringBuffer();
}
}
return true ;
} else if (id == ID.BACKGROUND)
{
super.setProperty(id, value);
this.repaint();
return true;
} else if (id == ID.FOREGROUND)
{
super.setProperty(id, value);
this.repaint();
return true;
} else
{
/** Delegate */
return super.setProperty(id, value);
}
}
public void mouseClicked(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
/**
* Mouse-entered event, remember flag for colored-shadow
* @param e
*/
public void mouseEntered(MouseEvent e)
{
m_hasFocus=true;
this.repaint();
}
/**
* Mouse-exited event, reset flag for colored-shadow
* @param e
*/
public void mouseExited(MouseEvent e)
{
m_hasFocus=false;
this.repaint();
}
/**
* Hyperlink-Listener, remembers last visited link in the document
* @param hyperlinkEvent
*/
public void hyperlinkUpdate(HyperlinkEvent hyperlinkEvent)
{
HyperlinkEvent.EventType type = hyperlinkEvent.getEventType();
URL url = hyperlinkEvent.getURL();
if (type == HyperlinkEvent.EventType.ENTERED)
{
m_event=hyperlinkEvent.getURL().toString();
} else if (type == HyperlinkEvent.EventType.ACTIVATED)
{
} else if (type == HyperlinkEvent.EventType.EXITED)
{
m_event=null;
}
}
/**
* Standard Method, overwritten to return the bean-specific properties to forms
* @return value
* @param id
*/
public Object getProperty(ID id)
{
if (id==LINK)
{
// return the last activated link
return m_event;
} else
{
return super.getProperty(id);
}
}
}
And here the forms-code
PACKAGE PK_CARDITEM IS
/**
This is just sample code, its free to use.
It is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
It is tested against Forms 10.1.2.0.2, but may stop working with any patch or future version of forms
Sample code for a forms-Side Package for the Carditem Java-Bean
To build up a carditem, follow these steps
-Create a textitem which the appropiate size and set the implementation class to forms.CardItem
-Create a WHEN-MOUSE-CLICKED-trigger at the item with a call to FK_GET_CLICKED_LINK
-For some internal reason you have to have another visible item in your block (maybe a dummy-item with size 0x0),
and you have to have a WHEN-NEW-ITEM-INSTANCE-trigger on your carditem with a NEXTITEM or a PREVIOUSITEM in it.
-Assign the needed values to the Card-Item in an appropiate trigger, maybe the POST-QUERY-trigger of your block with code like
DECLARE
lValues PK_CARDITEM.tValueList;
rValue PK_CARDITEM.tValue;
BEGIN
-- Fill the variables for the html-item
rValue.vcField:='NAME';
rValue.vcValue:=:EMP.FIRST_NAME ||' ' || :EMP.LAST_NAME;
lValues(1):=rValue;
rValue.vcField:='DATE';
rValue.vcValue:=TO_DATE(:EMP.HIRE_DATE);
lValues(2):=rValue;
rValue.vcField:='SALARY';
rValue.vcValue:=:EMP.SALARY;
lValues(3):=rValue;
:EMP.B:=PK_CARDITEM.FK_GET_ITEM_VALUE('EMP.B', lValues);
END
-make sure the jar is on the archive or achive_jini-tag
Template
- The template is built-up using standard-html. Everything which can be rendered by a JEditorPane can be used
which means, you have to test what works. If you want to put some variable, data-specific content in the html,
simply put the name of this "field" in the html enclosed by #, e.g. <P><FONT STYLE="font-size: 13pt">#DEPT#</FONT></P></TD>.
The dept will be replaced at runtime with the value passed via the FK_GET_ITEM_VALUE.
Using images and links you can use images in your
- You need the Database-package Pkg_Read_Blob_Image from Francois Degrelle's advanced image javabean
- You can use images and hyperlinks in your html-content
+ Links. You can use any link. If you want to use "virtual links" to call some forms-logic, make sure it starts with http://
+ Images. You can use real urls for images or use PR_READ_IMAGE to load an image to the carditem. You can use any url for the
image-link, you only have to make sure that the url starts with http://
*/
-- Type used for value-passing
TYPE tValue IS RECORD (
vcField VARCHAR2(30),
vcValue VARCHAR2(4000)
);
-- List-Type used for value-passing
TYPE tValueList IS TABLE OF tValue INDEX BY BINARY_INTEGER;
/** Initialization-method for a card-item
i_vcItem name of the item which represents the carditem
i_vcTemplate HTML-template to show the content of the item
i_vcStyle Borderstyle, either R for rounded rectangle or E for edged rectangle
*/
PROCEDURE PR_INIT(i_vcItem IN VARCHAR2,
i_vcTemplate IN VARCHAR2,
i_vcStyle IN VARCHAR2);
/** Setter for the template
i_vcItem name of the item which represents the carditem
i_vcTemplate HTML-template to show the content of the item
*/
PROCEDURE PR_SET_TEMPLATE(i_vcItem IN VARCHAR2,
i_vcTemplate IN VARCHAR2);
/** Setter for the shadow-Color
i_vcItem name of the item which represents the carditem
i_nRed Red-part of color
i_nGreen Green-part of color
i_nBlue Blue-part of color
*/
PROCEDURE PR_SET_SHADOW_COLOR(i_vcItem IN VARCHAR2,
i_nRed IN NUMBER,
i_nGreen IN NUMBER,
i_nBlue IN NUMBER);
/** Setter for the hover-Color, when the mouse moves over the carditem
i_vcItem name of the item which represents the carditem
i_nRed Red-part of color
i_nGreen Green-part of color
i_nBlue Blue-part of color
*/
PROCEDURE PR_SET_HOVER_COLOR(i_vcItem IN VARCHAR2,
i_nRed IN NUMBER,
i_nGreen IN NUMBER,
i_nBlue IN NUMBER);
/** Computes the value which has to be set to the item
i_vcItem name of the item which represents the carditem
i_lValues List of Key and Values
RETURN VARCHAR2-Value which has to be set as the values item
*/
FUNCTION FK_GET_ITEM_VALUE(i_vcItem IN VARCHAR2,
i_lValues IN tValueList)
RETURN VARCHAR2;
/** WHEN-MOUSE-CLICKED-trigger for the carditem
i_vcItem name of the item which represents the carditem
RETURN NULL if the item was clicked, name of a href-tag if it was clicked
*/
FUNCTION FK_GET_CLICKED_LINK(i_vcItem IN VARCHAR2)
RETURN VARCHAR2;
/** WHEN-MOUSE-CLICKED-trigger for the carditem
i_vcItem name of the item which represents the carditem
i_vcQueryForBlob Query which points to the BLOB of the image to be read
i_vcImageUrl Name under which the image will be referenced in the carditem
*/
PROCEDURE PR_READ_IMAGE(i_vcItem IN VARCHAR2,
i_vcQueryForBlob IN VARCHAR2,
i_vcImageUrl IN VARCHAR2);
END;
PACKAGE BODY PK_CARDITEM IS
PROCEDURE PR_INIT(i_vcItem IN VARCHAR2,
i_vcTemplate IN VARCHAR2,
i_vcStyle IN VARCHAR2) IS
BEGIN
SET_CUSTOM_ITEM_PROPERTY(i_vcItem, 'TEMPLATE', i_vcTemplate);
SET_CUSTOM_ITEM_PROPERTY(i_vcItem, 'RECT_STYLE', i_vcStyle);
END;
-- -------------------------------------------------------------------------------
PROCEDURE PR_SET_TEMPLATE(i_vcItem IN VARCHAR2,
i_vcTemplate IN VARCHAR2) IS
BEGIN
SET_CUSTOM_ITEM_PROPERTY(i_vcItem, 'TEMPLATE', i_vcTemplate);
END;
-- -------------------------------------------------------------------------------
PROCEDURE PR_SET_SHADOW_COLOR(i_vcItem IN VARCHAR2,
i_nRed IN NUMBER,
i_nGreen IN NUMBER,
i_nBlue IN NUMBER) IS
BEGIN
SET_CUSTOM_ITEM_PROPERTY(i_vcItem,'SHADOWCOLOR', TO_CHAR(i_nRed) || '|' ||
TO_CHAR(i_nGreen) || '|' ||
TO_CHAR(i_nBlue) || '|');
END;
-- -------------------------------------------------------------------------------
PROCEDURE PR_SET_HOVER_COLOR(i_vcItem IN VARCHAR2,
i_nRed IN NUMBER,
i_nGreen IN NUMBER,
i_nBlue IN NUMBER) IS
BEGIN
SET_CUSTOM_ITEM_PROPERTY(i_vcItem,'HOVERCOLOR', TO_CHAR(i_nRed) || '|' ||
TO_CHAR(i_nGreen) || '|' ||
TO_CHAR(i_nBlue) || '|');
END;
-- -------------------------------------------------------------------------------
FUNCTION FK_GET_ITEM_VALUE(i_vcItem IN VARCHAR2,
i_lValues IN tValueList)
RETURN VARCHAR2 IS
vcValue VARCHAR2(32000);
BEGIN
FOR i IN 1..i_lValues.COUNT LOOP
vcValue:=vcValue || i_lValues(i).vcField || '|' || REPLACE(i_lValues(i).vcValue, '|', ' ') || '|';
END LOOP;
RETURN vcValue;
END;
-- -------------------------------------------------------------------------------
FUNCTION FK_GET_CLICKED_LINK(i_vcItem IN VARCHAR2)
RETURN VARCHAR2 IS
nTopRecord NUMBER:=GET_BLOCK_PROPERTY(SUBSTR(i_vcItem, 1, INSTR(i_vcItem, '.')-1), TOP_RECORD);
vcLink VARCHAR2(2000);
BEGIN
IF :SYSTEM.MOUSE_RECORD!=:SYSTEM.CURSOR_RECORD THEN
GO_RECORD(:SYSTEM.MOUSE_RECORD);
END IF;
vcLink:=GET_CUSTOM_PROPERTY(i_vcItem, :SYSTEM.MOUSE_RECORD-nTopRecord+1, 'LINK');
RETURN vcLink;
END;
-- -------------------------------------------------------------------------------
PROCEDURE PR_READ_IMAGE(i_vcItem IN VARCHAR2,
i_vcQueryForBlob IN VARCHAR2,
i_vcImageUrl IN VARCHAR2) IS
LB$Ok boolean ;
LC$Image Varchar2(32767) ;
LC$Clause Varchar2(4000) ;
BEGIN
--
-- Read an image from the database
--
LC$Clause := i_vcQueryForBlob;
-- Select the Blob column --
If Pkg_Read_Blob_Image.Select_Blob(LC$Clause) Then
Loop
-- Get the image chunks from the database --
LC$Image := Pkg_Read_Blob_Image.Get_B64_Chunk ;
If LC$Image Is Not Null Then
-- Send the chunks to the Java Bean --
Set_Custom_Property( i_vcItem, 1, 'READIMGBASE', LC$Image ) ;
Else
-- End the sending process --
Set_Custom_Property( i_vcItem, 1, 'READIMGBASE', '[END_IMAGE]' || i_vcImageUrl) ;
Exit ;
End if ;
End loop ;
End if ;
END;
END;
This video shows the usage of the bean with the Oracle demo-tables EMPLOYEES (extended with a column called PHOTO to contain an image for each employee)
A compiled version with a demo-fmb can be found at Francois Degrelle's PJC-site here