Tuesday, August 20, 2013

for..in vs. Object.keys performance in JavaScript

For those curious, you may debate if the JavaScript for..in loop is faster than creating an array via Object.keys and iterating over that array.

Take an object with 26 attributes:

var attrs = {"a":1,"b":2,c:3,d:4,e:5,f:6,g:7,h:8,i:9,j:10,k:11,l:12,m:13,n:14,o:15,p:16,q:17,r:18,s:19,t:20,u:21,v:22,w:23,x:24,y:25,z:26}

Now run this code in a browser (non-IE would be best):

// Loop #1
var date = (new Date()).getTime();
for (var i = 0; i < 100000; ++i)
{
  for (var attr in attrs)
  {
    var num=attrs[attr];
  }
};
var endDate = (new Date()).getTime();
console.log(endDate - date);

// Loop #2
date = (new Date()).getTime();
for (var i = 0; i < 100000; ++i)
{
  var keys = Object.keys(attrs);
  for (var k=0, ksize = keys.length; k < ksize; ++k)
  {
    var num=attrs[keys[k]];
  }
};
endDate = (new Date()).getTime();
console.log(endDate - date);

In Chrome on a Mac, the first loop takes about 2000-3000ms and the second loop 4000-5000ms. In Safari on a Mac, it will take 500-1000ms for the first loop and 1000-2000ms for the second loop. Obviously the code above could be improved for loop #2 if the "var keys..." was outside of the outer for loop, but this test is to simulate a real app where the objects being iterated over would change and not always be the same. The conclusion is simply that for..in is about twice as fast as using Object.keys and iterating over the resulting array.

Thursday, October 25, 2012

Creating an adaptive layout in ADF for desktop vs. tablet

It is often ideal to create pages tailored to be accessed on a mobile device or on the desktop, but not every company has the resources to author and maintain two web sites. What if you could define most of your pages to be the same for both devices but simply adapt the layout based on the device?

The Oracle ADF framework has the necessary tools to make this easy. For example, take an application where you desire to show a list of navigation links that is always accessible and a main content area. On the desktop, you usually have a larger screen that you can work with. There you may choose to use a splitter pane that the user can collapse that is shown on the left side of the browser. On the right would be the details of the current page. On a mobile device, you may not be able to spare the width required to show the splitter and would instead rather show the navigation links in a popup.

The solution is fairly simple in ADF, by leveraging the dynamic declarative component, facet references and JSTL tags (that are supported in either facelets or JSP), you can have a layout that easily adapts to the device.

To create such a site, first create an ADF application. You can do so by downloading a 11.1.2.3.0 JDeveloper from the JDeveloper OTN page. Open JDeveloper and create a new Fusion Web Application (ADF) and accept the defaults. Create a new Page (either JSP or facelets based on your preference). In that page, you will want to add a reference to a page fragment that defines the content for a dynamic declarative component (note that a page template will also work and will allow you to pass an ADF model page template binding if desired). In this component, you will want to pass 2 facets, one for the navigation and one for the body. Should you want the same navigation on every page, you could create a page template and include the declarative component from the page template.

The technique allows you to define the content for the navigation area and the content separately from the layout. By passing these areas as faces to a declarative component, it allows you to dynamically place the content into different locations based on the device.

Example index page showing the usage of the declarative component:

<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<f:view xmlns:f="http://java.sun.com/jsf/core" xmlns:af="http://xmlns.oracle.com/adf/faces/rich">
  <af:document title="index.jsf" id="d1">
    <af:form id="f1">
      <af:declarativeComponent id="dc" viewId="/dynamicLayout.jsff">
        <f:facet name="navigation">
          <af:navigationPane hint="list" id="np1">
            <af:commandNavigationItem text="Home" id="cni1"/>
            <af:commandNavigationItem text="Manage items 1" id="cni2"/>
            <af:commandNavigationItem text="Manage items 2" id="cni3"/>
            <af:commandNavigationItem text="Manage items 3" id="cni4"/>
            <af:commandNavigationItem text="Manage items 4" id="cni5"/>
            <af:commandNavigationItem text="Manage items 5" id="cni6"/>
            <af:commandNavigationItem text="Manage items 6" id="cni7"/>
            <af:commandNavigationItem text="Manage items 7" id="cni8"/>
            <af:commandNavigationItem text="Manage items 8" id="cni9"/>
            <af:commandNavigationItem text="Manage items 9" id="cni10"/>
            <af:commandNavigationItem text="Manage items 10" id="cni11"/>
            <af:commandNavigationItem text="Manage items 11" id="cni12"/>
            <af:commandNavigationItem text="Manage items 12" id="cni13"/>
            <af:commandNavigationItem text="Manage items 13" id="cni14"/>
            <af:commandNavigationItem text="Manage items 14" id="cni15"/>
            <af:commandNavigationItem text="Manage items 15" id="cni16"/>
            <af:commandNavigationItem text="Manage items 16" id="cni17"/>
            <af:commandNavigationItem text="Manage items 17" id="cni18"/>
          </af:navigationPane>
        </f:facet>
        <f:facet name="content">
          <af:panelGroupLayout layout="scroll" id="pgl2">
            <af:outputText value="lorem ipsum dolor sit amet officia modi anim voluptas studiosius. viribus doloribus tempus, ratione quasi. voluptates laborum quis, unum rerum. quamque cumque dolor explicabo reprehenderit. maiores nostrud ad, mites quidem. honestam quamque placeat, Et propter. ad anim cum acceperat Ut. mollitia iure humani, quaerat acceperat. nec eos culpa reprehenderit dignissimos. aspernatur feris laboriosam dolore.
lorem ipsum dolor sit amet repellat et molestias liberos quia. fugiat enim sint, reddidit earum. magnam eveniet id ut necessitatibus. reprehenderit aliquam commodo viribus dolor. et voluptate et, reprehenderit non. esse reiciendis reddere; culpa.
lorem ipsum dolor sit amet non voluptatem sapiens. officia harum illum ita rerum. iste in ipsa perniciosissimis natus. passim esse abditos animi dolore. quibusdam irure ut, Quis ipsum. tenetur victu ut enim quos. illum orationem se, delectus reddere;. sed velit velit, eaque aliqua. aliquam sapiente lorem voluptatum dolore. aut fugiat totam rerum.
lorem ipsum dolor sit amet nondum voluptatem ab. primo Duis propagabant consequatur non. dolor quia victu quid dolore. dolorem eos Sed odio inscientiam. id aut in, et facilis. quo deserunt sed, sint errorem. studiosius delectus maxime qui ducimus. sapiens soluta alias labore ex. et corporis unam non se. nisi corporis et expedita.
lorem ipsum dolor sit amet facere ex occaecati. magnam quae reprehenderit, culpa acceperat. natus nesciunt quia lorem iste. qui eu aequabile quisquam.
lorem ipsum dolor sit amet quadam earum quidam. perferendis in in, optio dolor. expedita cognovit rerum minus quia. quas qui quanta fuga deleniti. quam labore deinde ducimus materia. praesentium non saepe, si ut. voluptas quia se acceperat.
lorem ipsum dolor sit amet et et rerum quibusdam commodo. aute temeraria nuptias, audientes non. rem voluptate viderat sit irure. alias reddidit aliqua propter pariatur. ad doloribus doloremque, recusandae pariatur. elicere ad ius viribus tempor. dignissimos dolores res, dignissimos optio. consectetur deleniti atque excepturi.
lorem ipsum dolor sit amet est et placeat. religionis et et animi dispersos. modi nulla voluptate, officia esset. consequatur sunt quisquam soluta et. reprehenderit illo et, liberos cupiditate. inesset victu cupiditate magnam.
lorem ipsum dolor sit amet fero se congregavit recusandae ratione. ea ad quamque, nihil nobis. aperiam facilis ea voluptate conpulit. perferendis eveniet suscipit cupidatat ius. cognovit cillum conpulit impedit eligendi. deserunt corporis non aut Et. ut eam rem, non propagabant. molestiae quis commodi nemo.
lorem ipsum dolor sit amet amet aliquip reiciendis esse dolores. et beatae consequatur, unam et. divinae beatae agros, ducimus sint. do provident quo, temeraria qui. omnis Nemo utilem Excepteur minus. qui culpa perspiciatis, ratione in. et exercitationem qui, a libero. minima fero vel quo.
lorem ipsum dolor sit amet et consequatur qui Quis aperiam. voluptatibus nisi ad, viderat corporis. non nihil amet quos in. et numquam earum voluptate totam. voluptatum ad animi, minim et. videlicet perspiciatis nisi, libero propter. repellendus victu propter, rerum officiis. quidam possimus quicquam, dolore nihil. rerum ullamco aliquip, quaerat rerum. asperiores a vitam non.
lorem ipsum dolor sit amet hominum ipsum feris voluptatem ea. eos ad Itaque, dolorem consectetur. qui eligendi voluptatibus distinctio corporis. occaecat eveniet Excepteur, eos nihil. acceperat dolores elicere ea blanditiis. id esse satellitibus vagabantur.
lorem ipsum dolor sit amet consectetur consequatur officia sit nulla. expedita molestiae est nihil deserunt. molestiae adipisci quo iusto reclamantes. sint quia et ea.
lorem ipsum dolor sit amet maiores magnam deleniti. legitimas ex et nulla voluptates. sapiens alias quas victu nulla. veniam fuga eos est dolorum. autem distinctio corrupti, fuga inesset. consequatur autem viribus, officia Ut. propter non reprehenderit et culpa. laboriosam maximas in ipsum repudiandae. dolor et expedita sunt divinae. magnus autem necessitatibus bestiarum.
lorem ipsum dolor sit amet ad tempore opportunitas. aut sit magni, quis elit. sapiente liberos occaecat adipisci delectus. consectetur dignissimos et, inventore officii. impedit nec in, nuptias vitam. in voluptatem rerum minim et. administrabant et et, quo adipisicing. beatae ratione in, pariatur consequatur. quas animis fero si eius. quod rerum molestiae aliqua.
lorem ipsum dolor sit amet qui honestam acceperat animi voluptas. excepturi saepe et, tempus maxime. ut doloremque dolores, illo officiis. eam sed optio, unde Nemo. omnis laborum quis fugiat in. et voluptate aut, inducens in. molestias honestam laboris, mollit facere. voluptatem eos nihil occaecat repellendus. dolor cupiditas pleraque quos mollit. mollit hic conpulit aut.
lorem ipsum dolor sit amet in molestias qui. reclamantes sit reddidit, quis abutebatur. esse deserunt Ut, laboris atque. quas locum At, eaque studiosius. aspexerat elit dispersos, distinctio inesset. quo aut quadam, orationem ullamco. ac inscientiam lorem, amet ut. voluptas ex silvestribus videlicet.
lorem ipsum dolor sit amet aute abutebatur perniciosissimis omnis non. quis ullam sunt, nemo qui. quia id rerum mansuetos nobis. eos quid in si bestiarum. est earum placeat, nulla et. in reddidit sint, homines nostrum. in aut iste, accusamus et. velit in nulla soluta rem. atque honestam At ratio dolorum. natus omnis dolores laborum.
lorem ipsum dolor sit amet ratione aliquid rerum nam assumenda. nihil omnis ut, studiosius id. rem corporis enim quoddam aut. pariatur pleraque est voluptatem Duis. explendam deserunt pleraque numquam qui. commodo dolor sint, studiosius reddere;. Nemo ut voluptas rerum sit. officia sed nec, a est. reprehenderit mites laudantium veniam."
                           id="ot1"/>
          </af:panelGroupLayout>
        </f:facet>
      </af:declarativeComponent>
    </af:form>
  </af:document>
</f:view>

In the declarative component, you will now want to use JSTL to build either a splitter or a button that shows a popup based on the device. ADF has an API for the browser agent that includes capabilities. One of the capabilities that is exposed is if the agent is a touch screen device (Android and iOS devices). By referencing this capability from JSTL, you can change what components are created. Example EL usage:

<c:if test="${adfFacesContext.agent.capabilities['touchScreen'] eq 'none' ? true : false}">

Using this you can create two layouts, each using af:facetRef to include the passed in facets. Note that JDev will report errors that the facet is used more than once, but that is only because it does not know the c:if tag will be sure to only render one set. Example code for the declarative component page fragment:

<?xml version='1.0' encoding='UTF-8'?>
<ui:composition xmlns:ui="http://java.sun.com/jsf/facelets"
                xmlns:c="http://java.sun.com/jsp/jstl/core"
                xmlns:af="http://xmlns.oracle.com/adf/faces/rich"
                xmlns:f="http://java.sun.com/jsf/core">
  <af:componentDef var="attrs" componentVar="comp">
    <af:xmlContent>
      <component xmlns="http://xmlns.oracle.com/adf/faces/rich/component">
        <description>Layout dynamically adjusted based on user agent</description>
        <facet>
          <facet-name>
            navigation
          </facet-name>
        </facet>
        <facet>
          <facet-name>
            content
          </facet-name>
        </facet>
      </component>
    </af:xmlContent>
    <c:if test="${adfFacesContext.agent.capabilities['touchScreen'] eq 'none' ? true : false}">
      <af:panelSplitter id="dc_ps1" dimensionsFrom="parent">
        <f:facet name="first">
          <af:facetRef facetName="navigation" />
        </f:facet>
        <f:facet name="second">
          <af:facetRef facetName="content" />
        </f:facet>
      </af:panelSplitter>
    </c:if>
    <c:if test="${adfFacesContext.agent.capabilities['touchScreen'] eq 'none' ? false : true}">
      <af:panelStretchLayout topHeight="2em" bottomHeight="0" id="dc_psl1"
                             dimensionsFrom="children">
        <f:facet name="top">
          <af:panelGroupLayout id="dc_pgl1">
            <af:commandButton id="dc_cil1" text="Open navigation" iconPosition="trailing"
                                 icon="/afr/fusion/dropdown_ena.png">
              <af:showPopupBehavior popupId="dc_navPopup" triggerType="action" align="overlap"/>
            </af:commandButton>
          </af:panelGroupLayout>
        </f:facet>
        <f:facet name="center">
          <af:facetRef facetName="content"/>
        </f:facet>
        <f:facet name="bottom">
          <af:popup id="dc_navPopup">
            <af:facetRef facetName="navigation" />
          </af:popup>
        </f:facet>
      </af:panelStretchLayout>
    </c:if>
  </af:componentDef>
</ui:composition>

Now load the page in a browser and on an iPad and see the layout change. How it looks on the desktop:

desktop image

How it looks on an iPad:

iPad image

How it looks on an iPad with the navigation open:

iPad image

You can download the sample JDeveloper workspace for 11.1.2.3.0 here.

Wednesday, October 3, 2012

tr:forEach to soon provide support for changes to the collection

In order to address the issues that I discussed in my blog regarding the c:forEach tag, I implemented enhancements to the Trinidad forEach tag since I am a developer on that team.

The changes are part of TRINIDAD-1940 which should be part of the future Trinidad 2.1.0 release. Until then, you can try out the changes by using the snapshot build.

The forEach tag documentation does not have the new documentation as the site is currently based on the 2.0.2-SNAPSHOT documentation.

Until then, here is a preview of the new forEach documentation (Content owned by the ASF is covered by the Apache version 2.0 license):

Summary

Tag name: <tr:forEach>

The forEach tag provides additional functionality from the JSTL <c:forEach> tag. The tag is only supported in JSP.

Facelets

The <tr:forEach> is not supported in facelets. As a convenience, the tag handler for the <c:forEach> will be used in facelets when a <tr:forEach> is used in a facelets page, but no Trinidad functionality will be supported.

Supported collections

The Trinidad forEach tag supports the following collections for the items attribute:
  • Java array
  • java.util.List
  • org.apache.myfaces.trinidad.model.CollectionModel
  • java.util.Map

When using a map, the map should be one that has a consistent order. Either the java.util.LinkedHashMap or java.util.TreeMap are examples of maps that provide this functionality. The keys for the maps must implement java.io.Serializable.

Usage

Valid Use Cases

The for each tag is not as efficient as component stamping. When possible, the <tr:iterator> should always be used instead of the for each tag. There are times this is not possible and usually involve the inclusion of content from other pages. For example, if different <jsp:include> tags need to be generated, pointing to different files based on items in a list, a for each tag must be used and not a stamping component like the Trinidad iterator.

ID and Component State

Due to the way JSF and JSP create ValueExpression objects, the for each tag cannot safely map the expressions created and stored on JSF components to the iteration of the for each loop. As a result, it is possible to cause a discrepency between the component's location in the for each tag and the value expressions that are stored in the tag.

Due to this limitation, the forEach tag must place usage requirements on the pages they are placed on. For index based collections (arrays and lists), the components must be mapped to the index of the for each loop and not the item in the collection. In these cases, the index of the for each loop is stored into the value expressions created and stored into the components. This results in the component state (like expanded state of disclosure components) being tied to the index of the for each loop. As a result, a key based collection (map or collection model) is always recommended when the collection may change between requests.

For key based collections (map and collection model), the component IDs must be associated with the key of the collection. This is because the key of the collection is used in the value expressions and if the ID were not bound to the key, then the component state would not match the EL values. You should associate the component to the key using immediate EL in the ID attribute. See the examples below.

Generated IDs

When the collection may change between requests, a key based collection should be used. When this is done, it is not recommended that JSF be allowed to generate the IDs of any components inside the for each tag. The reason is that JSF auto-generated IDs are based on the sequence that they are created in the current page. If items are re-arranged in the for each loop, the IDs will have a different generation order. As a result, JSF will not find the components with auto-generated IDs when it tries to map the JSP tag to the component ID. Existing components will be thrown out and new ones will be created and component state will be lost if this occurs.

Reordering and Collection Modifications

Modifications to the collection in the same JSF view (between requests) are only supported with key based collections. Even with this being supported, the for each tag will not auto-rearrange the children.

When JSP seeks to locate a component that was created in a previous request, it creates the ID that it expects the component to have and then searches the children of the parent component of the tag for the child component with that ID. If the component is not found, a new one will be created. If it is found, the framework does not do anything to the component, it is left to exist where it is found. This means that if you reorder items in your collection between requests, the component order will no longer match the order of the items in the collection. Page authors must reorder the components when changes to the collection are made.

Trinidad supports re-ordering of components using the change manager (org.apache.myfaces.trinidad.change.ChangeManager) and the org.apache.myfaces.trinidad.change.ReorderChildrenComponentChange class. The change manager may be retrieved from the org.apache.myfaces.trinidad.context.RequestContext instance. See the Trinidad demo application for examples on how to reorder the components when the collections changes.

When using a key based collection, it is recommended to use a naming container to simplify tying the component ID to the key. See the examples below.

Examples

Please see the Trinidad demo application for complete examples.

Index Based Collection

<tr:forEach var="person" items="#{forEachBean.simpleList}" varStatus="vs">
  <f:subview id="sv${vs.key}">
    <tr:panelGroupLayout id="personPgl" layout="horizontal">
      <tr:outputText id="personName" value="#{person.firstName} #{person.firstName}"
                     inlineStyle="padding-right: 1em;"/>
      <tr:outputText id="clientId" value="(Client ID: #{component.clientId})"/>
    </tr:panelGroupLayout>
    <tr:spacer id="s1" height="1em" />
  </f:subview>
</tr:forEach>

Key Based Collection

<tr:forEach var="person" items="#{forEachBean.collectionModel}" varStatus="vs">
  <f:subview id="sv${vs.key}">
    <tr:panelGroupLayout id="personPgl" layout="horizontal">
      <tr:outputText id="personName" value="#{person.firstName} #{person.firstName}"
                     inlineStyle="padding-right: 1em;"/>
      <tr:outputText id="clientId" value="(Client ID: #{component.clientId})"/>
    </tr:panelGroupLayout>
    <tr:spacer id="s1" height="1em" />
  </f:subview>
</tr:forEach>

Example Re-Order Code

// Create a list of all the children IDs of the parent component of the for each loop. This must
// contain all the children, not just those created by the for each tag.
List<String> orderedIds = ...;

// Get the reference to the component that is the parent of the for each tag. For finding relative
// components, you may try using
// org.apache.myfaces.trinidad.util.ComponentUtils.findRelativeComponent(UIComponent, String)
UIComponent forEachParent = ...;

ReorderChildrenComponentChange componentChange = new ReorderChildrenComponentChange(orderedIds);
RequestContext requestContext = RequestContext.getCurrentInstance();
ChangeManager cm = requestContext.getChangeManager();
// The component change must be added before the tag execution (before the render response JSF
// phase)
cm.addComponentChange(FacesContext.getCurrentInstance(), forEachParentComponent,
  componentChange);

// Ensure that the view is updated during PPR requests
requestContext.addPartialTarget(forEachParentComponent);
        

Attributes

Name Type Supports EL? Description
begin primitive int or java.lang.Number subclass No index at which iteration begins
end primitive int or java.lang.Number subclass No index at which iteration ends
items Object Only EL the collection to iterate over. Supported classes:
  • Java array
  • java.util.List
  • org.apache.myfaces.trinidad.model.CollectionModel
  • java.util.Map
step primitive int or java.lang.Number subclass No number to increment the index by on each iteration
var String No name of the variable exposed when iterating that references the item in the collection
varStatus String No name of the loop status exposed when iterating.

Properties:

Name Description
index the current index (int)
count the total number of times the for each tag will iterate (int)
begin the index to start at when collections are not used (int)
end the last index when collections are not used (int)
first true if this is the first iteration (boolean)
last true if this is the last iteration (boolean)
key the index (int) for index based collections and when the items has not been specified. The key (java.io.Serializable) for key based collections