Categories
dev2dev Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

ALUI Portlet Pagination Cookbook

A coworker asked me how to write Plumtree portlet pagination (i.e. showing records in a UI and allowing the user to move from page to page, n records at a time) the other day and the ensuing discussion made me rethink how I’ve always done this and consider some new options. In this post, I attempt to shed some light on a very simple concept that turns out to be quite interesting in terms of implementation.

First, let’s consider some of the ways developers typically add pagination to standard web applications, forgetting about portals and portlets for a moment. Let’s call n your page size, i your page number and t the total number of records. On the first page, you might see n records laid out with alternating row colors and a 1-n of t marker to show you were you are, e.g. Now Showing Records 1-5 of 45. There’s probably also a next button and a back button (grayed out for now), a first page button (also grayed out), a last page button and maybe even a “fast-forward” button to move forward several pages at a time.

A very easy way to implement this in a standard (non-portal) MVC Java/J2EE Web application would be to carry some state, say i (the page index), on the querystring. For example, say you have a record viewer servlet called “RecordView” running on a Java-enabled Web application container such as Tomcat. You could have something like http://bdg-plumtree:8080/bdg-plumtree-context/RecordView as your URL. Your servlet code snippet might look something like this:

import javax.servlet.*;
import javax.servlet.http.*;

...

private Model model;

protected void doPost(HttpServletRequest request,
                      HttpServletResponse response)
                        throws IOException {

    //if you didn’t specify a page argument, display the
    //first page (pageIndex = 0)
    int pageIndex = request.getParameter("i") == null ? 0 :
      Integer.parseInt((String)request.getParameter("i"));

    //make a call into the data model to get the i-th page
    //and put the results in the request
    request.setAttribute("results", model.getResults(pageIndex));

    //forward to your view (JSP)
    request.getRequestDispatcher("view.jsp").forward(request, response);
}

In the view, you would simply print out the results, line-by-line, perhaps alternating row colors to make it easier to read. Then you need to display the little marker that tells you what page you’re on and the buttons to get next/back/to the end/etc. The logic to figure out what buttons to display and which buttons to gray out is a little involved, but it’s mundane enough that I don’t think I need to cover it here. The important part for this discussion is what’s in the links that actually take you forward and (soon) back. The answer there is simple enough — just create links that append the appropriate querystring and you’re all set. Here’s an example:

<a href="http://bdg-plumtree:8080/bdg-plumtree-context/RecordView?i=<%=i + n%>">Next</a>

So far so good.

The problem is that when you try to do this in an ALUI portlet, you don’t have direct access to the querystring, so you can’t use this approach. You need to store the variable i using some kind of state mechanism. Here are your options:

  1. The HTTP session
  2. A portlet setting
  3. A session setting (G6 and up only)

There are tradeoffs between #1 and #2 but #3 offers a good compromise. Let me explain.

If you use the HTTP session, your users’ page setting (i), will only persist for the life of the session, which is probably desirable. But, if you’re using the Plumtree caching model for portlets (such as expires or last-modified/etag caching), you can’t cache this portlet at all, which is definitely not desirable. The reason is that every page, regardless of the value of i, will have the same cache key.

To implement session-based pagination, you only need to change two lines of code:

request.getParameter("i")
becomes request.getSession().getAttribute("i") and your anchors that control the moving from page to page now need to point to a different controller servlet. (Remember, you don’t have control over the query string any more in portletland).

<a href="http://bdg-plumtree:8080/bdg-plumtree-context/ChangePage?i=<%=i%>">Next</a>

The ChangePage servlet simply sets the session attribute and then calls return to portal as shown here:

request.getSession().setAttribute("i",
    request.getParameter("i"));
PortletContextFactory.createPortletContext(request,
    response).getResponse().returnToPortal();

The only way to unique-ify the cache key, therefore caching your portlet appropriately, is to go with approach #2, the portlet setting. Now, when users advance i, they will be creating a new cache entry for each value of i (since settings are always part of the cache key). The drawback is that the users’ page settings (i) will persist longer than the life of the session. In other words, they could be browsing page 5, then they could leave the portal for several days, come back, and still be on page 5!

For your view:

PortletContextFactory.createPortletContext(request,
  response).getRequest().getSettingValue(SettingType.Portlet, "i");

And for your ChangePage servlet:

PortletContextFactory.createPortletContext(request,
  response).getResponse().setSettingValue(SettingType.Portlet, "i", request.getParameter("i"));
PortletContextFactory.createPortletContext(request, response).getResponse().returnToPortal();

G6 offers a nice compromise: the session setting. (If I had to guess, I would say the session setting was designed expressly for pagination.) With a session setting, you get the best of both worlds: a page setting that lasts only for the duration of the session but also a unique cache key so that you can effectively cache your portlet.

For your view:

PortletContextFactory.createPortletContext(request,
  response).getRequest().getSettingValue(SettingType.Session, "i");

And finally, for your ChangePage servlet:

PortletContextFactory.createPortletContext(request,
  response).getResponse().setSettingValue(SettingType.Session, "i", request.getParameter("i"));
PortletContextFactory.createPortletContext(request,response).getResponse().returnToPortal();

All of these methods have one drawback — they refresh the entire page on every portlet pagination click. So . . . stay tuned for an upcoming post on AJAX-based portlet pagination.

Comments

dev2dev comments are listed in date ascending order (oldest first)

  • This is a good start for everyone on Java. If you are like me and are a .NET developer at heart, you will be glad to know the .NET Web Control Consumer supports the use of a MS data grid control. All that is needed is for the developer to drag one of these objects onto the form, hook it up to a data source, and presto, you get pagination, storability, edit, and any other thing you always wanted from a table.

    Andrew Morris – [email protected]

    Posted by: drews_94580 on August 15, 2006 at 2:03 PM

  • It’s true — in many ways, .NET is way ahead of Java. I have only a little experience with the .NET Web Controls and the Control Consumer, but from what I’ve seen, there’s a lot of power and flexibility there. My post was making the “I want to roll my own pagination in Java” assumption. 🙂

    Posted by: bucchere on August 16, 2006 at 8:30 PM

Categories
bdg dev2dev Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Mingle & the PHP EDK moved to dev2dev’s CodeShare

Just in case you’re looking for our two opensource projects that were once accessible via the Plumtree Portal, you can now find them on BEA dev2dev’s CodeShare. Here are the links:

Mingle: https://mingle.projects.dev2dev.bea.com
PHP EDK: https://plumtree-php-edk.projects.dev2dev.bea.com

Enjoy!

Categories
bdg Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Our relationship with BEA + BEAWorld 2006

I often get asked the question: what’s your relationship with BEA?

Without lapsing into buzzword bingo (e.g. synergy), let me just say this: bdg is a BEA “Select Services Partner.” We work on our own or team up with BEA sales and service folks (and sometimes other system integrators as well) to deliver ALUI solutions to our joint customers. Recently, our partner profile was approved by BEA for publication on their web site, where you can get the scoop on what we do here at bdg.

* * *

Considering all the fun we had at last year’s Odyssey, we’re planning on making a big splash at this year’s BEAWorld, which will feature a “Portal Pavilion” to cater to all the old Plumtree customers, partners and fans.

I can’t reveal too much about our plans just yet, but we’re putting out a call to our customers for speaking opportunities and gearing up to defend our “Booth of Pain” title (should we be called upon to do so). Also, if you plan to attend BEAWorld 2006, be sure to stop by the bdg booth to pick up some goodies . . . last year it was mousepads, but this year we’re thinking along the lines of something far less pragmatic. I’ll leave it at that. . . .

Categories
dev2dev Featured Posts Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Caveat Emptor: Using Varpacks in Pluggable Navigation

I got burned by this today, so I thought I would share it with you all. I had a perfectly good and working pluggable navigation that loaded a remote portlet in the left navigation pane. The only problem was that I had hardcoded the portlet ID and I wanted to make it configurable. So naturally I put the portlet ID and some other settings in a varpack, which is a reasonable thing to do. I then called my varpack from the constructor of my pluggable navigation class that implements IView.

Then I spent the next hour banging my head against the keyboard.

I kept getting a nasty Invocation Target Exception coming out of the reflection methods used to pre-load pluggable navigations. Eventually, using the handy space=MemoryDebug trick, I was able to ascertain that my varpack XML syntax was wrong and my varpack was looking empty to ALUI. So I fixed that and still, I got an ITE.

Then I looked at a PTSpy log and I discovered that the loading order of objects in ALUI was to blame. The portal loads built-in varpacks first, then it loads pluggable navigations, then it loads custom varpacks. So you can’t use the varpack from the IView constructor. I moved it to the Display method and everything started working again. Phew. 😐

So, shame on me for trying to use a custom varpack before it was loaded. Make sure not to make the same mistake!

Categories
bdg Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Announcing my new dev2dev blog

I’m very pleased to announce that I’ve been selected by BEA to be one of their featured bloggers on dev2dev, BEA’s product community site for developers by developers. You can read my dev2dev blog for all the Plumtree/ALUI tips that you’re used to finding here, but continue to check this space for news about bdg.

Also, stay tuned for a new Plumtree/ALUI podcast . . . coming soon!

Categories
dev2dev Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

Hello World + Portlet vs. Native Navigation

Hello world! This is my first post on my shiny new dev2dev blog and needless to say, I’m very pleased to be blogging here and to be given the opportunity to contribute to this prestigious and active community.

That being said, I hope you’ll find my contributions worthwhile. I plan to address mostly technical topics pertaining to AquaLogic User Interaction or ALUI (pronounced ah-LOO-eee by those in the know) with the occasional foray into the business side of ALUI and portals in general. I’ve been working with the ALUI (formerly Plumtree) products now for almost 10 years, but I also know a bit about Weblogic and I want to learn more about the other products in the AquaLogic family. So watch this space for news about all your favorite BEA products!

Okay, on to the topic at hand — navigation. Once upon a time (pre Plumtree 5.0), UI customization meant hacking through layers of ASP or JSP includes to find the right file and then changing this and that and trying not to break anything. And then when upgrade time came, you somehow had to merge all your changes out of the old UI code and into the new. It was a black art at best, a total goat rodeo at worst.

Enter Plumtree 5.0, which introduced the concept of header, footer and content canvas portlets along with “pluggable navigation.” For the first time, you could code your navigation against a supported and documented API and bundle it into a separate DLL or JAR file. When upgrades came along, you simply installed the new code and everything in your old DLL or JAR just worked. The only problem was that writing a pluggable navigation was really hard.

So, in ALUI G6 (the current version), you can easily make a navigation using a remote portlet and few lines of HTML, CSS along with a few handy XML tags. However, the old pluggable navigation model is still in the product, which begs the question: how do I know what form of navigation to use (native or remote)?

A lot of times you’ll hear me say, “it depends” i.e. it depends on your goals or it depends on your architecture. But, in this case, with 100% certainty, I can say that you want to go with remote navigation. Why? Because it’s about 1000 times easier to code, maintain and deploy, you can change it on the fly and in a breeze, you can include Javascript and CSS just like you would in a normal web page, you don’t have to work with confusing HTMLElement classes, you don’t have to know Java or C# . . . need I say more?

If you’re not sold yet on the concept of remote, XML tag based navigation over native pluggable navigation, consider the following example. Say I want to get all the mandatory with tab communities and display them in an unordered list.

Here’s the native code for that:

CListURLMediator mediator = new CListURLMediator(m_asOwner,
m_model.GetCategoryLinks(NavCategoryType.MANDATORYTABS,
false));
HTMLList ul = new HTMLList(1);
while (mediator.Next()) {
  HTMLListItem li = new HTMLListItem();
  li.AddInnerHTMLElement((HTMLAnchor)mediator.GetEntry());
  ul.AddInnerHTMLElement(li);
}
return ul;

And here’s the remote code:

<ul xmlns:pt='http://www.plumtree.com/xmlschemas/ptui/'>
  <pt:ptdata.mandtabcommsdata pt:id='mandtabcomms'/>
  <pt:logic.foreach pt:data='mandtabcomms' pt:var='c'>
    <li><pt:core.html pt:tag='a' href='$c.url'><pt:logic.value 
  pt:value='$c.title'/></pt:core.html></li>
  </pt:logic.foreach>
</ul>

The code speaks for itself. (If you never have to learn what a CListURLMediator is, consider yourself lucky.) Not to mention that you have to worry about the NavType and deployment issues and that you have to restart the portal any time you make a change to your navigation code.

So, is remote, tag-based portlet navigation the solution to all your navigation needs? Not exactly. With pluggable navigation, you can control six navigation areas: the top bar, above the header, below the header, the left side, the right side and above the footer. It’s easy enough to suppress the top bar and replace the first three areas with your header portlet. Same goes for the footer. But what about the left and right sides? You can’t really replace those with portlets unless you use the two narrow columns, which leaves you with only a single portlet column for content.

So you’re in a pickle — you read this post and you’re now the world’s biggest remote navigation fan — but you can’t use them in left and right navigation. There is a solution. If you read this post in the newsgroups, you’ll see that it’s possible to write some code that will load a remote portlet into the left and/or right columns. (The only thing I would change about this code is make sure you put the portlet ID in a VarPack instead of hardcoding it.) Using this fairly simple approach, you can have your cake and eat it too.

Comments

Comments from BEA dev2dev are listed in date ascending order (oldest first)

  • were doing UI customization to modify the top nav section, but would like to move to remote portlet with tags instead for reasons you mention.
    If i understand correctly, we have to do this in header portlet. How do we enforce that that the same top nav is used across the portal if the community managers are able to change the tag code in the header portlet?

thanks
steph
[email protected]

Posted by: sviau on July 9, 2006 at 7:04 AM

  • That’s a great question and I’m glad you asked it. First let me clear something up: community managers cannot and should not change the tag code. However, they can change which header portlets are being used for the communities they manage (and thus choose a whole new set of tags or no tags at all).

    Fortunately, there’s a easy way to prevent this from happening using community templates. There’s a check box on the “Header and Footer” page of the community template that says “Force Community to use Header and Footer from Experience Definition.” You’ll want to check this box. Then, as the portal administrator, you can mandate which community templates your community managers can use. Set it up so that your community managers are forced to use the community template with this box checked and you should be good to go.

    Posted by: bucchere on July 10, 2006 at 2:38 AM

  • Hopefully this is applicable here…I am trying to brand (or customize) the title bar of a portlet(s). I have determined how to change the text on the title bar (PortletResponse.setTitle) but cannot find nothing on changing colors or adding colors to the title bar. Can this even be done? Is there a CSS class somewhere or can it be done programmatically? Thanks for your assistance.

    Posted by: jayparker on January 2, 2007 at 12:28 PM

  • This should do the trick:
    .platportletHeaderBg {
     background-color: red;
    }

    Posted by: bucchere on January 10, 2007 at 7:13 PM

  • is the remote code supported in 6.1 mp1? I attempted to run it and nothing is generated. Has mandtabcommsdata gone away? I notice it also is absent from the current edocs http://edocs.bea.com/alui/devdoc/docs60/Portlets/Adaptive_Portlets/Using_Adaptive_Tags/PlumtreeDevDoc_Portlets_Adaptive_Navigation.htm

    Posted by: geoffgarcia on October 2, 2007 at 11:26 AM

Categories
Software Development

Cracking open ISO files

I’ve been doing a lot of installing and re-imaging lately 🙁 and I’ve had to work with ISO files quite a bit. I haven’t found any Windows freeware that writes ISO files to CDs that actually work. However, I did discover that WinRAR has built-in support for extracting ISO files onto your hard drive.

It won’t, however, help you burn an ISO to a CDR, but hopefully with this info you might not have to!

(By the way, Mac OSX has native support for burning ISOs to CDs built into the operating system.)

Categories
Software Development

Adventures in desktop linux

I’ve had such a good experience using Fedora on several of bdg’s enterprise systems (SugarCRM, Subversion, Bugzilla, Vetrics, Connotea, etc.) that I thought I would give desktop linux a shot.

What a mistake.

Actually, it was a good learning experience. But still, a mistake.

First I download Fedora Core 5 (Bordeaux) using BitTorrent. My first problem was mastering the ISO files to CD. Windows has no native support for this (surprise) and for the life of me I couldn’t find a free product without filesize restrictions or other issues. Finally I remembered that I had a purchased a license for Sateira DropToCD some time ago, so I attempted to use that miserable excuse for a program. I tried to burn the five CDs at 24x (~10 minutes each) and my computer would not recognize them. The CD-Rs, once burned, were useless, yet Windows did not show any data on them nor a volume label.

I did a little Googling and then remembered that I needed to burn at 4x in order to get Fedora Core 4 (and Solaris x86 — another mistake) to work. So I tried that (at ~30 minutes per CD) and again, total failure. Finally I used a real operating system, OS X, running on my wife’s Mac laptop, to create the ISOs. (Of course OS X has built in support for ISO burning that works like a charm.)

After all this nonsense, I was finally ready to install FC5. So I backed up all my company files, music, photos and other stuff to my Western Digital 250 Gb external firewire drive and off I went.

I must say, there are some nice things about FC5. Unfortunately, it’s a short list:

  1. The installer, Anaconda, is awesome.
  2. The graphic design is beautiful.
  3. Wireless networking just works.
  4. Firewire just works.

So I was off to a running start. But here is where my problems began. At the top of my shit list is CodeWeavers‘ CrossOver Office. What a complete piece of garbage. From all their press releases, I was led to believe that they actually supported some useful Windows programs such as Office and, more recently, iTunes on various flavors of Linux. Don’t believe what you read. It’s all lies. Damn lies.

I started with Office 2003. That just failed utterly and completely. I wasn’t about to go back to Office XP, so I gave up on running M$ Office. FC5 comes with OpenOffice, which claims to support Word, Excel, etc. so I figured I would just use that.

Next I moved to iTunes. First off, installing it is a series of hacks and kludges. Upon following these ridiculous instructions, iTunes actually launched! But:

  1. All my playlists were gone, even though I repeatedly pointed iTunes to my backed up iTunes Music folder.
  2. The best feature in iTunes, search, didn’t work — the search box was grayed out.
  3. A basic feature — scrolling — was inconsistent and buggy.
  4. It crashed about 10 times before I completely gave up on it.

So now I had limited options. I decided that I would give up on purchasing DRM music through the iTunes store (and save about $500/yr in the process) and switch to Banshee, which claimed to be everything that iTunes was minus the music store.

Okay, so music is just music. But what about e-mail? I’m totally addicted to Outlook — the proof is my 1.5+ Gb .pst saved mail file. Without CrossOver Office running Outlook, I had to fall back on Evolution or Thunderbird. Access to saved mail, however, was a showstopper. To use my gi-normous .pst file in a non-M$ program, I needed to convert it to MBOX format. That proved impossible. Or at least not possible within my own personal constraints of time, patience and most importantly, sanity.

First I tried Thunderbird, because I remembered using its Outlook .pst conversion program. After struggling for a long time with compilation issues, linking issues/missing dependencies (including the wrong version of libstdc++) and segfaults, I finally got the ol’ T-bird working on FC5. But to my disbelief, the option to import a .pst was missing. After some Googling, I found out that Mozilla’s hairbrained implementation actually relies on MAPI, so you need to have Outlook installed and configured on the machine with Thunderbird in order to convert from .pst to MBOX.

I tried various other programs, including a useless dungheap called MailNavigator. I also tried hand-compiling a C program called libpst that was supposed to work and didn’t. I was beginning to think that my .pst file had been corrupted, but that was impossible because it was running fine in Outlook.

After all this nonsense, I used my wife’s laptop to download a DOS book disk with fdisk, deleted all my partition info, and now here I am back on Windows XP.

Lessons learned:

  1. Linux is not ready for the desktop, even if you’re a hardcore developer.
  2. Don’t believe anything CodeWeavers say about CrossOver Office. It just doesn’t work. Period.
  3. Windows, for all its faults, is actually not that bad. I can’t believe I just said that, but it’s true. 😉
Categories
Plumtree • BEA AquaLogic Interaction • Oracle WebCenter Interaction

The Plumtree founding fathers — where are they now?

The short answer: one is retired, two are involved in real estate (one as a business and one as a hobby) and one is seeking his next adventure while investing in promising Web 2.0 start-ups.

Joe McVeigh

I heard through the grapevine that Joe has invested part of his personal fortune in Skobee (web site, blog), a Web 2.0 online community where you can organize events, sort of like evite meets MySpace, I guess. Honestly, I still haven’t quite figured out a use for it, but to its credit, it has a nice UI, well-written copy and a very cool intelligent e-mail integration that gromms out keywords from e-mails and uses them to create events.

Joe and his wife Julia recently had a baby girl (Jacqueline). Joe and his family still reside in the San Francisco Bay Area and Joe claims to be seeking out the next big thing. If anyone can find it, he certainly can.

Glenn Kelman

Glenn moved to the Seattle, WA area and recently got married. He’s now the CEO of Redfin, a real estate company that focuses on San Francisco and Seattle. They’ve been getting some pretty good press lately — I’ll be keeping my eye on them for sure. According to his bio on the Redfin site, Glenn also serves on the board of Naviance, a hosted service for schools and colleges.

Kirill Sheynkman

Far and away the most enigmatic of the three founders, Kirill is officially in retirement, although I suspect he’s also looking for something to keep him busy (as people with his IQ probably don’t much enjoy watching the grass grow). He currently lives in NYC and as a hobby, seems to have put together a real estate calculator of sorts called House Math. He also smokes a pack a day, blogs (although somewhat infrequently) and rants about various topics from Bali and Saddam to the financial outlook for MSFT and GOOG.

Categories
Personal

Integrate your iPod with your car — the right way

I normally restrict myself to writing about ALUI (Plumtree) topics, but I just can’t resist sharing my thoughts on a recent purchase I made that has changed my life (no kidding).

Up until Monday of this week, I’ve been a happy iPod user (3G, 20 Gb) who enjoys using his iPod in the car but who has never been completely happy with the available options for iPod automobile integration. I started with Griffin’s iTrip, a little cylindrical module that plugs into the top of an iPod and broadcasts the amplified sound to an FM frequency of your choosing. There are several problems with this approach:

  1. The sound is amplified — it would be better to start with a flat signal.
  2. You have to change frequencies when you travel because of interference from other stations.
  3. It’s incredibly difficult to change broadcast frequencies and there’s no way to tell which frequency you’re on.
  4. You have to operate the iPod while driving, which can be dangerous.
  5. You need to purchase separate accessories (such as a cigarette lighter charger) in order to keep the iPod juiced.
  6. You have to deal with messy cables and other electronica in your car that you need to remove and hide in the trunk when you park and leave the car.

Recently I purchased a better integration kit (also from Griffin) called the Road Trip. This unit addresses several of the problems, but not all of them.

  1. The sound is flat — it connects to the dock rather than the audio out.
  2. You still have to change frequencies when you travel.
  3. It’s super easy to change frequencies (and there are even presets) and there’s an LCD that tells you what frequency you’re on.
  4. You still have to operate the iPod in the car, but at least there’s a nice support structure that holds the iPod in a comfortable position for the driver.
  5. It automatically charges the iPod with the included cigarette lighter adapter.
  6. You still have to have the iPod in the car, although it’s more contained because the charger, FM modulator and holder are all part of the same unit.

So as you can see, I was getting closer to the ultimate iPod/car integration solution, but I still hadn’t arrived at it fully.

Until Monday.

After some extensive searching and several calls to the local BMW dealership, I found a product called the USA Spec iPod Adapter that solves all of my iPod/car integration woes, was easy to install, and well priced at around $130 (including tax and shipping) from Bavarian Autosport.

It works with most BMWs (as long as there is no navigation system installed) and it installs in literally 15 minutes.

To install it, I simply removed my car’s factory-installed, trunk-mounted 6-disc CD changer (which I’ve never used) and pulled out the two cables that power the unit and connect it to my Harmon Kardon audio system. I then attached these two cables to a cable (included with the adaptor) which plugs into the adapter. From there, there’s another cable that connects the adapter to the iPod. The whole unit (adaptor + cables + iPod) is safely concealed in the trunk.

I can now operate the iPod from my audio console, which sees the iPod as a CD changer. Playlists BMW1 through BMW4 are mapped to CDs 1-4, CD 5 plays all tracks on the iPod and CD 6 activates the auxillary RCA input jack into which I could plug satellite radio or any other component. The USA Spec adapter also charges the iPod, but is smart enough to shut off one hour after the car gets turned off to prevent drain on the battery.

So I’ve finally found it — the ultimate iPod/car integration kit. No more FM modulation, great sound, easy installation, easy and safe operation and the iPod is where it belongs: in the trunk!