Wednesday Nov 25, 2015

Quick Note: How to validate an input field in a list view

This article is a quick note on how to validate an input field added to an amx:listView and how to reset the list item value back to the original value. The use case in the sample code snippet is a field service application that technicians use to protocol service incidents, the status of a repair and the time spent on it. The validation should be such that the time the technician worked on an incident doesn't go beyond the 8 hours of the working shift.

About the Data Model

I like the Mobile Application Framework  data control as an abstraction layer, but I also like to be in control. Because of this, independent of the service or data source (SQLite) to access, I always work through a POJO model. For this sample, the simple POJO model I created includes to classes.

The entity

 public class ServiceAssignment {
    
    String orderId = null;
    String customerName = "";
    String incident = "";
    String repairStatus="";
    Integer repairHours = 0;
    private PropertyChangeSupport propertyChangeSupport = 
                              new PropertyChangeSupport(this);


    public ServiceAssignment() {
        super();
    }


    public void setOrderId(String orderId) {
        String oldOrderId = this.orderId;
        this.orderId = orderId;
        propertyChangeSupport.firePropertyChange("orderId", oldOrderId, orderId);
    }

    public String getOrderId() {
        return orderId;
    }

    public void setCustomerName(String customerName) {
        String oldCustomerName = this.customerName;
        this.customerName = customerName;
        propertyChangeSupport.firePropertyChange("customerName", 
                                  oldCustomerName, customerName);
    }

    public String getCustomerName() {
        return customerName;
    }

    public void setIncident(String incident) {
        String oldIncident = this.incident;
        this.incident = incident;
        propertyChangeSupport.firePropertyChange("incident", 
                                          oldIncident, incident);
    }

    public String getIncident() {
        return incident;
    }

    public void setRepairStatus(String repairStatus) {
        String oldRepairStatus = this.repairStatus;
        this.repairStatus = repairStatus;
        propertyChangeSupport.firePropertyChange("repairStatus", 
                                 oldRepairStatus, repairStatus);
    }

    public String getRepairStatus() {
        return repairStatus;
    }

    public void setRepairHours(Integer repairHours) {
        Integer oldRepairHours = this.repairHours;
        this.repairHours = repairHours;
        propertyChangeSupport.firePropertyChange("repairHours",
                                 oldRepairHours, repairHours);
    }

    public Integer getRepairHours() {
        return repairHours;
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }
}

The facade (becoming the data control)

public class TechnicianAssignments {
    
    
    protected transient ProviderChangeSupport   providerChangeSupport 
                                     = new ProviderChangeSupport(this);  
    
    private ArrayList<ServiceAssignment> assignments = 
                                        new ArrayList<ServiceAssignment>();

    private PropertyChangeSupport propertyChangeSupport = 
                                          new PropertyChangeSupport(this);

    public TechnicianAssignments() {
        super();
        
        
        ServiceAssignment sa1 = new ServiceAssignment();
        
        sa1.setOrderId("1");
        sa1.setIncident("washing machine - short circuit");
        sa1.setRepairStatus("fixed");
        sa1.setRepairHours(new Integer(1));
        sa1.setCustomerName("John Muller");
        
        ServiceAssignment sa2 = new ServiceAssignment();
        
        sa2.setOrderId("2");
        sa2.setIncident("TV set broken");
        sa2.setRepairStatus("fixed");
        sa2.setRepairHours(new Integer(3));
        sa2.setCustomerName("Bill Spender");
        
        ServiceAssignment sa3 = new ServiceAssignment();
        
        sa3.setOrderId("3");
        sa3.setIncident("Cofee machine doesn't clean");
        sa3.setRepairStatus("pending");
        sa3.setRepairHours(new Integer(0));
        sa3.setCustomerName("Gaby Crown");
        
        ServiceAssignment sa4 = new ServiceAssignment();
        
        sa4.setOrderId("4");
        sa4.setIncident("dish washer - leaks water");
        sa4.setRepairStatus("fixed");
        sa4.setRepairHours(new Integer(5));
        sa4.setCustomerName("John King");
        
        assignments.add(sa1);
        assignments.add(sa2);
        assignments.add(sa3);
        assignments.add(sa4);
        
    }

    public void setAssignments(ArrayList<ServiceAssignment> assignments) {
        ArrayList<ServiceAssignment> oldAssignments = this.assignments;
        this.assignments = assignments;
        propertyChangeSupport.firePropertyChange("assignments", 
                                       oldAssignments, assignments);
    }

    public void refreshAssignmentsList(){
        //refresh the list for the UI to refresh
        propertyChangeSupport.firePropertyChange("assignments", 
                                           null, assignments);
    }

    public ArrayList<ServiceAssignment> getAssignments() {
        return assignments;
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }
    
    public void addProviderChangeListener(ProviderChangeListener l) {  
         providerChangeSupport.addProviderChangeListener(l);  
      }  
      public void removeProviderChangeListener(ProviderChangeListener l) {  
         providerChangeSupport.removeProviderChangeListener(l);  
      }  
}

Please have a special interest in the following method added to the data control facade:

public void refreshAssignmentsList(){
        //refresh the list for the UI to refresh
        propertyChangeSupport.firePropertyChange("assignments", null, assignments);
    }

This method invokes the provider change event for the "assignments" list that then propagates the event notification to the list for the  list view to refresh. This is important to ensure that the value change issued by the validation is displayed on the screen.

The View

The amx view renders a list based on the "assignments" collection that is exposed on the data control.

<amx:listView var="row" value="#{bindings.assignments.collectionModel}"
                  fetchSize="#{bindings.assignments.rangeSize}"
                  selectedRowKeys="#{bindings.assignments.collectionModel.selectedRow}"
                  selectionListener="#{bindings.assignments.collectionModel.makeCurrent}" 
                  showMoreStrategy="autoScroll"
                  bufferStrategy="viewport" id="lv1">
      <amx:listItem id="li1"> ... </amx:listItem>
</amx:listView

 The list view is configured such that selecting a list item will automatically (or automagically) set the selected row as current in the MAF binding layer. The input component that exposes the working hours gets a value change listener that points to a managed bean to evaluate the user entry and - if needed - to correct it.

The managed bean code

The managed bean can live in viewScope as there is no state it needs to keep across page navigation. The code simply checks whether the user provided value is above 8 hours or below 0 (though I never experienced a field service to not at least charge for one hour. So probably 0 is for warranty cases)

public class RepairAssignmentsBacking {
    public RepairAssignmentsBacking() {
    }

    public void onRepairHoursChange(ValueChangeEvent valueChangeEvent) {
        
          Integer newVal = new Integer((String) valueChangeEvent.getNewValue());
          Integer oldVal = new Integer((String) valueChangeEvent.getOldValue());
        
          
          //validation condition
          if(newVal>8 || newVal<0){
              
             BasicIterator iterator = (BasicIterator) AdfmfJavaUtilities.getELValue
                                      ("#{bindings.assignmentsIterator.iterator}");          

             ServiceAssignment sa = (ServiceAssignment) iterator.getDataProvider();
             
             sa.setRepairHours(oldVal);
             
             DataControl dc = AdfmfJavaUtilities.getDataControlById(
                                 iterator.getDataControl().getName());

             TechnicianAssignments dcImpl = (TechnicianAssignments)dc.getDataProvider();
             
             //refresh UI
              dcImpl.refreshAssignmentsList();
                            
              throw new AdfException("The working time cannot be greater than 8 hours", AdfException.ERROR);
          }
    }
}

As you can see, the trick really is to know about the iterator that servers the values for the listView component. If te underlying data control is POJO based, then accessing the base iterator (shown in the code above) and calling getDataProvider() gives you access to the entity bean that represents the row. This is where you perform the update.

But this is not the whole 9 years we need to go to get the use case working. After updating the entity you need to refresh the collection the entity is located in. For this, a method has been exposed on the POJO DC (explained earlier) to trigger the refresh of the collection by invoking the provider change event for the "assignments" property.

DataControl dc = AdfmfJavaUtilities.getDataControlById(
                   iterator.getDataControl().getName());

TechnicianAssignments dcImpl = (TechnicianAssignments)dc.getDataProvider();
             
//refresh UI
dcImpl.refreshAssignmentsList();

Btw.: If you don't like the end user notification via an ADFException, which I agree is not a good user experience, you can

i) define a managed bean and an error message component / area in the amx. The managed bean is updated from the validator and thanks to property change listeners would update the error message instantly.

Or, ii) you use a custom popup as explained here https://blogs.oracle.com/mobile/entry/how_to_open_and_close

----

QUIZ YOURSELF: There is a design flaw in the code above! Can you spot it? No? Then read on.

----

A Problem you may run into with this ...

The managed bean code so far assumes that the input data is validated while the row currency doesn't change. This however may happen if validation is in a value change listener and thus - for the use case I describe - can be considered a design flow in my sample.

So lets fine tune the sample code to ensure it works even if the row currency canges to avoid updating the wrong row. To avoid the problem, you add a set property listener to the list item in the view to memorize the selected row in the managed bean. This way, the validation code can explicitly set the current row in the model to the row that the changes should be applied to.

Step 1 Add a PropertlyListener to the list item

    <amx:listItem ...
        <amx:setPropertyListener id="spl1" 
                                 from="#{row.rowKey}" 
                                 to="#{viewScope.RepairAssignmentsBacking.rowKey}"
                                 type="action"/>
      </amx:listItem

The row key of a list item is set to a property defined in the managed bean that also performs the validation. Because the bean is in view scope, the property value becomes accessible to the validation code

Step 2 Set the current row

if(newVal>8 || newVal<0){
           
      BasicIterator iterator = (BasicIterator) AdfmfJavaUtilities.getELValue(
                                   "#{bindings.assignmentsIterator.iterator}");    
            
      //in case of row currency change, this is the key of the newly selected row
      Object currentRowKey = iterator.getCurrentRowKey();
      //set the current row back to the row that the values should be reset for
      iterator.setCurrentRowWithKey((String) this.rowKey);
      ServiceAssignment sa = (ServiceAssignment) iterator.getDataProvider();
             
      sa.setRepairHours(oldVal);
           
      //now set the current row back to the row the user navigated to (if at all)
      iterator.setCurrentRowWithKey((String) currentRowKey);            
           
      DataControl dc = AdfmfJavaUtilities.getDataControlById(
                                      iterator.getDataControl().getName());

      TechnicianAssignments dcImpl = (TechnicianAssignments)dc.getDataProvider();
           
      //refresh UI
      dcImpl.refreshAssignmentsList();
                          
     throw new AdfException("The working time cannot be greater than 8 hours"
                            , AdfException.ERROR);
 }


The Complete Managed Bean Code (with the change)

The corrected managed bean code shown below highlights the changes in bold. 

 public class RepairAssignmentsBacking {
    
    private Object rowKey = null;
    private PropertyChangeSupport propertyChangeSupport = 
                                          new PropertyChangeSupport(this);

    public RepairAssignmentsBacking() {
    }

    public void onRepairHoursChange(ValueChangeEvent valueChangeEvent) {
        
          Integer newVal = new Integer((String) valueChangeEvent.getNewValue());
          Integer oldVal = new Integer((String) valueChangeEvent.getOldValue());
        
          
          //validation condition
                    if(newVal>8 || newVal<0){
              
             BasicIterator iterator = 
            (BasicIterator) AdfmfJavaUtilities.getELValue(
                             "#{bindings.assignmentsIterator.iterator}");    
             
             //in case of row currency change, this is the key of the newly selected row
             Object currentRowKey = iterator.getCurrentRowKey();
             //set the current row back to the row that the values should be reset for
             iterator.setCurrentRowWithKey((String) this.rowKey);
             ServiceAssignment sa = (ServiceAssignment) iterator.getDataProvider();
             
             sa.setRepairHours(oldVal);
             
             //now set the current row back to the row the user navigated to (if at all)
             iterator.setCurrentRowWithKey((String) currentRowKey);            
             
             DataControl dc = AdfmfJavaUtilities.getDataControlById(
                                                    iterator.getDataControl().getName());

             TechnicianAssignments dcImpl = (TechnicianAssignments)dc.getDataProvider();
             
             //refresh UI
              dcImpl.refreshAssignmentsList();
                            
              throw new AdfException("The working time cannot be greater than 8 hours", 
                                     AdfException.ERROR);
          }
    }


    public void setRowKey(Object rowKey) {
        Object oldRowKey = this.rowKey;
        this.rowKey = rowKey;
        propertyChangeSupport.firePropertyChange("rowKey", oldRowKey, rowKey);
    }

    public Object getRowKey() {
        return rowKey;
    }

    public void addPropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.addPropertyChangeListener(l);
    }

    public void removePropertyChangeListener(PropertyChangeListener l) {
        propertyChangeSupport.removePropertyChangeListener(l);
    }
}

Monday Nov 09, 2015

Mobile: The Heart of Your Digital Transformation


By 2020 over 80% of the world’s population will own a smartphone. As an enterprise, are you ready to embrace this digital revolution? Digital transformations are happening all around us; some are disrupting entire industries, others are impacting their enterprises and all are revolutionizing how we engage with customers, partners and employees.

Hear Rimi Bewtra, Senior Director of Mobile, IoT and Digital Product Marketing at Oracle discuss in detail how:
  • Companies are leveraging mobile technology and making their own digital transformation
  • You can develop a complete, end-to-end enterprise mobile strategy
  • To increase your mobile ROI and drive improved personal engagements

Register now

Follow@OracleMobile 

Thursday Oct 15, 2015

Mobile @ OOW: Mobilize Enterprise Applications using Oracle Mobile Cloud Service

Enterprise mobility can transform businesses by providing new channels for end user interaction, leading to dramatic process improvements and faster decision-making. But most enterprise services are built for content-heavy, bandwidth-rich web applications, not mobile ones. Wiring up services to mobile apps and managing the accompanying infrastructure (notifications and storage) is cumbersome.

In addition, enterprises building a portfolio of mobile apps face significant challenges: integrating with myriad back-end systems both on premises and with software-as-a-service (SaaS), dealing with multiple security policies across B2E and B2C apps, and increasing the velocity of mobile app delivery.

The cloud is a perfect match for mobile application needs. Oracle Mobile Cloud Service goes beyond simple mobile-first mobile-backend-as-a-service (MBaaS) solutions to provide a robust, enterprise-grade cloud infrastructure to achieve all this and more.

Attend these three sessions to hear more about how you can use Oracle Mobile Cloud Service to mobilize enterprise applications.

  • Mobilize Oracle E-Business Suite Using Oracle Mobile Cloud Service: Monday, Oct 26, 2:45 p.m. | Moscone South—270
  • Extend SaaS Applications to Mobile Using Mobile Cloud Service: Monday, Oct 26, 4:00 p.m. | Moscone West—3020
  • Go Mobile with JD Edwards EnterpriseOne: Tuesday, Oct 27, 12:15 p.m. | Moscone West—3024

There's also a  hands-on workshop (Wednesday, Oct 28, 8:45 a.m. | Hotel Nikko—Mendocino I/II) to learn how to build the next generation of enterprise mobile apps. Discover how back-end developers can easily collect and format information from their vast enterprise infrastructure -- on-premises packaged applications, cloud data, or custom applications -- and present it to mobile developers. 

For more information on other Mobile sessions at Open World 2015, refer to Mobile Central.

Follow@OracleMobile 

Wednesday Oct 14, 2015

Stretching AMX components with amx-fitParent and amx-filmStrip-stretchItems styles

Stretching AMX components with amx-fitParent style

If you want any AMX component (that is stretchable, i.e. one that responds to width and height dimensions like a listView or deck) to stretch and take up the size of it's parent component (assuming the parent component is allocating space for children vs. one that is as tall or wide as its children), you can use the amx-fitParent style on that component.

Example 1:

In the example shown below, an amx:deck component is placed within the center facet of a panelStretchLayout. On setting the styleClass attribute to amx-fitParent , the deck component takes up the available width , available height, and stretches to fill up the center facet. You can find this example implementation in the LayoutDemo public sample. Look at tabsTopWithFilmStrip.amx page within the LayoutDemo sample for more details.

<amx:deck displayedChild="item1" styleClass="amx-fitParent" id="d1"/>



Example 2:

In the example shown below, an amx:commandLink component is placed within an amx:filmStripItem component. On setting the styleClass attribute to amx-fitParent, the commandLink component takes up the size of it's parent filmStripItem component.  You can find this implementation in the WorkBetter sample application. Look at login.amx page for more details.

<amx:filmStripItem id="fsi3">
<amx:commandLink id="cl3" styleClass="amx-fitParent" shortDesc="Login Karen Link">
</amx:commandLink>
</amx:filmStripItem>

Stretching AMX components with amx-filmStrip-stretchItems style 

If you want the filmStripItem component to stretch and take up the size of the filmStrip component, you can set the attribute itemSizing="stretched" on the filmStrip component. Alternatively you can also set the styleClass attribute on the filmStrip component to amx-filmStrip-stretchItems. The former option (setting itemSizing attribute to 'stretched') is the recommended approach.

Example :

In the example shown below, the section highlighted in the red represents a filmStripItem component which is taking up the entire available width and available height of the it's parent (filmStrip) component. You can find this implementation in the LayoutDemo sample application. Look at filmStripWithFlex.amx page for more details.

<amx:filmStrip id="fs1" styleClass="amx-fitParent" itemSizing="stretched">
<amx:filmStripItem id="fsi1">
</amx:filmStripItem>
</amx:filmStrip>

Oracle MAF 2.2 New Features

V2.2 is the new release of Oracle Mobile Application Framework (Oracle MAF). This blog provides an overview of several new features added in this release.

 1. UI Components

This release introduces several new components and enhancements to help developers support latest mobile patterns

Swipe To Reveal :

Allows user to swipe on a row in a list to reveal contextual actions. This functionality can be added to an AMX page using <amx:accessoryLayout/> component.

Example usage:

Swipe To Reveal Sample


Pull To Refresh :

Allows developers to swipe down and refresh the contents of a page. This capability can be added to a page using <amx:refreshContainer/> component.  

Example usage:

Pull to refresh

New Layout Components 

Allow developers to easily build flexible Dashboard and Grid layouts.  

MasonaryLayout :  Typically used for building Dashboard style pages involving tiles laid out in the form of a grid. The size of each tile can be adjusted using css. It provides the following key capabilities

  1. Adjusts layout based on the available width
  2. Allows drag and drop of tiles in the layout 

Example usage:

Pull to refresh

FlexLayout: A layout component that displays its children in a group. It supports horizontal and vertical orientations, with automatic changes based on the device orientation. By default, the layout creates even space for each child, and stretches these children within its boundaries.

Example usage: 

Data Visualization Enhancements :

  • Stock Charts : Stock charts are useful for displaying stock data across time. A unique feature of stock charts is the ability to render series data as 'candlesticks' representing open, close, high, and low stock price data.
  • Chart Drill Events : Allows users to tap on series or group or data items to raise drill events to drill in to the chart data
  • Support for overview and vertical orientation for Timeline component

Alta Mobile V1.4 :  

New skin with support for Google Material design for Android L

2. Data binding Enhancements

Support For Nested DataControl Context:

  • Allows developers to build recursive navigation flows using Task Flows
  • Isolate state at the Taskflow level
  • Manage the number of Taskflow/DC instances maintained in the stack 
  • Example: Opportunity List -> Opportunity Detail -> Account Detail (from the account associated with the Opportunity) -> Related Opportunity List -> Opportunity Detail (with a different Opportunity than previous)

3. Navigation Enhancements

  •  Full support for Android back button
    • Support for overriding the default behaviour using <amx:systemActionBehavior> tag or JS API
    • “__back” navigation rule is used by default
  • Support for limiting pageFlowScope variables to TaskFlow boundaries

4. Performance Improvements

  •  30% overall performance improvement compared to previous release
  • Major Performance improvements in the following areas
    • JSON Parsing : New parser based on JSONP
    • New Optimized JVM : 30-40% performance improvement in Java processing
    • UI performance improvements to improve the page rendering time 

Summary  

Oracle MAF 2.2 has many exciting features and we encourage everyone to upgrade and give it a try. Several of the features listed above are used in the sample applications shipped with the release. Please refer to the samples for sample code. Component Gallery, Layout Demo and WorkBetter sample Apps cover most of the features listed above.

Monday Oct 12, 2015

Oracle OpenWorld Mobile Showcase

OpenWorld Mobile Showcase

If you’re coming to OpenWorld, make sure you stop by the Mobile Showcase in Moscone South. 

Samsung is the lead sponsor for the showcase. This year the showcase is a blend of mobile partners and Oracle teams representing their mobile solutions.  

Samsung will be bringing showcasing their mobile solutions and additionally will be showing off their latest mobile devices in the Mobile Showcase lounge.  Stop by, charge up your device and check out the latest phones and tablets from Samsung.  I’m still holding out hope they decide to bring their Gear VR headsets, but since they don’t launch until November, it doesn’t look like they’re likely to steal their own thunder. Oh,well, they will still be showing off some beautiful hardware to checkout.



Other partners include:

Xamarin: They’ll be demoing their build, test, monitoring tools for building native apps for various mobile platforms including iOS and Android. These are 100% native apps with the performance and native experience you’d expect from a native app.  Plus, checkout their demo building mobile apps connecting to Oracle Mobile Cloud Service (MCS) with the Xamarin SDK for Oracle Mobile Cloud Service.  If you’re a C# developer, you’ve pretty much have a great tool for enterprise mobility.

If you’re a mobile HTML5 developer, you should be interested in checking out Sencha, which has a large and established HTML5 community building cross platform apps on their hybrid-mobile framework.

Who else? We’ve got Nokia, AuraPlayer, Sofbang, AirWatch, Syniverse and more.   In addition to the demo kiosk, there will be theatre sessions showing longer, more in-depth demos throughout the day.

Oracle will be showing off Oracle Mobile Cloud Service, Oracle Mobile App Framework, Oracle Mobile Security Suite, mobile development with Oracle JDeveloper and Oracle Enterprise Pack for Eclipse, mobile applications and more. 

It’s going to be a great showcase that you won’t want to miss.

About

This blog is is dedicated to announcements,tips and tricks and other items related to developing, integrating, securing, and managing mobile applications using Oracle's Mobile Platform. It is created and maintained by the Oracle Mobile product development team.

Archive of past entries

Even More Mobile Development Blogs

Oracle A-Team Site - Mobile Related Entries

Code samples from the Community

Fusion Middleware Blogs

Search

Archives
« February 2016
SunMonTueWedThuFriSat
 
2
5
6
7
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
     
       
Today