Wednesday, 10 March 2010

Auto Menu

Automatic Menus

For the old stuff look at AutoMenuOld (I wouldn't bother if I were you).

The Problem

I think I might, at last, have invented something useful (no that isn't the problem). One of the reasons that this web site is created using Emacs-Wiki is that I am much too lazy to write HTML and to keep all the links in order. The problem is that even though Emacs-Wiki is good at maintaining the links within the Wiki it isn't terribly convenient when it comes to external links or links to non-Wiki parts of the site.

Another problem is that I often forget to put the links in in the first place.

Automenu is my attempt at a solution to both problems.

Javascript only solutions

First Solution

My first idea was to do this entirely in Javascript on the client side. It works but if the page is long it means that it takes a long time to load. The very first version was merely intended to create graphical links. It works and is reasonably lightweight, doesn't noticeably increase the size of the page and so on. Take a look at the result. It all works using the Document Object Model of the page and should work with any modern browser (at least it does work in IE6 and Opera7). Examine the source of the page to see how it works.

Development of the first Solution

Replacing words with graphics produces an interesting effect but doesn't actually solve the problem. Conceptually at least, it is simple to extend the IMG tag replacements to create links as well. See text2.htm for a version that turns the IMG tags into links and also allows for links that are not IMG tags.

Second Solution

Graphics are all very well but what I really wanted was a richer navigation system. There are lots of Javascript solutions to create menus in pages but all of those I could find require you to hard code the menu into the page; that's not practical when the source of the page is a Wiki or when you have copied a page that you don't want to edit but do want to link from. The solution is to replace text nodes with DIV elements that contain popup menus: here it is, the first automenu. Unfortunately the behaviour is significantly different in Opera and IE6. Still it does work to some extent.

Third Solution

The problem with all the ideas so far is that a large page will take a long time to process. comenu is an attempt to reduce the apparent time by rewriting the page a piece at a time instead of all at once.

Co-Co Menu is much the same except that the work of replacement is done in a timer event separate from the function that finds the candidate text. This means that the page loads completely before running the replacement process so that the user can at least read it. It works in IE6 and degrades in Opera to show a simple link without a menu. Another feature is that the scripts expect there to be a link defined in the head of the document that points at the root directory of the site.

Not perfect but better than nothing perhaps.

Fourth, Fifth and others in no particular order

onblur demonstrates some event handling that is conceptually similar to that used in the varuous AutoMenu demos though different in implementation.

The series of pages named Popup Menu (Popup Menu, Popup Menu kjw, Popup Menu kjw 2, Popup Menu kjw 3, Popup Menu 4) is another attempt from way back in May 2003; that is, they predate the automatic rewriting versions. They are shown here to illustrate the menu part of the problem rather than the rwwriting part. Some only work in IE6 some work in Opera as well. For comparison and ideas mcrider shows menus that drop down.

Problems with the Client Side Solutions

Browser Compatibility
The current version only works in IE. This is not inherent in the idea, merely lack of attention to the various quirks of the Document Object Model. I did have a version that worked in both but it seems to have disappeared so I leave it as an exercise for the student as they say in all the most irritating text books.
Speed
The most serious problem after browser compatibility is that the client side solution is slow and irritating for the reader.

Mixed AWK and Javascript solutions

I got fed up with trying to optimize the Javascript version and anyway it seems silly to have the client do over and over again what could be done once. I tried to port it all to a Windows Scripting Host version but that failed after I discovered that my WSH installation was broken. I have no idea why and no interest in finding out WSH is an idiotic solution to something that would not be a problem if someone would simply make a Javascript interpreter available that could read and write files. I can't for the life of me see that there is a security problem if the user can switch the ability on and off as is done with REBOL. So I went back to basics: the problem is one of text substitution so why not use a tool designed for the job. The obvious choice for a sometime Unix sysadmin is AWK (don't mention Perl, that is only suitable for masochists).

You can find the AWK and shell scripts that process my web site to add the menus and to upload the site in MenuTools. The process is essentially the same as for the Javascript versions except that it happens before uploading to the host and that there is no Document Object Model so we must parse the HTML ourselves.

Actually the parsing is quite simple if we keep our eyes on the ball. That is we concentrate on what we are trying to achieve and not on any politically correct ways of doing things. Think UNIX not objects.

An HTML file is a list of strings; there are two types of string: tags and text. The two types alternate and the second, text, can be of zero length. With this insight we can carve up the HTML by setting the record separator to a right triangular bracket and then using the split function to split each record on the left triangular bracket. this gives us an array where the first element is text and the second is a tag. Now we use a simple state machine to decide whether or not to substitute some of the words in the text. Essentially the state machine simply keeps track of anchor tags and the body tag so that only when we are in the body and not in an anchor we will substitute in the text. We could refine the script to only exclude text from href anchors if we wanted.

Once we have a text string that is to be processed we simply repeatedly use the gensub function to replace text with popup menus. One point to bear in mind is that we maintain a stack of things to do and the stuff that has been processed goes on to that stack for further substitution until no more can be done. To prevent substitution inside the newly created menus they are surrounded by guard strings that are not legal HTML. These prevent recognition of the item so that it won't be modifed again. They are of course stripped off before the modified text is emitted.

Problems with all solutions

Links
Absolute URLs work fine but relative links are more awkward. Because a link is specified in the same way regardless of the location of the file in which it is inserted this system has the limitation that all files must have the same relationship to the targets. It might be thought that we could simply express the target URLs relative the the root of the web site; the problem is that we have no obvious way of determining the root of the web site. We cannot assume that the root is simply / because many, or most, personal web sites are sub-directories of the host root. For instance the index page of my site on Tele2 is at http://home.c2i.net/kwhitefoot/index.html, the root of the site is at http://home.c2i.net/ but the root of my part of it is at http://home.c2i.net/kwhitefoot. But I can't hardcode the kwhitefoot partly because I cannot guarantee that it will be correct if I copy the web site to another host (or partition it among several hosts, see PartitionWebSite) but also because such a scheme means that I would have to have a corresponding location on my local disk, not terribly practical in Windows where file system links are either not available (Win98) or not easily managed (Win2k, WinXP). This last objection of course only applies when you want the local web to function directly from the disk, if you use a local web server you can arrange for the site it to live in any virtual directory you like for local viewing purposes.

Solutions

Links
The simplest solution I can think of is for each document to know its own position in the web relative to the root of the site. This can be specified by a link element. Just define a new value for the rel attribute. I chose "root". Set the href attribute to the relative path of the root directory. Scripts can then look for this link element and use it to determine the path to a linked document. The best thing would be to use a rev link and give the path from the current document to the root; however, html editors can often maintain link addresses automatically so that when you move a document all the relative links are adjusted so that they point at the original targets, Front Page at least cannot cope with rev links, it still thinks that they are forward links and adjusts them accordingly. So we must use rel links and do the work ourselves in the scripts. So imagine that we have a link in our links array that points at a document in folder x and that we are creating the href anchor in a document in folder y. Then the rel link looks like: The link to the document in x looks like x/doc.html. We can simply concatenate the two addresses to get URL relative to the current document: ../x/doc.html. So the script has to do two things: find the href attribute of a link element having attribute rel="root" and concatenate this with any relative URLs before adding them to the newly created anchors.

How to use the scripts

Javascript version

Create a menudata file

It is simply a list of words or phrases associated with URLs and menu text.

To use it you simply

  • create an array for the phrases and URLs in a Javascript file,
  • add a reference to this file in the head of each document,
  • add a reference to the automenu script,
  • add a call to the show function either as the onload event of the body of the document or in a script element after the body element.

That's it. Now, if your document contains matches for the phrases it will now contain links to the urls. For a demonstration you need look at the various links in the discussion above.

Just view the source and pick up the script files and the popup.css file to see the gory details.

AWK version

See MenuTools for a list of the scripts that I use.

Create a menudata file. This time we are creating a text file to be processed by AWK. Each menu is represented by a regular expression that matches the text to be replaced by the menu and a list of menu items. Each menu item is a URL followed by the text to be displayed for the link. All of these items are separated from each other by a space two at signs and another space. For instance:

# Links to GNU AWK and ordinary AWK
gawk|awk @@ http://www.gnu.org/software/gawk/manual/index.html @@ GAWK Manual @@ http://www.csd.uwo.ca/staff/magi/175/lecture/topics/awk/ @@ AWK Lecture Notes

The makemenu.awk script turns this into:

gawk|awk
<a href = "#" onMouseover = "showmenu(event,  makemenu(['http://www.gnu.org/software/gawk/manual/index.html', 'GAWK Manual', 'http://www.csd.uwo.ca/staff/magi/175/lecture/topics/awk/', 'AWK Lecture Notes']))" onMouseout  = "delayhidemenu()">&</a>

Note that each menu in the input file is on a single line no matter how many entries there are in the menu. This is not a fundamental feature of the system, the makemenu.awk script could be, and I might do it, rewritten to accept one item per line. It would just need a slightly different state machine.

The page you are looking at now was created with the MenuTools scripts. See UnixText for some more information.

No comments:

Post a Comment

Blog Archive

Followers