Developing Custom Components with Oracle Mobile Application Framework

Introduction

Oracle’s Mobile Application Framework (MAF) is a great tool for web developers to enter the mobile application development space in an easy and seamless manner. MAF’s full support for JavaScript and CSS to create application behavior and styling, means developers can carry over the same skills of web development into the world of mobile apps. While MAF’s AMX component set is fully featured and is a great springboard to get your application jump-started, MAF developers are not limited by the component set that AMX offers. MAF supports custom components to be created in pure JavaScript and CSS. These custom components become custom tag libraries that can be reused. Custom components can even be used to wrap standard AMX components, making them ideal to customize or enhance the component set.

Main Article

In this article we will take a brief look at how you can build and use a custom component. We will examine how the custom tag is created, registered, and the various artifacts that are required to support your custom component. To demonstrate the concepts, we will build a very simple custom component that is rendered like a notification badge inside the application. The component will have support for custom attributes that are used by the component implementation. In this article we also use Font Awesome, which is a font and CSS toolkit, which gives you glyphs and icons that are font characters instead of images. The finished notification badge looks like this finished The AMX code that accomplished this is simple a single line

<notification:badge id="badge" class="fa fa-bell fa-2x appIcon" unread="#{applicationScope.unreadCount}"/> 

This is obviously not an AMX component tag, and it has some non standard attributes like “unread”. This is how a custom component will appear inside an AMX page, and we are going to take a look at how this tag was created, and what happens at runtime to render the icon with a notification badge. The finished project can be downloaded here.

The parts of a custom component

A custom component is really just a JavaScript function, that is usually accompanied by some CSS styling. The CSS Styling is of course optional. The job of the JavaScript function is to render the component by generating HTML for it. Any AMX page is ultimately rendered as HTML by the MAF runtime on the device on to the device’s WebView. The DOM structure that a custom component’s JavaScript function generates is inserted at the location where the custom component tag is encountered in the AMX markup as the MAF runtime renders it. You can also pass parameters to your JavaScript function using custom attributes on the tag you have defined for your custom component. In the example below, the unread attribute is used by the JavaScript render() function to decide what HTML to generate and what CSS classes to add to the DOM nodes when rendering the component. In the AMX markup, the custom attribute values can be EL expressions like in our example. The EL expression is evaluated and the JavaScript function sees the evaluated value. For our simple example, we are nor triggering this from real push notifications, but instead just driven off of an  input slider component’s value. For more complex custom components, you can obviously organize the code with additional helper functions, but the very minimum for the JavaScript code is to define what HTML to generate when our custom tag is encountered. The JavaScript for our custom component is below :

(function () {
    // ---------------------------------------------------------------------------------------------
    // This is the Javascript for the notification:badge custom component
    // ---------------------------------------------------------------------------------------------
    var badge = adf.mf.api.amx.TypeHandler.register("http://xmlns.ateam.com/notification", "badge");
    badge.prototype.render = function (amxNode, id) {
        var rootElement = document.createElement("div");
        try {
            var link = document.createElement("a");
            //tag's custom attribute values being accessed in the JavaScript function
            var userClass = amx.getTextValue(amxNode.getAttribute("class"));
            var unreadCount = parseInt(amx.getTextValue(amxNode.getAttribute("unread")));

            link.setAttribute("href", "#");
            link.setAttribute("class", userClass);
            rootElement.appendChild(link);

            var badgeOverlay = document.createElement("span");
            badgeOverlay.className = "notification-badge";

            // Display a badge only if there are more than 0 unread notifications
            if (unreadCount > 0) {
                badgeOverlay.appendChild(document.createTextNode(unreadCount));
            }
            else {
                badgeOverlay.className = "notification-badge-hidden";
            }
            link.appendChild(badgeOverlay);

        }
        catch (problem) {
            alert(problem);
            adf.mf.log.Framework.logp(adf.mf.log.level.SEVERE, "notification", "render", 
                                       "Problem with custom component creation: " + problem);
        }
        return rootElement;
    };

})();

A closer look at the JavaScript and the APIs used

How did MAF know to invoke the function you just wrote, when it encountered the <notification:badge> tag in the AMX page ? This is because we registered a TypeHandler for the tag within the tag name-space in the first line of code above. This is how custom tags are registered with the runtime and how the framework knows what function to call to generate the HTML for a custom component when that custom component’s tag is encountered. TypeHandler is the primary API for creating and managing custom UI components. The adf.mf.api.amx.TypeHandler.register is a static API that lets you provide a name-space and a tag name to register that tag under that name space, and the API returns the TypeHandler subclass. This subclass can be used to prototype functions for this TypeHandler which is now registered for that (namespaced) tag. The function that should be prototyped mandatorily is on the returned TypeHandler is, unsurprisingly, render() which you see in the very next line of code. The render() function returns an HTMLElement subclass by creating the DOM hierarchy for this custom component, as shown in the code snippet above. This function can be arbitrarily complex and reply upon helper functions as well. The important thing is that it should prototype the render() function and return a single HTMLElement that is the root element for the custom component’s HTML.

Putting it together

Once you have the custom component’s JavaScript function and the optional CSS styling used by the component in a CSS file, you can start using it in your MAF application. Custom components are added as JavaScript and CSS additions at a feature level. To add the files to your feature, open the maf-feature.xml in your ViewController>ApplicationSources>Meta-INF directory. Select the feature you want to add your custom component to, and add the JavaScript and CSS files to the “includes” section.

addition

Once the assets are added to the feature, you can add the custom component to your AMX page. In our example we have the notificationBadge.js that does the registration and provides the TypeHander render prototyping, the notification.css that contains the CSS classes that we used to style the notification badge.  The complete AMX code is short and concise :

<?xml version="1.0" encoding="UTF-8" ?>
<amx:view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
          xmlns:amx="http://xmlns.oracle.com/adf/mf/amx"
          xmlns:dvtm="http://xmlns.oracle.com/adf/mf/amx/dvt" 
          xmlns:notification="http://xmlns.ateam.com/notification">
 <amx:panelPage id="pp1">
   <amx:facet name="header">
     <amx:outputText value="Custom Component Sample" id="ot1"/>
   </amx:facet>
   <amx:facet name="primary">
     <notification:badge id="badge" 
                      class="fa fa-bell fa-2x notification-icon"
                      unread="#{applicationScope.unreadCount}"/>
   </amx:facet>
   <amx:inputNumberSlider label="# of Notifications" 
     id="ins1" minimum="0" maximum="15" 
     stepSize="1"
     value="#{applicationScope.unreadCount}"/>
 </amx:panelPage>
</amx:view>

Do not forget to add the xml namespace for your custom tag to the <amx:view>  tag.
<amx:view xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xmlns:amx="http://xmlns.oracle.com/adf/mf/amx"
                xmlns:dvtm="http://xmlns.oracle.com/adf/mf/amx/dvt"     
                xmlns:notification="http://xmlns.ateam.com/notification">

Custom Components : Pros, Cons and Best practices

With custom components in MAF, it becomes possible for web developers to implement sophisticated and highly customized mobile user experiences with the familiar JavaScript and CSS toolset.  On the flip side though, custom components do come with certain limitations, at the time of writing this article, custom components do not offer a way to be packaged and shared as a single unit. This means that custom components need to be added as feature additions on all features that require it. The JDeveloper design time does not support using the property inspector to set the custom components properties. This is because the properties offered by a custom component are designed by the custom component creator and can be totally arbitrary, while no such meta-data is known to the JDeveloper design time.

When building your own custom components, it is useful to have a set of general development guideines. It is recommended to surround the JavaScript function in a try... catch block to easily troubleshoot and debug the code using JavaScript debugging methods described here(Android) and here (iOS). Do not assume the DOM structure or develop a custom component that assumes and depends on a certain DOM structure for the rest of the DOM tree. This makes the component fragile and changes to the DOM structure either in the rest of the application code or in the AMX component implementation (perhaps in a later framework release) might break the custom component. Always use the APIs to introspect the AmxNode structure than relying on an observed DOM structure. In most cases like our example above, the markup that is generated also needs to be styled. For styling, any CSS class available through the skin or through a CSS file added to the feature can be used. In our example, we add a separate CSS file that contains CSS classes specific to the custom component. This keeps the skin, which is applicable to any MAF application, devoid of custom component styling. When developing CSS classes, it is a good practice to follow a naming convention like <namespace>-<component>-<element> , which is the structure that AMX uses. For example,class names like notification-badge and notification-badge-text would make it clear what component the CSS class belongs to, and would also be better at avoiding collisions.

Dialing it up a notch : Nesting AMX components inside your custom component

Taking the custom components to the next level, the first question would be if you can nest standard AMX components inside your custom component tag, and the answer is yes. This is particularly useful when you are building a custom component that wraps one of more AMX components to add specialized UI behavior. The TypeHandler, AmxNode and AmxTag APIs among others, make the process of mixing and interacting with AMX components simple and easy by letting you have programmatic access to these AMXNodes. Two of the most important functions on the AmxNode API to programmatically access and manipulate the custom component’s nested child components would be the would be the getChildren() API that returns an array of AMXNodes that are the children of the current node. This lets you use the API to manipulate the child nodes before they are rendreded. Just as with the custom component you created above, the standard AMX components also generate their HTML root elements that are HTMLElement subclasses when they are rendered by the runtime. The renderDescendants() API rendders the child nodes and returns an array of HTMLElements that are the root elements created by the child AmxNodes. Our simple example here to get the reader introduced to custom components does not leverage these APIs, and stands to prove that even with very simple implementations meaningful custom components can be built. The framework also ships with a set of samples that show how these APIs work. The samples can be found in the PublicSamples.zip file within the jdev_install/jdeveloper/jdev/extensions/oracle.maf/Samples

I hope that this has whet your appetite for more on custom components, and we shall dive deeper in to custom components and the APIs in a future post. You can download the sample application used in the article here : MAFCustomComponentExample

 

Add Your Comment