ADF How to Bring Common UI Patterns to ADF Luc Bors, AMIS, The Netherlands Wednesday, June 27, 2012 ODTUG KScope 12 San Antonio, Texas, USA
UI PATTERNS
FUSION APPS PATTERNS
RECOGNIZE THESE ?
RECOGNIZE THESE ?
TABBED NAVIGATION
TABBED NAVIGATION
CLOSE ALL / CLOSE OTHERS
CLOSE ALL / CLOSE OTHERS
CLOSE ALL / CLOSE OTHERS • e
CAN YOU DO THIS IN ADF ?
CREATE THE CONTEXT MENU <af:popup id="contextMenu" contentDelivery="lazyUncached" eventContext="launcher" launcherVar="source"> <af:menu id="men1" text="#{source.attributes.menuInvokedOnTab}"> <af:commandMenuItem id="cmi1" text="close“ actionListener="#{viewScope.jhsDynTabContext.closeThis}"> <af:setPropertyListener from="#{source.attributes.menuInvokedOnTab}" to="#{viewScope.jhsDynTabContext.menuInvokedOnTab}" type="action"/> </af:commandMenuItem> <af:commandMenuItem id="cmi2" text="close others“ actionListener="#{viewScope.jhsDynTabContext.closeOthers}"> ……. </af:commandMenuItem> <af:commandMenuItem id="cmi3" text="close all“ actionListener="#{viewScope.jhsDynTabContext.closeAll}"> …………….. </af:commandMenuItem> </af:menu> </af:popup>
INVOKING THE CONTEXT MENU <af:navigationPane ...........> <af:commandNavigationItem ...............> <f:attribute name="tabId" value="#{tab.id}"/> <af:clientListener method="showMenu" type="contextMenu"/> <af:clientAttribute name="menuInvokedOnTab" value="#{tab.id}"/> </af:commandNavigationItem> </af:navigationPane > function showMenu(evt) { var popup = AdfPage.PAGE.findComponent("pt:contextMenu"); …………….. popup.show(hints); evt.cancel(); }
USE EXISTING FUNCTIONALITY
CLOSE THIS TAB public void closeThis(ActionEvent action) { String id = getMenuInvokedOnTab(); List<String> tabsToRemove = new ArrayList(); for (DynTab t : getActiveTabList()) { String x = t.getId(); if (id == x) { tabsToRemove.add(x); } } for (String t : tabsToRemove) { removeTab(t); } }
CLOSE OTHER TABS public void closeOthers(ActionEvent action) { String id = getMenuInvokedOnTab(); List<String> tabsToRemove = new ArrayList(); for (DynTab t : getActiveTabList()) { String x = t.getId(); if (id != x) { tabsToRemove.add(x); } } for (String t : tabsToRemove) { removeTab(t); } }
CLOSE ALL public void closeAll(ActionEvent action) { List<String> tabsToRemove = new ArrayList(); for (DynTab t : getActiveTabList()) { tabsToRemove.add(t.getId()); } for (String t : tabsToRemove) { removeTab(t); } }
CLOSE ALL / CLOSE OTHERS
MOST RECENTLY USED (..HISTORY)
IMPLEMENTATION • Record activities – Use setPropertyListeners • Historybean (session scope) that manages the collection of navigation events – Display Label, Entity Type and Primary Key – bean calls … • BusinessService to record event in database for this user – (Optional) Also remove events for deleted records!
CREATE THE BEAN • Create History bean <managed-bean id=“1"> <managed-bean-name id=“2"> recentHistoryBean</managed-bean-name> <managed-bean-class id=“3"> nl.amis.jsf.history.beans.RecentHistoryBean </managed-bean-class> <managed-bean-scope id=“3">session</managed-bean-scope> </managed-bean>
INTERCEPT ACTIVITY <af:commandLink id="ot3" text="#{row.LastName}" action="edit" actionListener="#{recentHistoryBean.add}"> <af:setPropertyListener from="#{row.EmployeeId}“ to="#{recentHistoryBean.entityInstanceIdentifier}" type="action"/> <af:setPropertyListener from="#{row.LastName}“ to="#{recentHistoryBean.entityInstanceDisplayLabel}" type="action"/> <af:setPropertyListener from="#{'EMP'}" to="#{recentHistoryBean.entityType}" type="action"/> </af:commandLink>
RECORD ACTIVITY public void add(ActionEvent actionEvent) { // Add event code here... recentHistory.add(new HistoryEvent(new oracle.jbo.domain.Date( , entityType , entityInstanceIdentifier , entityInstanceDisplayLabel)); HRServiceImpl hrAppMod = (HRServiceImpl) pageTemplateBc.getDataControl().getApplicationModule(); hrAppMod.recordAndPersistHistoryEntry(recentHistory.get(0));
PERSIST ACTIVITY public void recordAndPersistHistoryEntry(HistoryEvent event) { String statement = "RECENT_HISTORY_MANAGER.RECORD_AND_PERSIST_ENTRY(?,?,?,?)"; callStoredProcedure(statement, new Object[] {event.getEntityType() , event.getKey().toString() , event.getDisplayLabel(), null}); }
THE RESULT
DO YOU RECOGNIZE THIS ??
GOOGLE SEARCH
ADF QUERY COMPONENT ……
PREPARE THE DATABASE • Make sure that the HR user is allowed to use the ctxsys.ctx_ddl package grant EXECUTE on CTXSYS.CTX_DDL to HR
CREATE A SEARCH PACKAGE function get_emp_search_item ( p_rowid in rowid ) return varchar2 as begin for b in (select e.first_name , e.last_name , e.email , e.phone_number , j.job_title from employees e left join jobs j using (job_id) where e.rowid = p_rowid) loop return b.first_name || ' ' || b.last_name || ' (' || b.email || ', ' || b.phone_number || ', ' || b.job_title || ')'; end loop; end get_emp_search_item;
CREATE THE ORACLE TEXT INDICES -- Configure preferences... ctx_ddl.create_preference('emp_datastore', 'user_datastore'); ctx_ddl.set_attribute('emp_datastore', 'procedure' , 'ot_search.create_emp_search_item'); -- Create the indices... execute immediate 'create index emp_search_index on employees(last_name) indextype is ctxsys.context parameters (''datastore emp_datastore wordlist wordlist lexer lexer stoplist stoplist sync (on commit)'')';
THE BASE CLASSES • OracleTextSearchSupport: • Converts the given user input (the search command): <code>searchValue</code> to an Oracle Text search-string. • BaseViewObjectImpl • overrides getCriteriaItemClause(ViewCriteriaItem vci) • BaseViewDefImpl • implementation to provide the following custom properties on ViewObjects: • ORACLE_TEXT_SEARCH_ATTRIBUTE: To mark the column in which the seach info is queried • ORACLE_TEXT_INDEX_ATTRIBUTE: To mark the database column on which the index was defined in the database.
CREATE THE MODEL PROJECT
CREATE THE MODEL PROJECT
VIEWCONTROLLER : A BEAN <managed-bean-name id="1">departmentsQuickSearch</managed-bean-name> <managed-bean-class id="4"> adfplus.quicksearch.controller.bean.QuickSearchBean </managed-bean-class> <managed-bean-scope id="2">pageFlow</managed-bean-scope> <managed-property id=“8"> <property-name id=“10">iteratorBindingName</property-name> <property-class>java.lang.String</property-class> <value id="9">DepartmentsVO1Iterator</value> </managed-property> <managed-property id="11"> <property-name id="13">searchAttribute</property-name> <property-class>java.lang.String</property-class> <value id="12">DepartmentSearchString</value> </managed-property> <managed-property id="14"> <property-name id="15">searchIteratorBindingName</property-name> <property-class>java.lang.String</property-class> <value id="16">DepartmentsVO1IteratorQuickSearch</value> </managed-property>
VIEWCONTROLLER : A SUBFORM <af:subform id="s1" defaultCommand="cb7"> <af:panelGroupLayout id="pgl4" layout="horizontal" inlineStyle="margin:10px;"> <af:inputText label="Search" id="it2“ value="#{pageFlowScope.departmentsQuickSearch.searchValue}"> <af:autoSuggestBehavior suggestItems= "#{pageFlowScope.departmentsQuickSearch.suggestItems}" maxSuggestedItems="10"/> </af:inputText> <af:commandButton text="Search" id="cb7" action="#{pageFlowScope.departmentsQuickSearch.go}" partialSubmit="true"/> </af:panelGroupLayout> </af:subform>
VIEWCONTROLLER : SEARCH METHOD private synchronized List<SelectItem> search(String searchValue) { DCIteratorBinding iter = getSearchIteratorBinding(); applySearchCriteria(iter, searchAttribute, searchValue); translations.clear(); lastSuggestList = new ArrayList<SelectItem>(); lastSearchValue = searchValue; Row[] rows = iter.getAllRowsInRange(); for (Row row : rows) { String description = (String)row.getAttribute(searchAttribute); lastSuggestList.add(new SelectItem(description)); translations.put(description, row.getKey()); } return lastSuggestList; }
THE RESULT
REAL TIME UPDATES
THE CONCEPT
THE IMPLEMENTATION; THE DATABASE grant change notification to <user>;
REGISTER FOR DBQRCN (STEP 1) public void startChangeNotification(){ DatabaseChangeRegistration dcr = null; String query = "SELECT * from DEPARTMENTS"; Properties prop = new Properties(); prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS ,"true"); prop.setProperty(OracleConnection.DCN_QUERY_CHANGE_NOTIFICATION ,"true"); try { dcr = conn.registerDatabaseChangeNotification(prop); RdbmsChangeEventListener listener = new RdbmsChangeEventListener(this); dcr.addListener(listener); ………..
REGISTER FOR DBQRCN (STEP 2) // second step: add objects in the registration: Statement stmt = conn.createStatement(); ((OracleStatement)stmt).setDatabaseChangeRegistration(dcr); ResultSet rs = stmt.executeQuery(query); while (rs.next()){} rs.close(); stmt.close();
WHATS NEXT ….
SETUP ACTIVE DATA COMPONENT public void setupActiveData() { ActiveModelContext context = ActiveModelContext.getActiveModelContext(); Object[] keyPath = new String[0]; context.addActiveModelInfo( this , keyPath , "activemessage"); System.out.println("add active bean as listener"); databaseNotificationProcessor.registerAsListener(this); }
SETUP THE ACTUAL UPDATE public void triggerDataUpdate(String message) { this.message = message; counter.incrementAndGet(); ActiveDataUpdateEvent event = ActiveDataEventUtil.buildActiveDataUpdateEvent( ActiveDataEntry.ChangeType.UPDATE, counter.get(), new String[0], null, new String[] { "activemessage" }, new Object[] { message }); System.out.println("fireActiveDataUpdate"); fireActiveDataUpdate(event); }
IMPLEMENTATION IN THE PAGE <af:activeOutputText value="#{pageFlowScope.trackChangesBean.updates}" id="aot1" visible="false"> <af:clientListener method="activeDataCallback" type="propertyChange"/> </af:activeOutputText> <af:resource type="javascript"> activeDataCallback = function (event) { var button = AdfPage.PAGE.findComponentByAbsoluteId("pt1:r1:0:cb1"); button.setVisible(true); } </af:resource>
THE RESULT
PATTERNS UNDER INVESTIGATION • Grouping Tabs • Drag and Drop Tabs in UI Shell – dragSource and dropTarget • Duplicating Tabs – Restarting a new instance of a taskflow
PATTERNS UNDER INVESTIGATION • Adding Sticky Notes – dragSource and dropTarget – Contextual events – Concept : http://technology.amis.nl • Search for: adf-11g-dragn-drop-and-contextual-events/
RESOURCES
RESOURCES
SUMMARY
ADF How to Bring Common UI Patterns to ADF Luc Bors, AMIS, The Netherlands Luc.Bors@amis.nl LucBors@gmail.com Follow me on Twitter : @lucb_ Wednesday, June 27, 2012 ODTUG KScope 12 San Antonio, Texas, USA

How to Bring Common UI Patterns to ADF

  • 1.
    ADF How to BringCommon UI Patterns to ADF Luc Bors, AMIS, The Netherlands Wednesday, June 27, 2012 ODTUG KScope 12 San Antonio, Texas, USA
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
    CLOSE ALL /CLOSE OTHERS
  • 9.
    CLOSE ALL /CLOSE OTHERS
  • 10.
    CLOSE ALL /CLOSE OTHERS • e
  • 11.
    CAN YOU DOTHIS IN ADF ?
  • 12.
    CREATE THE CONTEXTMENU <af:popup id="contextMenu" contentDelivery="lazyUncached" eventContext="launcher" launcherVar="source"> <af:menu id="men1" text="#{source.attributes.menuInvokedOnTab}"> <af:commandMenuItem id="cmi1" text="close“ actionListener="#{viewScope.jhsDynTabContext.closeThis}"> <af:setPropertyListener from="#{source.attributes.menuInvokedOnTab}" to="#{viewScope.jhsDynTabContext.menuInvokedOnTab}" type="action"/> </af:commandMenuItem> <af:commandMenuItem id="cmi2" text="close others“ actionListener="#{viewScope.jhsDynTabContext.closeOthers}"> ……. </af:commandMenuItem> <af:commandMenuItem id="cmi3" text="close all“ actionListener="#{viewScope.jhsDynTabContext.closeAll}"> …………….. </af:commandMenuItem> </af:menu> </af:popup>
  • 13.
    INVOKING THE CONTEXTMENU <af:navigationPane ...........> <af:commandNavigationItem ...............> <f:attribute name="tabId" value="#{tab.id}"/> <af:clientListener method="showMenu" type="contextMenu"/> <af:clientAttribute name="menuInvokedOnTab" value="#{tab.id}"/> </af:commandNavigationItem> </af:navigationPane > function showMenu(evt) { var popup = AdfPage.PAGE.findComponent("pt:contextMenu"); …………….. popup.show(hints); evt.cancel(); }
  • 14.
  • 15.
    CLOSE THIS TAB publicvoid closeThis(ActionEvent action) { String id = getMenuInvokedOnTab(); List<String> tabsToRemove = new ArrayList(); for (DynTab t : getActiveTabList()) { String x = t.getId(); if (id == x) { tabsToRemove.add(x); } } for (String t : tabsToRemove) { removeTab(t); } }
  • 16.
    CLOSE OTHER TABS publicvoid closeOthers(ActionEvent action) { String id = getMenuInvokedOnTab(); List<String> tabsToRemove = new ArrayList(); for (DynTab t : getActiveTabList()) { String x = t.getId(); if (id != x) { tabsToRemove.add(x); } } for (String t : tabsToRemove) { removeTab(t); } }
  • 17.
    CLOSE ALL public voidcloseAll(ActionEvent action) { List<String> tabsToRemove = new ArrayList(); for (DynTab t : getActiveTabList()) { tabsToRemove.add(t.getId()); } for (String t : tabsToRemove) { removeTab(t); } }
  • 18.
    CLOSE ALL /CLOSE OTHERS
  • 19.
    MOST RECENTLY USED(..HISTORY)
  • 20.
    IMPLEMENTATION • Record activities – Use setPropertyListeners • Historybean (session scope) that manages the collection of navigation events – Display Label, Entity Type and Primary Key – bean calls … • BusinessService to record event in database for this user – (Optional) Also remove events for deleted records!
  • 21.
    CREATE THE BEAN • Create History bean <managed-bean id=“1"> <managed-bean-name id=“2"> recentHistoryBean</managed-bean-name> <managed-bean-class id=“3"> nl.amis.jsf.history.beans.RecentHistoryBean </managed-bean-class> <managed-bean-scope id=“3">session</managed-bean-scope> </managed-bean>
  • 22.
    INTERCEPT ACTIVITY <af:commandLink id="ot3"text="#{row.LastName}" action="edit" actionListener="#{recentHistoryBean.add}"> <af:setPropertyListener from="#{row.EmployeeId}“ to="#{recentHistoryBean.entityInstanceIdentifier}" type="action"/> <af:setPropertyListener from="#{row.LastName}“ to="#{recentHistoryBean.entityInstanceDisplayLabel}" type="action"/> <af:setPropertyListener from="#{'EMP'}" to="#{recentHistoryBean.entityType}" type="action"/> </af:commandLink>
  • 23.
    RECORD ACTIVITY public voidadd(ActionEvent actionEvent) { // Add event code here... recentHistory.add(new HistoryEvent(new oracle.jbo.domain.Date( , entityType , entityInstanceIdentifier , entityInstanceDisplayLabel)); HRServiceImpl hrAppMod = (HRServiceImpl) pageTemplateBc.getDataControl().getApplicationModule(); hrAppMod.recordAndPersistHistoryEntry(recentHistory.get(0));
  • 24.
    PERSIST ACTIVITY public voidrecordAndPersistHistoryEntry(HistoryEvent event) { String statement = "RECENT_HISTORY_MANAGER.RECORD_AND_PERSIST_ENTRY(?,?,?,?)"; callStoredProcedure(statement, new Object[] {event.getEntityType() , event.getKey().toString() , event.getDisplayLabel(), null}); }
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
    PREPARE THE DATABASE • Make sure that the HR user is allowed to use the ctxsys.ctx_ddl package grant EXECUTE on CTXSYS.CTX_DDL to HR
  • 30.
    CREATE A SEARCHPACKAGE function get_emp_search_item ( p_rowid in rowid ) return varchar2 as begin for b in (select e.first_name , e.last_name , e.email , e.phone_number , j.job_title from employees e left join jobs j using (job_id) where e.rowid = p_rowid) loop return b.first_name || ' ' || b.last_name || ' (' || b.email || ', ' || b.phone_number || ', ' || b.job_title || ')'; end loop; end get_emp_search_item;
  • 31.
    CREATE THE ORACLETEXT INDICES -- Configure preferences... ctx_ddl.create_preference('emp_datastore', 'user_datastore'); ctx_ddl.set_attribute('emp_datastore', 'procedure' , 'ot_search.create_emp_search_item'); -- Create the indices... execute immediate 'create index emp_search_index on employees(last_name) indextype is ctxsys.context parameters (''datastore emp_datastore wordlist wordlist lexer lexer stoplist stoplist sync (on commit)'')';
  • 32.
    THE BASE CLASSES •OracleTextSearchSupport: • Converts the given user input (the search command): <code>searchValue</code> to an Oracle Text search-string. • BaseViewObjectImpl • overrides getCriteriaItemClause(ViewCriteriaItem vci) • BaseViewDefImpl • implementation to provide the following custom properties on ViewObjects: • ORACLE_TEXT_SEARCH_ATTRIBUTE: To mark the column in which the seach info is queried • ORACLE_TEXT_INDEX_ATTRIBUTE: To mark the database column on which the index was defined in the database.
  • 33.
  • 34.
  • 35.
    VIEWCONTROLLER : ABEAN <managed-bean-name id="1">departmentsQuickSearch</managed-bean-name> <managed-bean-class id="4"> adfplus.quicksearch.controller.bean.QuickSearchBean </managed-bean-class> <managed-bean-scope id="2">pageFlow</managed-bean-scope> <managed-property id=“8"> <property-name id=“10">iteratorBindingName</property-name> <property-class>java.lang.String</property-class> <value id="9">DepartmentsVO1Iterator</value> </managed-property> <managed-property id="11"> <property-name id="13">searchAttribute</property-name> <property-class>java.lang.String</property-class> <value id="12">DepartmentSearchString</value> </managed-property> <managed-property id="14"> <property-name id="15">searchIteratorBindingName</property-name> <property-class>java.lang.String</property-class> <value id="16">DepartmentsVO1IteratorQuickSearch</value> </managed-property>
  • 36.
    VIEWCONTROLLER : ASUBFORM <af:subform id="s1" defaultCommand="cb7"> <af:panelGroupLayout id="pgl4" layout="horizontal" inlineStyle="margin:10px;"> <af:inputText label="Search" id="it2“ value="#{pageFlowScope.departmentsQuickSearch.searchValue}"> <af:autoSuggestBehavior suggestItems= "#{pageFlowScope.departmentsQuickSearch.suggestItems}" maxSuggestedItems="10"/> </af:inputText> <af:commandButton text="Search" id="cb7" action="#{pageFlowScope.departmentsQuickSearch.go}" partialSubmit="true"/> </af:panelGroupLayout> </af:subform>
  • 37.
    VIEWCONTROLLER : SEARCHMETHOD private synchronized List<SelectItem> search(String searchValue) { DCIteratorBinding iter = getSearchIteratorBinding(); applySearchCriteria(iter, searchAttribute, searchValue); translations.clear(); lastSuggestList = new ArrayList<SelectItem>(); lastSearchValue = searchValue; Row[] rows = iter.getAllRowsInRange(); for (Row row : rows) { String description = (String)row.getAttribute(searchAttribute); lastSuggestList.add(new SelectItem(description)); translations.put(description, row.getKey()); } return lastSuggestList; }
  • 38.
  • 39.
  • 40.
  • 41.
    THE IMPLEMENTATION; THEDATABASE grant change notification to <user>;
  • 42.
    REGISTER FOR DBQRCN(STEP 1) public void startChangeNotification(){ DatabaseChangeRegistration dcr = null; String query = "SELECT * from DEPARTMENTS"; Properties prop = new Properties(); prop.setProperty(OracleConnection.DCN_NOTIFY_ROWIDS ,"true"); prop.setProperty(OracleConnection.DCN_QUERY_CHANGE_NOTIFICATION ,"true"); try { dcr = conn.registerDatabaseChangeNotification(prop); RdbmsChangeEventListener listener = new RdbmsChangeEventListener(this); dcr.addListener(listener); ………..
  • 43.
    REGISTER FOR DBQRCN(STEP 2) // second step: add objects in the registration: Statement stmt = conn.createStatement(); ((OracleStatement)stmt).setDatabaseChangeRegistration(dcr); ResultSet rs = stmt.executeQuery(query); while (rs.next()){} rs.close(); stmt.close();
  • 44.
  • 45.
    SETUP ACTIVE DATACOMPONENT public void setupActiveData() { ActiveModelContext context = ActiveModelContext.getActiveModelContext(); Object[] keyPath = new String[0]; context.addActiveModelInfo( this , keyPath , "activemessage"); System.out.println("add active bean as listener"); databaseNotificationProcessor.registerAsListener(this); }
  • 46.
    SETUP THE ACTUALUPDATE public void triggerDataUpdate(String message) { this.message = message; counter.incrementAndGet(); ActiveDataUpdateEvent event = ActiveDataEventUtil.buildActiveDataUpdateEvent( ActiveDataEntry.ChangeType.UPDATE, counter.get(), new String[0], null, new String[] { "activemessage" }, new Object[] { message }); System.out.println("fireActiveDataUpdate"); fireActiveDataUpdate(event); }
  • 47.
    IMPLEMENTATION IN THEPAGE <af:activeOutputText value="#{pageFlowScope.trackChangesBean.updates}" id="aot1" visible="false"> <af:clientListener method="activeDataCallback" type="propertyChange"/> </af:activeOutputText> <af:resource type="javascript"> activeDataCallback = function (event) { var button = AdfPage.PAGE.findComponentByAbsoluteId("pt1:r1:0:cb1"); button.setVisible(true); } </af:resource>
  • 48.
  • 49.
    PATTERNS UNDER INVESTIGATION •Grouping Tabs • Drag and Drop Tabs in UI Shell – dragSource and dropTarget • Duplicating Tabs – Restarting a new instance of a taskflow
  • 50.
    PATTERNS UNDER INVESTIGATION • Adding Sticky Notes – dragSource and dropTarget – Contextual events – Concept : http://technology.amis.nl • Search for: adf-11g-dragn-drop-and-contextual-events/
  • 51.
  • 52.
  • 53.
  • 54.
    ADF How to BringCommon UI Patterns to ADF Luc Bors, AMIS, The Netherlands Luc.Bors@amis.nl LucBors@gmail.com Follow me on Twitter : @lucb_ Wednesday, June 27, 2012 ODTUG KScope 12 San Antonio, Texas, USA