Thursday, 2 July 2009

Atmosphere 0.2 now Available


Atmosphere is an annotation-based, easy to use, portable framework for developing Ajax Push and Comet applications, and it's now at version 0.2.

This version has added annotations for REST applications; improved support for Servlet 3.0, Tomcat, Jetty, GlassFish, and JBossWeb 2.1.x; and BroadcasterLookup support for EJB and non-web applications. New sample applications include Twitter, Counter, and Chat.

Posted by ted.goddard at 11:10 AM in Entries by Ted Goddard

Rogers iPhone 3G S Resolution


After purchasing the iPhone 3G S, things went downhill briefly. It turned out that the phone number I picked with my new contract was previously owned by someone under "legal investigation" (at least, that's what the people phoning me said). That, combined with the fact that my previous voice plan was significantly cheaper compelled me to switch the iPhone to my previous plan.

Apparently, the only way to do this was to order a new iPhone (which would arrive by UPS in three to five days), not use the other iPhone more than 30 minutes, and return it to the store. I realized that this was ridiculously inefficient and a strange form of torture (having the iPhone, but not being allowed to use it) but was willing to endure this in order to save the activation fee and a recurring $11 per month.

Of course, when I called five days later to request a tracking number, I learned that the iPhone had never shipped and that they were out of stock.

Fortunately, the reasonable option was now possible: I could activate the iPhone under my existing plan and cancel the new plan. Either the representative was more knowledgeable (she was certainly very helpful -- rather than simply transferring me, she acted on my behalf for both the shipping cancellation and the plan cancellation), or Rogers had a change of heart due to the problems people were experiencing.

In summary, it is possible for Rogers customers to upgrade to an iPhone 3G S and just add a data plan to their existing voice plan. (Perhaps the threat of 3G competition from Telus in October is at work as well.)

Posted by ted.goddard at 11:01 AM in Entries by Ted Goddard

Wednesday, 1 July 2009

Rogers iPhone 3G S Activation Nightmare


How long does it take to buy a new iPhone 3G S in Canada? It probably depends on how many people are ahead of you in line. For instance, if you arrive when the store opens (at 10:00 AM) and one person is ahead of you, it should take at least two and a half hours (I arrived at 10:00 AM and left at 12:30). The process that Rogers uses is very thorough: after the clerk signs in, authorizes the transaction, enters in the SIM ID, and asks you to choose a phone number, the system times out, discarding everything that has been entered in so far. I'm unclear on the purpose of the last step, but it must be very important, because it was performed at least three times.

But that's just the funny story; the sad story is that the one person ahead of me in line arrived at the store at 7:00 AM. Being from New York, they expected long lineups and fierce competition for all of the five iPhones available at the store. (That's right -- in three hours, a total of five transactions were processed.)

What's also "funny" is that the "hardware upgrade price" to upgrade my aging Motorola V220 from 2005 was $347 (not the $199 you see advertised) and required a three year contract. So, I switched to a new plan (also requiring a three year contract).

Surely, with all the cell phone fees we pay in Canada (like my $35 activation fee -- hey, that's only $14/hour) Rogers could purchase enough computing power to activate 5 phones per store.

But what can we learn from this experience (other than the need for cell phone carrier competition in Canada)? First, some part of the back-end system did not have sufficient capacity, perhaps a registration service. (I suspect this because the web application remained reasonably responsive, but still failed with a timeout; however, contradicting this is the fact that the entire session would be lost each time, indicating that the web tier actually was the problem.)

Could Ajax Push have helped? Possibly: if a back-end service is slow, the web interface can be designed so that time consuming operations are updated via push, allowing better user feedback (and the ability to manage multiple tasks from the same browser interface).

The new iPhone is great, by the way, Apple did their part. (Also, the clerk was very pleasant throughout the entire frustrating process; if anyone earned the activation fee, it was her.)

Posted by ted.goddard at 6:26 PM in Entries by Ted Goddard

Ajax Push for OpenSocial


At CommunityOne, Chris Schalk and I presented our ideas on how to integrate Ajax Push with OpenSocial: Developing Sleek and Collaborative Applications with OpenSocial and AJAX Push. For the dramatic conclusion to our talk, we showed a demo that used ICEfaces Ajax Push to push OpenSocial Activity updates to connected users.

The demo is a good way to learn about both the OpenSocial and ICEfaces APIs, but you'll need a few things to get started:

Download the Java version of Shindig and copy shindig-server-1.1-SNAPSHOT.war to Tomcat 6 webapps/shindig.war (this gives the Shindig application the expected URL).

You can download the icefaces-opensocial application source code from subversion:

http://anonsvn.icefaces.org/repo/projects/icefaces-opensocial/trunk/icefaces-opensocial

Download ICEfaces 1.8.1 and unpack it.

Edit icefaces-opensocial/build.properties so that icefaces.home points to your "icefaces" directory from ICEfaces 1.8.1. Invoke "ant" and copy the .war file in icefaces-opensocial/dist to webapps/ in Tomcat 6.

Then, launch separate browser instances for the URL http://localhost:8080/icefaces-opensocial . As the different users create Activities (with a specified title and body), the updates will be pushed to the other users. It would be easy to customize the code to accept more interesting Activity objects with application-specific properties; for instance, with image or hyperlink payloads.

How does it work?

When the ActivityBean starts up, we use SessionRenderer.addCurrentSession(personName) to register the current session for all updates for the logged in user (the demo is single user from a login perspective; you will likely wish to expand on that capability). We also prepare a stub for our connection to the local Shindig server. Note the TOKEN initialization; this is necessary to encourage the Shindig server to allow us to change (rather than just read) Activities.

When the user clicks on the submit button activate() is called. We create a new Activity in the Shindig server with the provided input and cause the new Activity to be pushed out to all connected browsers with SessionRenderer.render(personName)

The final main part of the application is the display of the Activities themselves; it's simply a dataTable that displays a list of Activities fetched via socialClient.fetchActivitiesForPerson(personName).

As you can see, the push capability is entirely driven by the web application front-end. An interesting extension to the OpenSocial RESTful protocol would be to incorporate push capabilities there directly, so that HTTP clients could listen for server-side changes.

Chris has posted the full set of slides on slideshare.

Posted by ted.goddard at 6:26 PM in Entries by Ted Goddard

Ajax vs JavaFX


Is JavaFX the "next thing" to replace Ajax? Should we even be comparing these technologies at all? Just for fun, here's an entry from my blog in a parallel universe (I have HTTP-only access to this parallel universe through alien technology that was discovered inside an asteroid that crashed in northern Alberta. Note that ICEfaces also makes use of this advanced alien technology.)

One of the most important features of NetBeans 7.0 is the new timeline-based editor for enterprise applications. Just drag and drop your form fields into the keyframes and the tweening animator will interpolate automatically. For instance, if your case number is derived from the submitted date, the date field will morph into the case number field. For users, the interface to the generated application is simple and familiar, like a movie player. They just position their cursor on the first field and press play. It's fun for users to try to get their text input into the various fields as focus automatically sweeps across the animated data entry form.

GlassFish now also boots via JavaFX rather than OSGi. Just press the play button, and GlassFish modules from the individual keyframes start up in sequence. EJB and Asynchronous Servlets have new sound effects that can be dynamically loaded from the update center.

Further extending the capabilities of ICEfaces Ajax Push to support timeline-based enterprise applications (and their associated client-side Java runtime requirements), ICEfaces now introduces Hardware Push in association with Federal Express. Ajax Push allows page updates to be asynchronously delivered from the server to the client; Hardware Push takes this concept further, allowing a full client machine with current software to be pushed to the user (please allow two business days for delivery), allowing them to use timeline-based enterprise applications. Once the initial hardware is delivered, ICEfaces automatically determines the incremental updates, and pushes only the additional hardware or software necessary.

At this point, you're wondering if I've gone insane. Remember, the above text is taken from a web page in a parallel universe, it has nothing to do with me. So, let's return to our comparison: what is JavaFX?

JavaFX is an expressive rich client platform for creating and delivering rich Internet experiences across all the screens of your life. The JavaFX platform gives you unparalleled freedom and flexibility to create expressive content across multiple screens, including mobile devices, desktops, televisions, and other consumer devices.

In other words, JavaFX is a great way to create Applets for mobile phones (at least, those that run Java) and set-top boxes. Ajax is a great way to deliver user interfaces in web browsers for enterprise applications (and any other browser-based application). This distinction is very clear, and yet many people are confused (myself included, until late last week). The confusion arises not from the JavaFX message itself, but from where it is delivered. When Larry Ellison tells us during the JavaOne General Session:

"We'd like to see accelerated development based on this exciting new platform Java with FX, which now allows us - thank-you very much James [Gosling], no more AJAX tools, which a lot of suffering programmers will, you know, pray for you for the rest of their lives because they don't have to program in AJAX any more."

Everyone is sitting in the General Session as an enterprise Java developer, with the perspective that everyone else in the audience is an enterprise Java developer. Therefore, the message on JavaFX ("use it to develop your application") is perceived to be about enterprise Java applications. The message itself isn't confusing ("JavaFX is great for set-top box UIs"), but the environment of the message causes it to be misdirected. In other words, we shouldn't even be comparing JavaFX and Ajax. (Also remember that coding Ajax by hand does bring suffering, but using ICEfaces to build Ajax applications is really very pleasant.)

I suspect that The Ajaxians on Ajax vs JavaFX was one of the more entertaining talks of the show, but I was unfortunately unable to attend (conflict with JSF/JSR-314 and Servlet/JSR-315 expert group meetings). Maybe this was their conclusion as well?

Posted by ted.goddard at 6:26 PM in Entries by Ted Goddard

On the road to ICEfaces 2.0


Although ICEfaces 1.8 currently runs on JSF 2.0, it doesn't make use of many of the new features (but it is a good demonstration of the backwards-compatibility of JSF). To really take advantage of JSF 2.0, it is necessary for ICEfaces to leave its JSF 1.1/1.2 legacy behind. You can find the beginnings of this here under the codename "glimmer":

http://anonsvn.icefaces.org/repo/icefaces/scratchpads/glimmer

One of the biggest changes is brought by the standardization of Ajax input and output in JSF 2.0. When a user event is received in the browser, the containing form is serialized and sent to the server. On the server, the JSF lifecycle runs, decoding the serialized form and executing the application, resulting in an edit list of changes for the browser page. The edit list is returned, and the page is updated in-place. ICEfaces 2.0 makes use of this fundamental Ajax capability, but adds in Direct-to-DOM rendering to make application development easy. In effect, D2D rendering automatically determines the page update regions, eliminating the need for defining the regions manually, such as with:

<f:ajax execute="reset" render="form1"/>

The extension points provided by JSF 2.0 for this are found mainly in the PartialViewContext and ResponseWriter classes. For ICEfaces, this allowed considerable simplification because it allowed the unique aspects of ICEfaces Ajax capabilities to be implemented on top of the more common aspects of other frameworks, now built into the standard implementation.

Of course, another compelling feature of ICEfaces is Ajax Push, and we have implemented this differently for JSF 2.0 as well. The new strategy can be described as "notification/refresh". In ICEfaces 1.8, page updates for Ajax Push are determined by creating a synthetic request on the server, allowing the JSF lifecycle to run for that request, then pushing the page changes to the browser. This strategy has the advantage that page updates can be delivered with a minimum of HTTP traffic, but it has the disadvantage of a synthetic request (resulting in implementation complexity for ICEfaces). The notification/refresh strategy works as follows: when a render is requested, a notification is pushed to the browser, whereupon it makes an Ajax refresh request to the server, causing the JSF lifecycle to run, and current page updates to be returned and applied. (Note that we may bring back the synthetic request push capability as an optimization for mobile devices.)

Keep in mind that ICEfaces 2.0 will undergo significant changes from now until release, including major API and implementation changes, but if you're curious to play around with Ajax Push on JSF 2.0 and are interested in participating in the ICEfaces open source community, please fire up your subversion client, check it out, and invoke "ant".

You'll be able to learn more about this in person at CommunityOne

and JavaOne:

Posted by ted.goddard at 6:25 PM in Entries by Ted Goddard

Ajax Push for Data Visualization


Please join Francois Trible and me for a webinar on Tuesday on Ajax Push for Data Visualization. Essentially, we would like to show you the integration available between the ILOG and ICEfaces JSF components:

Data visualization driven through Ajax Push allows better business decisions to be made in a real time connected enterprise. This web seminar explains the importance of visualization and how it can be implemented in web applications with a combination open source and commercial technologies. Stepping through the code of a multi-user shipping/receiving application, attendees will learn how to include visualization in their applications and how to apply push to enable multi-user interaction and collaboration. Topics will include development and deployment considerations as well as a behind-the-scenes look at techniques used internally by the ILOG and ICEfaces technologies.

In every domain, we are faced with more and more complex information to deal with. Visualization is one of the most powerful techniques for absorbing information quickly. It's also a great way to make your web application look really cool ...

Posted by ted.goddard at 6:25 PM in Entries by Ted Goddard

The New New New Economy


The recent federal ruling on the open source economic standard is certain to have far-reaching consequences, but should not be surprising considering the current difficulties faced by the financial sector. A historical perspective will help us to understand it better:

The gold standard was in effect in some form until 1971, but did not allow sufficient flexibility or circulation for a modern economy. The next major phase was the New Economy, where web site hits were the unit of exchange. Perhaps if page views had been used instead, the collapse of the dot-com boom could have been avoided. Then, the New New Economy returned to money and added risk as the units of exchange, resulting in a destructive feedback loop with the most serious consequence being the abandonment of the automobile as a cultural focus.

With the New New New Economy, though, we've finally got it right. Instead of gold or the output of differential equations, the New New New Economy is based on the exchange of open source. Open source code has many advantages as the basis of a financial system: it is scarce (human labour is required to create it), it cannot be forged (it must compile and pass the test suite), it is durable (when stored under version-controlled conditions), and it is fungible (who wouldn't trade any of their stuff for some source code?).

Infrastructure changes will include the addition of computer terminals to grocery stores so that source code can be developed and saved at the time of food purchase. The greatest impact on the american consumer, however, will be the other regulations in the same bill requiring all open source exchanges to take place in metric units!

In the meantime, however, it is important to contribute to your favorite open source projects (such as ICEfaces) through traditional monetary means, by purchasing support. Your contributions translate directly into the generation of more lines of open source code, and constitute a vital part of the stimulus for the New New New Economy.

Posted by ted.goddard at 6:24 PM in Entries by Ted Goddard

Ajax Push is Easy


Let's take a step back and look at how we write Ajax applications. What would the ideal language for Ajax look like? What if we also want the application to be able to update the page at any time via Ajax Push (aka comet)? Need that be any more difficult than requesting "update the page"?

It can be easy, we just need to use the right language to define our Ajax application.

Since we're still a few steps back, let's see if we can just observe what our Ajax language looks like. We know the output of our application is HTML, mostly static but with the interesting parts dynamically generated. So the input language looks like HTML, but some of the elements variable in some way. We learned long ago that mixing executable code with markup is hard to maintain (remember JSP scriptlets?), so our markup language should incorporate dynamic elements by referring to values held by objects. It looks like this:

OK, so the name "inputText" is not ideal, and the XML namespaces are tedious; the point is that we can work with this language from a design perspective.)

Where's the Ajax though? When the user interacts with the page, how do we incrementally update it from the server without a full page refresh? (Should something this low-level really be the job of the application developer?) We've fully specified how the output markup relates to the objects in the model. It's should be the responsibility of the framework processing the language to update the page based on user input and application logic. Here's how it works:

  1. When the page is first rendered (fetched by the browser via a GET request) getters are called on the objects for dynamic values and a full page of markup is returned.
  2. When the user enters data, the form is encoded and submitted to the server via an XMLHttpRequest POST.
  3. The user input is decoded and applied to the model via setter methods.
  4. The page is rendered, changes from the existing page are determined, and an edit list is returned in the HTTP response.
  5. The browser applies the edits to the page.

In other words there was no reason for the application developer to be aware of Ajax at all, and this is exactly the process that takes place when you use the combination of JavaServer Faces and ICEfaces. Also notice that the rendering process that generates the initial page and the rendering process that determines the incremental updates are the same, considerably simplifying the Ajax pipeline.

But now what about Ajax Push? The application needs to change some text on the page independently of any user event; do we now need to rethink the web in terms of arbitrary executable clients on a message bus? After all, we just have some web pages that we want to update under application control, do we need a completely different set of abstractions?

Ajax Push is a revolutionary change in terms of what we can do on the web, but fortunately it's a small change from the above process. The only difference is that the page rendering (step 4) can be initiated by the application rather than the user, and the browser is listening for updates to arrive at any time, not just in response to user events. Here's how it works:

  1. When the page is first rendered the browser requests any page updates to be delivered when they are available.
  2. When something interesting happens in the application, the model is updated on the server, and the application renders the page.
  3. The page is rendered, changes from the existing page are determined, and an edit list is pushed to the browser.
  4. The browser applies the edits to the page.
  5. The browser requests any further page updates and the process repeats.

In other words, we can re-use our existing Ajax pipeline, and the application developer only needs to worry about one thing: requesting that a page be rendered. As you can imagine, this is only a few lines of code. With ICEfaces it looks like this:

Not only does the application developer not have to worry about low-level protocol details or messaging, they don't even have to worry about what has changed on the page -- this is all taken care of by the declared binding between the page and the model. The above code actually does a bit more, it also keeps track of all pages in each user's session, and all sessions in the specified group. Working with groups is useful for multi-user applications: for instance, when the moderator advances a presentation slide, all the users watching that slideshow should have their pages updated as well.

To summarize: we have dynamic pages that can be rendered on the server at any time, and the updates are pushed to the browser where the changes are applied to the DOM. From the application developer's point of view, that's all there is to it.

Under the Hood

Now let's look under the hood, is that simple as well? It's actually somewhat tricky, but the reasons for the difficulties are simple at least: browsers enforce per-host TCP connection limits, but JavaScript sandboxing does not allow easy communication between windows. Push notification requires a TCP connection to be held open, but each browser tab cannot have its own connection, otherwise the limited connections would be quickly exhausted when multiple tabs or windows are opened by the user. Yet it is difficult to share a single connection between browser tabs because the JavaScript sandboxes cannot easily communicate.

It turns out that the windows can communicate by sharing a cookie (it's a property of the connection, which is globally shared), allowing them to exchange rudimentary messages. Here ICEfaces takes a simpler approach than cometd. Rather than pass complex messages over the push channel (and be expected to propagate the full messages between browser windows), only the notification that page updates are available is pushed. The specific window then fetches its updates in the usual manner.

There is an additional wrinkle, however. What if the user opens multiple browser tabs to distinct push applications on the same server? In this case a single push connection must be shared between all of the applications and all of the tabs, requiring a single point of contact at the server which all applications must relay their push notifications to. It looks like this:

Fortunately, all of this complexity can be hidden behind a few simple APIs, as described above. (ICEfaces supports a variety of application servers in addition to GlassFish; for illustration, though, we show the custom integration with Grizzly as a way to optimize the server-side handling of push connections.)

Building Ajax Into the Web

Today we implement Ajax in JavaScript, but it is natural to consider incremental page update as an inherent part of the web. Rather than returning a full page response to a form POST, the browser could directly accept an edit list for the current DOM and apply it automatically. If connection limits were regarded per-frame (with each its own process so that when the Flash plugin crashes it doesn't take down the whole browser ... but I digress) then Ajax Push would also be very simple to implement.

Of course, with ICEfaces, Ajax Push is easy for the application developer now, independently of any upgrades we might want to consider for the web overall.

Posted by ted.goddard at 6:24 PM in Entries by Ted Goddard

From Ajax Push to JSF 2.0: ICEfaces on GlassFish


Ten minutes wasn't quite enough to explain ICEfaces. I've expanded on that with a podcast on ICEfaces, GlassFish, and JSF 2.0 on Aquarium TV.

This talk will provide details on how to build and deploy rich web applications with Ajax and Ajax Push (aka Comet) and catch a glimpse of the future with an overview of JavaServer Faces 2.0.

This presentation illustrates the fundamentals of Ajax Push, covering application design, development, and deployment, drawing on ICEfaces sample applications and implementation. Topics will include network protocols, application-level push APIs, GlassFish Grizzly integration, and the ramifications of the two-connection limit.

Looking forward to JSF 2.0, we will cover Ajax integration, new scopes and annotations, and custom components.

Posted by ted.goddard at 6:24 PM in Entries by Ted Goddard

Ajax Push - ZK versus ICEfaces


Recently we've noticed that all of the comparisons between ICEfaces and ZK have been somewhat one-sided (written by ZK). Check out Steve's blog to set the record straight. Besides the implementation concerns that Steve brings up, the next most important observation about ZK is that it uses a proprietary Java API and a proprietary markup language (all the while claiming to be "Standards-based").

Posted by ted.goddard at 6:23 PM in Entries by Ted Goddard

Comet Atmosphere 0.1-ALPHA1 Released


Atmosphere 0.1-ALPHA1 has just been released, providing a portable (Tomcat, Jetty, Grizzly and GlassFish) API for developing comet (aka Ajax Push) applications. Take a look at the introductory material to get started.

Two things are of particular interest with Atmosphere: portability, and simplicity. Until Servlet 3.0 is available, there is no portable way to write scalable push applications, but atmosphere provides a common API for obtaining asynchronous request processing features on the various servers. Also, using Atmosphere is much simpler than understanding the various ARP APIs. For instance, Atmosphere allows you to declare a @Broadcaster via an annotation and then use the Broadcaster to publish and subscribe to the suspended responses of the multiple users within your application .

Posted by ted.goddard at 6:23 PM in Entries by Ted Goddard

GlassFish Portfolio - ICEfaces PodCast


In case you missed the live webinar of the GlassFish Portfolio launch, the sessions are now online for viewing at your leisure. The ICEfaces talk is essentially an introduction to ICEfaces in ten minutes. I cover the basics of ICEfaces programming, describe Ajax Push, and touch briefly on how to address browser connection limits in push applications with the Ajax Push Server automatically installed via the GlassFish Update Center.

Posted by ted.goddard at 6:22 PM in Entries by Ted Goddard

ICEfaces in GlassFish Update Center


The ICEfaces demos are now easier to install than ever; just select them in the GlassFish update center for automatic installation. WebMC is included, allowing you to host your own slideshow conferences. Run ./updatecenter/bin/updatetool from your GlassFish install:

One of the key features of this set of demos is the included Ajax Push Server — it is essential for allowing multiple push applications to be used simultaneously from the same browser. Implementing push requires a persistent TCP connection to be maintained with the server to carry notifications to the browser, but according to the HTTP specification, a client should only open two connections to a given host. If each web application is handling push on its own, it's easy to see how multiple web applications can quickly exhaust the available TCP connections (we could also argue that distinct browser windows not sharing a JavaScript memory space are different clients, hence should have distinct TCP connections, but only Safari takes this rational approach). The solution is the ICEfaces Ajax Push Server.

By coordinating all of the push notifications for the different applications through a central point via JMS, it is possible to share a single connection to the browser, allowing an arbitrary number of applications to be accommodated without exceeding the two-connection limit. ICEfaces detects the presence of the required JMS topics (which are installed for you via the update center) and automatically takes advantage of the push server.

Posted by ted.goddard at 6:22 PM in Entries by Ted Goddard

ICEfaces 1.8RC1, JSF 2.0, and Maven


ICEfaces 1.8 is nearly here: ICEfaces 1.8RC1 is now available, awaiting your feedback just prior to the final release. Some of the new features include: Excel data export, Date and Time entry (full spacetime input will be available in the future), and improved clustering. We've also added support for maven (more details to follow on how to configure this).

Additionally, you can now begin to experiment with JSF 2.0. Here's how:

  • Download the ICEfaces 1.8RC1 source bundle.
  • Download GlassFish v3 Prelude.
  • Update GlassFish to JSF 2.0.0-9 in the GlassFish Administation Console Update Tool on port 4848.
  • Copy glassfish/modules/jsf-api.jar and glassfish/modules/web/jsf-impl.jar to icefaces/lib/ext/jsf-api-2.0.jar and icefaces/lib/ext/jsf-impl-2.0.jar respectively.
  • Build ICEfaces against JSF 2.0 with "ant" in "icefaces" (I take no responsibility for any strange runtime exceptions you might see if you don't build against the JSF 2.0 .jars)
  • Build auctionMonitor.war in icefaces/samples/auctionMonitor with "ant glassfishv2" (Yes, we should rename this target to "glassfish")
  • Copy dist/auctionMonitor.war to glassfishv3-prelude/glassfish/domains/domain1/autodeploy
  • Enjoy.

So does this mean that ICEfaces support for JSF 2.0 is done?

Not at all; JSF 2.0 is designed explicitly with Ajax in mind, significantly simplifying the integration points used by ICEfaces 2.0 (the 1.8 release described above does not invoke the new APIs). Developers will benefit from the new scopes and Facelet component development. Of course, ICEfaces 2.0 will retain the simple page design (no need to specify Ajax events or update regions) and Ajax Push capabilities that distinguish it now, and the component library will continue to grow.

Posted by ted.goddard at 6:22 PM in Entries by Ted Goddard