Monday, February 26, 2007

Completing the Tree

It seems like things are always easier to do when you've had a good night's rest. This morning I was able to finish adding AJAX to the message tree, with fallback and anchors, in about an hour.

Wicket provides a semi-magical component called AjaxFallbackLink that transparently allows your application to do AJAX if it is capable and falls back to a normal link if it is not. In typical Wicket fashion, the intrusiveness of AJAX is minimal, with much of the magic being handled behind the scenes. The one thing you have to watch out for is the case where the AjaxRequestTarget passed to onClick is null (which is what happens when you fell back).

Then the general paradigm is this:


public void onClick(AjaxRequestTarget target) {
doStuff();
if (target != null) {
doAjaxStuffToo();
}
}


The doAjaxStuffToo would typically involve doing some kind of replace operation in your Wicket page structure and then adding the modified part of the page structure to the target. In the event that AJAX isn't available, you don't have to worry about this, because the entire page is going to be re-rendered anyway.

Thanks to Igor Vaynberg for his help this morning in figuring that out.

Now one might naturally say, "but what about when AJAX isn't available? Won't the page refresh and put me way back up at the top of the page? That will be annoying!" Ordinarily it would, but Wicket makes this easy to deal with. You simply anchor your link to the Component you want (and so that Component needs to have setOutputMarkupId(true) called on it). It's as easy as calling setAnchor(parent) on your AjaxFallbackLink (or other type of Link).

Unfortunately, the setAnchor method defined by Link has the return type of void, so you can't be super lazy and define your Link and call setAnchor on it inside an add method on another component. This is easily worked around by just assigning your Link to something, calling setAnchor and then adding it. I have added WICKET-238 as a wishlist item for Wicket so that this operation is congruent with so many other Wicket operations that are "chainable".

Here's what a final bit of code looks like for handling navigation links. For the full source, look to the repository on tally-ho.dev.java.net.


private void addNavLinks(final TreeNode node, Fragment nodeFragment) {

if (!node.hasChildren()) {
nodeFragment.add(new WebMarkupContainer("nav").setVisible(false));
return;
}

Fragment navFragment = new Fragment("nav", "navigation");

if (node.hasOpenKids()) {
AjaxFallbackLink closeLink = new AjaxFallbackLink("closeLink") {

private static final long serialVersionUID = 1L;

public void onClick(AjaxRequestTarget target) {
node.closeKids();
if (target != null) {
parent.replace(new Entry("entry", root.getChildren(), parent));
target.addComponent(parent);
}
}

};
closeLink.setAnchor(parent);
navFragment.add(closeLink);

navFragment.add(new WebMarkupContainer("openLink").setVisible(false));
navFragment.add(new WebMarkupContainer("openAllLink").setVisible(false));

}

Labels:


Sunday, February 25, 2007

The Message Tree Lives

I decided after a bit of a struggle that it probably would be faster and easier just to go ahead and migrate the message board system for morons.org to use Wicket than it would be to continue to fight with trying to integrate two basically incompatible systems.

I like to deal with the most difficult problems first, which in this case meant the message viewing system. The morons.org message board has received much praise from users over the years for being fairly intuitive and easy to deal with. It has been evolving for many years based on reader feedback and on my needs and desires at the time. (It started its life as an unthreaded system where the messages where stored in flat text files!)

Wicket makes it fairly easy to get the recursive display part going, and my first step was to just display a full tree with no navigation. This I accomplished using Panels with ListViews. The ListView was used to display a set of Panels, and each Panel contained a span element where a nested item could be placed. Naturally this nested item can be another Panel of the same type, so you can build this huge recursive structure quite easily.

It was while doing this that I learned a very basic trick that comes in quite handy. In a previous entry I talked about how one can use Fragments to display one glob of HTML vs another depending on conditions in the program. If all you need to do is make some bit of HTML disappear, there's an even easier way. Just add a Label for that wicket:id, and call setVisible(false) on it. This is what I do to my nested item from above if there are no children to show. (Otherwise Wicket would surely complain that I had an element in my HTML that had no corresponding Component in my Java structure).

If you need to make an entire section of HTML disappear (ie, multiple tags/Components) it's still better to use a Fragment so you avoid calling setVisible many times as opposed to once.

Another thing I noticed is that I'm starting to see a relationship between nice looking code and relationships with the HTML components. I don't have enough information to really quantify this yet, but I'll give an example. I observed tonight that it was really nice to have one method that corresponded to one wicket:fragment. Rather than include a bunch of code inside my populateItem method, I could just say addNavLinks(node, nodeFragment); to deal with the corresponding navigation fragment. I probably could (and maybe should) do the same thing with creating the nodeFragment itself (it's a different bit of HTML based on whether the node is expanded).

Lastly on the topic of Fragments, note that you don't nest them in your markup. Even if you're only going to use a wicket:fragment from inside another fragment, the markup goes at the top level of the HTML document.

I got a good tip tonight in ##wicket on freenode from JonathanLocke: when you start getting into complicated containment with recursive structures, get a sheet of paper and draw out what's happening. As I learned in the last few days, it can get very complicated to mentally conceptualize what the hell is going on. I wish he and I had had that conversation before I got all the way through it :) but it is good advice nonetheless.

Another handy Wicket trick is to use setRenderBodyOnly. This comes in handy when you need to use some tag in your HTML as a placeholder for something else, but you don't really want the tag itself to show up in the final product. (Do you have tons of useless span elements all over the place?) You can't do this if you're using AJAX on the Component/tag in question however (for mostly-obvious reasons; the Javascript would not be able to find your tag if it wasn't rendered to the output, and so it would be impossible to act on it.)

Next I got the basic navigation working. This was easy to do using Wicket's Link class. It's really handy to be able to say things like this:

navFragment.add(new Link("openAllLink") {
private static final long serialVersionUID = 1L;
public void onClick() {
node.openAllKids();
}
});

... and have it just work. In my TreeNode structure, I have a method for recursively opening all of the children of the TreeNode, and in my ListView implementation, my populateItem method takes care of doing the right thing based on whether a TreeNode claims to be open, have children, etc. It all fits together so perfectly!

One thing that I did have to struggle with for a bit is the unfortunate fact that you can't really mix a ListView with a Fragment. In the case of the morons.org comments system, when a thread isn't expanded in the view, it has a different style than when it is. It would have been really nice to have been able to conditionally use a Fragment from within populateItem. The simple workaround for this is to wrap the contents in a div element, which turns out to be needed for the AJAX stuff anyway.

Speaking of AJAX, that was the next step. I just wanted to make something really simple happen using AJAX at first, so I figured I'd just make the whole subsection of the tree get replaced with a Label. This seems like it should be easy to do, but in actuality, it is difficult to replace something on the page with something that is not on the page. This is because Wicket waits to assign the markup IDs until page rendering time. The AJAX code will complain that the markup ID is empty on the new element, because the markup ID will not be assigned until later. This means you can't take something like a ListView and replace it with a Label

What you can do is have Wicket replace the contents of something that is contained on the page. In other words, you can get the Panel containing your ListView to replace it with a new ListView. The difference is subtle, but it's a question of AJAX replacing what exists inside the thing it is acting on versus replacing the thing it is acting on itself. The actual code looks a little bit like this:

navFragment.add(new AjaxLink("ajaxLink") {

public void onClick(AjaxRequestTarget target) {
node.openAllKids();
//myTree.replaceWith(new Entry("entry", root.getChildren()));
parent.replace(new Entry("entry", root.getChildren(), parent));
target.addComponent(parent);
}

});

Note that one uses parent.replace (where parent is the Panel), not someListView.replaceWith(someNewListView). The latter will appear to work once, but the second time it won't work anymore, because it has effectively been orphaned by not getting reattached to its parent container properly. The second time the link is used, Wicket would return an error about applying the method to an entity that hadn't been added to the parent in the latter case.

There's still a bit of work to be done, especially in tidying up the code and moving it from an experiment to playing an actual role on a proper article page, but much progress has been made.

In other news, java.net has finally approved our new home. I can't really say anything more about Google Code than Hani Suleiman hasn't already said. I briefly looked at Codehaus and decided that it's at least as retarded as Google Code, except that you can actually use Google Code; you can't use Codehaus until you prove that you're 31337 enough for them. You can't request a project with them without a login, and you can't have a login. I defy anyone to find somewhere on their site that one can create an account. Moreover, they seem to only want projects that are already complete. What the hell is the point of setting up a project and using a repository, etc., for projects that are already done? Go back to IRC, you boar-arse-licking nitwits. I'm too old for that shit...

...but I digress. Our new home is https://tally-ho.dev.java.net/

That's all for tonight. More to come.

Labels:


Friday, February 23, 2007

billy mumy's autograph

To Nick: stay out of the cornfield.


Tuesday, February 13, 2007

bigass waves


also baker beach


baker beach


This page is powered by Blogger. Isn't yours?

Subscribe to Posts [Atom]