Pages

Sunday, February 15, 2009

FaceBook ADF Task Flow

This is an example of a Facebook Task Flow which you can use in every ADF 11G application. Just an little example how powerfull and reusable Task Flow are. I used for this example the facebook-java-api, which is an opensource project. Great work guys.

Here some screenshots of the facebook TF. First we have to authenticate on the facebook site. This is only necessary the first time after this it will use a cookie to authenticate. I use the af:inlineFrame component to open the special facebook url with my application id.Now we can open the second accordion page. This will update my panelbox with my photo and show my friends in an ADF table.

We can email some friends by selecting these in the adf table and go to the email accordion and send the email.


Here is the facebook backing bean where it all happens

package nl.whitehorses.tf.facebook.beans;

import com.google.code.facebookapi.FacebookException;
import com.google.code.facebookapi.FacebookJaxbRestClient;
import com.google.code.facebookapi.ProfileField;
import com.google.code.facebookapi.schema.FriendsGetResponse;

import com.google.code.facebookapi.schema.User;
import com.google.code.facebookapi.schema.UsersGetInfoResponse;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;

import javax.faces.event.ActionEvent;

import oracle.adf.view.rich.component.rich.data.RichTable;
import oracle.adf.view.rich.component.rich.input.RichInputText;
import oracle.adf.view.rich.component.rich.layout.RichPanelBox;
import oracle.adf.view.rich.component.rich.layout.RichShowDetailItem;
import oracle.adf.view.rich.context.AdfFacesContext;

import org.apache.myfaces.trinidad.event.DisclosureEvent;
import org.apache.myfaces.trinidad.model.RowKeySet;


public class FaceBookBean {

private String APIKEY;
private String SECRET;

private List<User> modelFriends = null;
private int friendSize;

private String sessionID;
private String token;
private FacebookJaxbRestClient client;
private String facebookUser;
private String facebookUserImage;
private RichTable resultTable;
private RichPanelBox panelBox;
private String facebookLoginUrl;
private RichShowDetailItem loginTab;
private RichShowDetailItem resultTab;
private Long loggedUser;
private RichInputText emailSubject;
private RichInputText emailBody;

public FaceBookBean() {

}

private void refresh() {
try {
if ( sessionID==null){
sessionID = client.auth_getSession(token);
facebookLoginUrl = "http://www.facebook.com";
}
if (sessionID != null) {

loggedUser = client.users_getLoggedInUser();
List<Long> loggedUsers = new ArrayList<Long>(1);
loggedUsers.add(loggedUser);
client.users_getInfo(loggedUsers,
EnumSet.of( ProfileField.NAME
, ProfileField.PIC_SMALL));

UsersGetInfoResponse userResponse = (UsersGetInfoResponse)client.getResponsePOJO();
for (User user : userResponse.getUser()) {
facebookUser = user.getName();
facebookUserImage = user.getPicSmall();
}

FriendsGetResponse response =
(FriendsGetResponse)client.friends_get(loggedUser);
List<Long> friends = response.getUid();
client.users_getInfo(friends,
EnumSet.of( ProfileField.STATUS
, ProfileField.NAME
, ProfileField.BIRTHDAY
, ProfileField.PIC_SMALL));

userResponse =
(UsersGetInfoResponse)client.getResponsePOJO();

modelFriends = userResponse.getUser();
friendSize = modelFriends.size();

resultTable.setRows(friendSize);

// disable the login tab, if we are here we have a session object
loginTab.setDisabled(true);
loginTab.setVisible(false);

// refresh panelbox and the result table
AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
adfFacesContext.addPartialTarget(resultTable);
adfFacesContext.addPartialTarget(panelBox);
}
} catch (FacebookException ex) {
ex.printStackTrace();
}


}

public void sendEmail(ActionEvent actionEvent) {
try {
RowKeySet selection = resultTable.getSelectedRowKeys();
Object[] keys = selection.toArray();
List<Long> receivers = new ArrayList<Long>(keys.length);
for ( Object key : keys ) {
User user = this.modelFriends.get((Integer)key);
receivers.add(user.getUid());
}
Object succes = client.notifications_sendTextEmail( receivers
,(String)emailSubject.getValue()
,(String)emailBody.getValue());
System.out.println(succes.toString());
} catch (FacebookException e) {
e.printStackTrace();
}
}

public void afterFaceBookLogin(ActionEvent actionEvent) {
refresh();
}


public void resultTableDisclosure(DisclosureEvent disclosureEvent) {
refresh();
}

public String getFacebookUser() {
return facebookUser;
}

public String getFacebookUserImage() {
return facebookUserImage;
}

public void setModelFriends(List<User> modelFriends) {
this.modelFriends = modelFriends;
}

public List<User> getModelFriends() {
return modelFriends;
}


public void setResultTable(RichTable resultTable) {
this.resultTable = resultTable;
}

public RichTable getResultTable() {
return resultTable;
}

public void setPanelBox(RichPanelBox panelBox) {
this.panelBox = panelBox;
}

public RichPanelBox getPanelBox() {
return panelBox;
}

public String getFacebookLoginUrl() {
if (sessionID==null){
try {
client = new FacebookJaxbRestClient(APIKEY, SECRET);
token = client.auth_createToken();
facebookLoginUrl= "http://api.new.facebook.com/login.php?api_key=" + APIKEY + "&v=1.0&auth_token=" + token;
} catch (FacebookException ex) {
ex.printStackTrace();
}
}
return facebookLoginUrl;
}

public void setLoginTab(RichShowDetailItem loginTab) {
this.loginTab = loginTab;
}

public RichShowDetailItem getLoginTab() {
return loginTab;
}

public void setResultTab(RichShowDetailItem resultTab) {
this.resultTab = resultTab;
}

public RichShowDetailItem getResultTab() {
return resultTab;
}


public void setAPIKEY(String APIKEY) {
this.APIKEY = APIKEY;
}

public String getAPIKEY() {
return APIKEY;
}

public void setSECRET(String SECRET) {
this.SECRET = SECRET;
}

public String getSECRET() {
return SECRET;
}

public void setEmailSubject(RichInputText emailSubject) {
this.emailSubject = emailSubject;
}

public RichInputText getEmailSubject() {
return emailSubject;
}

public void setEmailBody(RichInputText emailBody) {
this.emailBody = emailBody;
}

public RichInputText getEmailBody() {
return emailBody;
}
}

Here is the code of my Facebook page fragment

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:f="http://java.sun.com/jsf/core">
<af:panelBox text="Facebook Task Flow of #{FaceBook.facebookUser}"
icon="#{FaceBook.facebookUserImage}" id="box"
inlineStyle="height:500px; width:700px;"
binding="#{FaceBook.panelBox}">
<af:panelAccordion inlineStyle="height:480px; width:680px;">
<af:showDetailItem text="Facebook Login" binding="#{FaceBook.loginTab}">
<af:inlineFrame source="#{FaceBook.facebookLoginUrl}"
inlineStyle="width:99%; height:99%;"
shortDesc="Loginpage"/>
<f:facet name="toolbar"/>
</af:showDetailItem>
<af:showDetailItem text="Facebook Results" binding="#{FaceBook.resultTab}"
disclosureListener="#{FaceBook.resultTableDisclosure}">
<af:panelCollection id="col" inlineStyle="width:99%; height:98%;">
<f:facet name="toolbar">
<af:toolbar>
<af:commandToolbarButton text="Refresh"
actionListener="#{FaceBook.afterFaceBookLogin}"/>
</af:toolbar>
</f:facet>
<af:table value="#{FaceBook.modelFriends}" id="result" var="row"
rowSelection="multiple" emptyText="#{'No rows yet.'}"
binding="#{FaceBook.resultTable}" columnStretching="blank"
inlineStyle="height:97%; width:100%;">
<af:column sortable="false" headerText="Name" width="200">
<af:outputText value="#{row.name} "/>
</af:column>
<af:column sortable="false" headerText="Birthdate" width="100">
<af:outputText value="#{row.birthday}"/>
</af:column>
<af:column sortable="false" headerText="Image" width="60">
<af:image source="#{row.picSmall}"/>
</af:column>
<af:column sortable="false" headerText="Status" width="120"
noWrap="false">
<af:outputText value="#{row.status.message}"/>
</af:column>
<af:column sortable="false" headerText="Id" width="60">
<af:outputText value="#{row.uid}"/>
</af:column>
</af:table>
</af:panelCollection>
</af:showDetailItem>
<af:showDetailItem text="Email">
<af:panelFormLayout>
<af:inputText label="Subject" columns="52"
binding="#{FaceBook.emailSubject}"/>
<af:inputText label="Body" rows="3" columns="50" wrap="soft"
binding="#{FaceBook.emailBody}"/>
</af:panelFormLayout>
<f:facet name="toolbar">
<af:commandButton text="Send Email"
actionListener="#{FaceBook.sendEmail}"/>
</f:facet>
</af:showDetailItem>
</af:panelAccordion>
</af:panelBox>
</jsp:root>


When you drop this task flow on an page you have to fill the api and secret input parameters. I can't give you mine keys. You can do this in the pagedef of the page.

<?xml version="1.0" encoding="UTF-8" ?>
<pageDefinition xmlns="http://xmlns.oracle.com/adfm/uimodel"
version="11.1.1.51.88" id="startPageDef" Package="pages">
<parameters/>
<executables>
<taskFlow id="facebooktaskflowdefinition1"
taskFlowId="/WEB-INF/facebook-task-flow-definition.xml#facebook-task-flow-definition"
xmlns="http://xmlns.oracle.com/adf/controller/binding">
<parameters>
<parameter id="API" xmlns="http://xmlns.oracle.com/adfm/uimodel"
value="'0eff-----------------1'"/>
<parameter id="SECRET" xmlns="http://xmlns.oracle.com/adfm/uimodel"
value="'e1cee430d8599bc3be2e42fc19fe9d68'"/>
</parameters>
</taskFlow>
</executables>
<bindings/>
</pageDefinition>

To use this Task Flow in your ADF application, we need to go to http://developers.facebook.com/get_started.php where you can register your own application. Now you will have your own applicationId and secret which you can use for the pagedef task flow parameters. And please fill in callback url to a website.



Here is the 11g workspace.

Thursday, February 12, 2009

Reset / Clear an ADF page

Sometimes you want to reset or clear the jsf page without leaving and reloading the page. This can be a little tricky, it depends if you are using ADF inputtext items with ADF pagedef bindings or use it values from a backing bean.

First we need to add an button on the page with an actionlistener

<af:commandButton text="Revert Changes" actionListener="#{MainBean.revertCategories}"/>

In this backing bean method we can reset the ADF bindings. Use this code to requery all the iterators in the pagedef.
   
public void revertCategories(ActionEvent actionEvent) {
dc.refreshControl();
DCIteratorBinding iterBind= (DCIteratorBinding)dc.get("queryCategoryFindAllIterator");
iterBind.executeQuery();
iterBind.refresh(DCIteratorBinding.RANGESIZE_UNLIMITED);
}


Resetting inputtext items based on values in a backing bean can be tricky especially when a user has changed an inputtext component. In that case you can try to reset this value from the backing bean, but it may not work because the submittedValue of the uicomponent with the user input is still set. We need to reset the inputtext uicomponent and add partial trigger to it. Here is the code to do this. This method has as input parameter the backing bean binding of a form or an other higher uicomponent. This method will reset all the childern uicomponents who are not disabled.


private void resetValueInputItems(AdfFacesContext adfFacesContext, UIComponent component){
List<UIComponent> items = component.getChildren();
for ( UIComponent item : items ) {

resetValueInputItems(adfFacesContext,item);

if ( item instanceof RichInputText ) {
RichInputText input = (RichInputText)item;
if ( !input.isDisabled() ) {
input.resetValue() ;
adfFacesContext.addPartialTarget(input);
};
} else if ( item instanceof RichInputDate ) {
RichInputDate input = (RichInputDate)item;
if ( !input.isDisabled() ) {
input.resetValue() ;
adfFacesContext.addPartialTarget(input);
};
}
}
}

public void resetForm(ActionEvent actionEvent) {
AdfFacesContext adfFacesContext = AdfFacesContext.getCurrentInstance();
resetValueInputItems(adfFacesContext,vvFormBinding );
testMap.put("TEST", "initial value");
}

Friday, February 6, 2009

Display detail records in a dynamic ADF FormLayout

Sometimes you have to make a CRUD page on master detail tables which don't have fixed columns like the tables in the HR demo schema. The detail table has for example only a foreign key to the master table and a type and value column. The detail values are now stored vertically and not horizontal in columns. In this blog I will show how to make a dynamic custom form with inputtexts for each master.
Here is an picture of the category and category_items table I use in this blog. In this example I got for example a category U2 with the some items records like Style and Country and a category week with the weekdays as items.
I will use an local EJB as model because this gives me the Category and CategoryItems classes which I can use in the backing bean and the EJB session bean has a nice merge method which I can use when I send back the changed category.
On the EJB Session bean I created an ADF datacontrol so I can call method actions in the page definition and these methods actions will fill the method iterators.

In the left site of the page I will display all the categories and on the right I have a dynamic task flow region. You can click on a Id this will display the detail form.

When I click on my car then I will call an method action to retrieve this category class. With the items of this category I will open the category items task flow and use af:forEach to display all the items

If I select an other category I first remove the previous inputtext components and display the new items
When I change an item I can press the save button. This will call the merge method action and passes the category with the changed items to the ejb session bean, EJB will update the items values.

Here is the ddl of my examples tables.


create table CATEGORY
(
ID NUMBER not null,
NAME VARCHAR2(30) not null
)
;
alter table CATEGORY
add constraint CATEGORY_PK primary key (ID);

insert into CATEGORY (ID, NAME)
values (1, 'U2');
insert into CATEGORY (ID, NAME)
values (2, 'SEAT LEON');
insert into CATEGORY (ID, NAME)
values (3, 'WEEK');
commit;

create table CATEGORY_ITEMS
(
CAT_ID NUMBER(4) not null,
NAME VARCHAR2(40) not null,
VALUE VARCHAR2(40) not null
)
;
alter table CATEGORY_ITEMS
add constraint CATEGORY_ITEMS_PK primary key (CAT_ID, NAME);
alter table CATEGORY_ITEMS
add constraint CATEGORY_ITEMS_CATEGORY_FK1 foreign key (CAT_ID)
references CATEGORY (ID);

insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Style', 'Pop');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Country', 'Ireland');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (1, 'Albums', 'War,Rattle and hum');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'Color', 'Black');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, '4WD', 'No');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'HorsePower', '140');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (2, 'Transmission', 'Automatic');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day1', 'Monday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day2', 'Tuesday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day3', 'Wednesday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day4', 'Thursday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day5', 'Friday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day6', 'Saturday');
insert into CATEGORY_ITEMS (CAT_ID, NAME, VALUE)
values (3, 'day7', 'Sunday');
commit;


My backing which calls the method action and retrieves the Category from the method iterator.

package nl.whitehorses.dynamic.beans;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.faces.event.ActionEvent;

import nl.whitehorses.dynamic.form.model.entities.Category;

import nl.whitehorses.dynamic.form.model.entities.CategoryItems;

import oracle.adf.controller.TaskFlowId;
import oracle.adf.model.BindingContext;
import oracle.adf.model.bean.DCDataRow;
import oracle.adf.model.binding.DCBindingContainer;

import oracle.adf.model.binding.DCIteratorBinding;

import oracle.adf.view.rich.component.rich.layout.RichPanelFormLayout;

import oracle.adfinternal.view.faces.model.binding.FacesCtrlActionBinding;

import oracle.jbo.Row;

public class Main {

private Long catId;
private DCBindingContainer dc;
private Category category;
private String emptyTaskFlowId = "/WEB-INF/empty-task-flow-definition.xml#empty-task-flow-definition";
private String categoryTaskFlowId = "/WEB-INF/category-task-flow-definition.xml#category-task-flow-definition";
private String taskFlowId;
List catItemList;
private RichPanelFormLayout categoryItemForm;

public Main() {
dc = (DCBindingContainer)BindingContext.getCurrent().getCurrentBindingsEntry();
}

public void categoryTableLink(ActionEvent actionEvent) {
// Add event code here...

// the category pk is already set
// now get the category
FacesCtrlActionBinding categoryFindByKey = (FacesCtrlActionBinding)dc.getControlBinding("queryCategoryFindByKey");
categoryFindByKey.execute();

// adf executed the method action
// now get the result and put this in the category variable
DCIteratorBinding categoryBinding= (DCIteratorBinding)dc.get("queryCategoryFindByKeyIter");
Row[] categoryRows = categoryBinding.getAllRowsInRange();
for (Row row : categoryRows) {
category = (Category)((DCDataRow)row).getDataProvider();
}


// remove old childern when a other category is selected
if ( categoryItemForm != null ) {
int size = categoryItemForm.getChildren().size();
for ( int i = 0 ; i < size ; i++ ) {
categoryItemForm.getChildren().remove(0);
}
}
catItemList = category.getCategoryItemsList();
// set category taskflow
taskFlowId = categoryTaskFlowId;
}

public void saveCategoryItems(ActionEvent actionEvent) {
// Add event code here...
FacesCtrlActionBinding merge = (FacesCtrlActionBinding)dc.getControlBinding("mergeEntity");
merge.execute();
}

public TaskFlowId getDynamicTaskFlowId() {
if ( taskFlowId == null ) taskFlowId = emptyTaskFlowId;
return TaskFlowId.parse(taskFlowId);
}


public void setCatId(Long catId) {
this.catId = catId;
}

public Long getCatId() {
return catId;
}

public String getCategoryFragment() {
return category.getName();
}

public void setCatItemsList(List catItemList) {
this.catItemList = catItemList;
}

public List getCatItemsList() {
return catItemList;
}

public void setCategoryItemForm(RichPanelFormLayout categoryItemForm) {
this.categoryItemForm = categoryItemForm;
}

public RichPanelFormLayout getCategoryItemForm() {
return categoryItemForm;
}

public void setCategory(Category category) {
this.category = category;
}

public Category getCategory() {
return category;
}
}

and as last the category items jsf page code

<?xml version='1.0' encoding='windows-1252'?>
<jsp:root xmlns:jsp="http://java.sun.com/JSP/Page" version="2.1"
xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
xmlns:f="http://java.sun.com/jsf/core">
<af:subform>
<af:panelHeader text="Category #{MainBean.categoryFragment}">
<af:panelFormLayout binding="#{MainBean.categoryItemForm}">
<af:forEach var="row" items="#{MainBean.catItemsList}">
<af:inputText label="#{row.name}" value="#{row.value}"/>
</af:forEach>
<f:facet name="footer">
<af:commandButton text="Save"
actionListener="#{MainBean.saveCategoryItems}"/>
</f:facet>
</af:panelFormLayout>
</af:panelHeader>
</af:subform>
</jsp:root>


Here is my 11g example workspace