Skip Navigation

CodeStore Blog

Quick Tip: How to Use Colo(u)r on Visited Links

Back in the old days of HTML and the web it was all very simple -- websites were just plain old text scattered with links to other sites. Browser helped you out by distinguishing those links you'd read from those you hadn't. An unvisited blue link turned a purpley colour once visited. A useful visual tool to let you know you'd "read" a page.

Forward to now and things aren't quite so simple. Websites aren't just collections of links to other places. There's often a whole load of links on any site that are for navigation within the same site.

I was asked recently to change the colour of visited links on a database I'd created for a customer. What I didn't do was dive straight in with the easiest option and add some CSS like this:

a{
    color:gray;
}
 
a:visited{ 
    color:light-gray; 
}

Doing so would have an adverse effect on the links in the site which don't point to static content within. Changing the colour of links only makes sense in the context of a link to a static page. At least I think so. Don't you?

Take the Domino forum on IBM's site for example:

forum-read

This is a great example of a typical Domino website, where the main focus is on user-generated content and the rest of the site is there to help you navigate that content. 

In the image above you can see a good example of visited link colours in action. Notice I've read the third document down in the view. I now know to over-look that one when I'm deciding what to pick to read next. 

There's also an example of visited link colours used badly. Notice the "Next" link (see big red arrow ;o) has changed colour because I've been to the next screen of the view. However the next page is not static content. The change of colour should be used to tell me I've read that page and don't need to again. That's not true though, as the content of the page opened by that link is likely to change at any moment.

The "next link", in my mind, should be considered a functional link and should not change colour. The same goes for the "Forgot your password" link in the same image. The only links that should change colour are those that link to pages which don't change.

How To Change Colour of Static Links Only

The easiest way to do this that I've found is to change the markup of your links. Like so:

<a href="0/unid?OpenDocument" class="markread">This is a link to a document</a>

The CSS then becomes:

a{
    color: gray;
}
 
a.markread:visited{
    color: light-gray;
}

 

Only links with the class "markread" change colour once they are, errrm, read.

If you don't generate your own HTML for each link (and so can't add the class) you could do it an easier way and wrap your embedded View with a DIV element, like this (where the table would be generated by Domino of course):

<div class="view">
    <table>
        <tr>
            <td><a href="view/doc1/">Link to document 1</a></td>
            <td><a href="view/doc2/">Link to document 2</a></td>
        </tr>
    </table>
</div>

 

The CSS you need would then be:

div.view a:visited{
    color:light-gray;
}

 

What do you think? It certainly makes sense to me. While I don't know what the official guideline is on this I don't see it making sense to just mark all visited links in a different colour.

In Summary

Don't used a:visited in CSS for all links. Only use it for links to pages that rarely (if ever) change. Whether the IBM forum is a perfect example I don't know, but I hope you get the point.

Comment Icon 9 Comments Read - Add | Tue 22 Jul 2008 | Open »

My New Laptop - Replacing the Broken One

At the end of last month I mentioned my broken laptop. Well, it's fixed now, in that I have a "new" one. I probably should have just bought it as soon as the old one went capput, as it's been a big old faff in between.

As mentioned in the first post I dropped it off some step ladders and suspected the backlight of the screen to be broken. First thing I did was source one on eBay. There's a "shop" on eBay UK that specialises in CCFLs.

Using the tiniest screwdriver I own I managed to get in to where the CCFL live and it was immediately clear that was the problem:

There's little wonder it broke. It's like a flourescent tube that's 2mm wide. If anything's going to break after falling 6' then this is it.

Using Karen's soldering skills (never thought I'd let Karen and a soldering iron anywhere near my laptop but she assured me she'd picked up the skill in a part-time job at some point!) we managed to install the replacement. On first boot the screen worked, which was a real woop-out-loud "Yes!" moment, followed shortly by a few expletives when the screen went weird and then refused to work again.

At this point I resigned not to try another CCFL but to chance it with a replacement inverter (also sourced off eBay). This didn't work either and so I resigned to leave it at that. There's only so much you can do for a laptop of its age. Sadly I had to let it go.

This is where I took Karen's advice and called our home insurance providers. As we're covered for accidental damage to our belongings it made sense to. In Karen's words "What's the point of being covered if you're never going to claim!?".

What surprised me was that they agreed to pay out. Especially as it's a "business tool" and our policy is a domestic one, which doesn't cover such items, apparently. Not trusting insurance companies I expected them to use any excuse they could to avoid shelling out.

A couple of days after the "field agent" had visited to assess the laptop I had a call from the company they use to replace IT equipment and they offered me some crappy HP laptop worth less than half what I paid for mine, which it's listed on our like-for-like policy as being worth. Apparently they "can't source Lenovos".

While I considered what to do with their offer I carried on looking on eBay and managed to find a T42 with the same screen (15" SXGA for 1400*1050 res) which are farely hard to come by. It was in mint condition and up for £250. It arrived the next day. Despite it being a 2373-N1U and my old one a 2373-F7G I managed to swap the hard drives over and it started perfectly first time. That alone saved me a day's time in migrating to and configuring a new laptop. It also meant my current port replicator still worked!

After swapping over the DVD bay and the spare RAM module I'd added to my old one I had, in effect, a brand-new laptop. From what I can tell it's never been used (still got the protective bits on top of the glass LED display). I'm a happy bunny once more. It's got an American keyboard but I can learn to live with that. What I've got now is my old laptop back but in sparkling condition.

All that I need to do now is make up my mind about the insurance offer. Do I cancel the claim and protect my "clean slate" of never having claimed, thus reducing my premium (I presume?) or do I call back and haggle? It is after all a like-for-like policy and what I really want is a Lenovo T61. Maybe I just accept the HP they're offering and let Quinn and Karen use it. Maybe I accept and sell it on eBay?

Where I'll have trouble is in arguing against what they've offered me, as it is the same spec on paper. I just don't want a HP. It leaves me wondering why Thinkpads are so pricey. How can I argue I need a laptop that costs more than twice the one they're offering me when they have the same spec?

Comment Icon 10 Comments Read - Add | Mon 21 Jul 2008 | Open »

More On Running Both LotusScript and Java WQS Agents Simultaneously

In response to yesterday's ponderings it was quickly pointed out that I could easily run two agents in a Form's WQS event by simply calling one from within the other. Imagine this code in the first agent:

Dim agentTwo As NotesAgent
Set agentTwo = database.GetAgent("(wqs Agent Two)")

agentTwo.Run( document.NoteID )

All seems fairly obvious and I don't know why I didn't think to try it in the first place.

However, there's a problem with this approach. Running the second agent against the document in which the first WQS agent was triggered only works if you save the document before running Agent Two and then again before leaving Agent Two.

The first save within Agent One is needed for two reasons.

  1. A new document doesn't have a NoteID, which you need to pass to the other agent, until it's been saved.
  2. Any changes you make to the document in the code in Agent One won't be reflected in the document you see in Agent Two. This is because Agent Two is getting a handle on the document that has been committed to the disk and not the context document, which Agent One is working on.

The second save is called at the end of Agent Two and is needed because we're operating outside the context of the document being saved. We won't be able to access the altered values in Agent One after Agent Two has run as we're effectively working on different instances of the same document. At least that's how I understand it. Although in practice I found that changes made in Agent Two don't commit anyway as they're over-written by the impending save of the document from the completion of Agent One, which is the controlling WQS agent.

All in all, while testing this, I found it doesn't really pan out like you'd hope it would and it's not an approach I'd recommend or like to use myself. The only time I'd use it is if the code in Agent Two was merely reading the document and not making any changes. Then there's the matter of calling doc.save() from within a WQS agent, which I never like to do. It just seems wrong.

In summary, if you want to run two agents in the WQS event -- one LotusScript, one Java -- then I'd still recommend running them one after the other, as shown yesterday and avoid having to save a document which is already being saved. If you want to run Java that merely needs read access to the document then it might work for you.

If you're interested in seeing how I came to the above conclusions here's the database I was messing about with. Put it on a server and watch the console while saving documents. You should get an idea of what's going on.

I'm still leaning toward re-writing the agent in Java though...

Comment Icon 6 Comments Read - Add | Fri 18 Jul 2008 | Open »

Running Two WQS Agents, Where One is Conditional on the Other

A thought occurred to me yesterday. We all know you can run multiple WQS agents and that you can make them conditional using @If statements, right? What I didn't know was whether you could have two agents run but only have the second agent run depending on the outcome of the first. Imagine the code below:

ScreenShot001

 

In the example shown, does the server decide which agents to run and then just run them or does it run the first and then enter the @If to decide whether to run the second? That's what I didn't know. Well, it turns out it's the latter case. Agent One runs and sets a field on the document called "runSecondAgent" to a value of 1. This trigger Agent Two. If in Agent One you stop the code from setting that field value then Agent Two doesn't run. Just what I was hoping would happen.

Something I found out from testing this out is that you can't "print" to the browser from consecutive agents. Anything you print from the first is lost if a second agent runs. Even if that second agent doesn't print anything.

How did I end up wondering this in the first place?

It came about because I wanted to use the iText PDF library to create and send out an invoice once a document was saved. The Form already has a WSQ agent written in LotusScript. To generate the PDF I need to run some Java. Therein lied the problem. Depending on certain logic in the main LotusScript WQS agent I need to run some Java. The concept above solves this.

It's a bit of a messy solution though. Makes me wonder if there's isn't another way?

An Alternative, Which I'd Tried First, But Which Failed

Before I got to wondering about the conditional agents I had tried using LS2J. Ever heard of it? It allows you to call JavaScript code from within LotusScript. I've used it successfully in the past to resize images from LotusScript using Julian's code. It's all very clever, but in this case doesn't seem much use as I can't find a way to pass a handle to the document context in to the Java class used.

I did all the usual LS2J stuff to create an object of the class called PDFInvoice, the code for which lived in a Java Code Library in the database and looked something like this:

   1: import lotus.domino.*;
   2:  
   3: public class PDFInvoice extends AgentBase{
   4:     Database db;
   5:     
   6:     public PDFInvoice (String docUnid) throws NotesException    {    
   7:         try { 
   8:             Session s = getSession();
   9:             //Can't get past the above line        
  10:         } catch ( Exception e ){
  11:             e.printStackTrace();
  12:         }
  13:     }
  14: }

It fails on line 8 though when you try and create a session object. Any reference to "s" after that returns a "null pointer".

Is there no way in LS2J to have the Java that's called operate on the current document (or any Notes objects for that matter)? As I understand it you can only pass primitive data-types as arguments. However, I was hoping to pass the document's ID over and somehow get to it that way. If I can't open a session though, how would that work?

I'm starting to wonder how good an idea it would be to just write all Agents in Java from now on. Seems to me there's little LotusScript can do that Java can't. Whereas the opposite is not true at all.

Comment Icon 7 Comments Read - Add | Thu 17 Jul 2008 | Open »

MetaWeblog.newMediaObject and Word 2007 -- BlogId as Integer and URL as String

This is one of those posts that will be of little interest or use to my regular readers but which I felt compelled to write in order to try and save future Googlers the pain I've suffered these last few days.

It's no coincidence that on Monday I was talking about changes to the way I post blogs and that I've been asked by a customer to enable posting to their Notes-based blogging system from Word 2007. Being paid to write code you've been wanting to do in your spare time is brilliant when it happens, which is all too rarely.

The system is one I've talked about in the past and was developed to allow students to have a private blogging area (case study) within their university. As all of the students on campus are about to be upgraded to Word 2007 it's seems logical to support blogging directly from what most seem use as their preferred "text editor". Heck, it's better than them copy/pasting from Word to TinyMCE. What a mess that makes!

In the same way Windows Live Writer (WLW) works Word 2007 also uses the Metaweblog API (or any other for that matter) to post to blogs. If you've written the code to support posting from WLW then you should be able to support Word too. That's the theory at least. You'd think that the fact there's a spec for both the XML-RPC and the Metaweblog API would mean the Word developers would know how to go about using them. This is Microsoft though and that would be too easy. Instead, in a way only Microsoft can, they've re-invented the specs. Well, at least they've refused to follow them to the letter.

 

Problem 1 -- The First Paramater Passed to the newMediaObject Method is Meant to be a String.

If you've written your own Java code to handle uploading files, it would look something like this.

   1: public Hashtable newMediaObject(String blogid,String username,String password,Hashtable content){

The problem is that Word 2007 passes the blogid parameter as an integer. That's the first of two WTFs. To get round this one you have to write an over-loaded method just to handle calls from Word, like so:

   1: public Hashtable newMediaObject(int blogid, String username, String password, Hashtable content)
   2:         throws Exception {
   3:             return newMediaObject(Integer.toString(blogid), username, password, content);
   4: }

One problem solved. One to go.

Problem 2 -- Word Expects the newMediaObject to return the URL as a String

When XML-RPC data is returned to the calling client the XML it sends tells you what type of data it contains by wrapping it in certain tags. The XML is something like:

   1: <struct>
   2:     <member>
   3:         <name>upperBound</name>
   4:         <value>
   5:             <int>139</int>
   6:         </value>
   7:     </member>
   8:     <member>
   9:        <name>empty</name>
  10:        <value>
  11:            <boolean>false</boolean>
  12:        </value>
  13:     </member>
  14:     <member>
  15:         <name>Title</name>
  16:         <value>
  17:             <string>This is a simple test</string>
  18:         </value>
  19:     </member>
  20: </struct>

 

Although the Title in this example (line 17) is returned explicitly as a string it doesn't need to be. The XML-RPC spec says that any value not wrapped in a tag that tells you what type it is can be assumed to be a string.

With most blogging clients you can return basic strings without wrapping them in a <string> tag. Apart from when you return the URL to Word 2007 following a newMediaObject call. The URL must be wrapped in a <string> tag! If not you'll just get a non-explicit error from Word saying your blog service doesn't support uploading of images, which isn't of course true. It's that Word doesn't follow the spec.

To get round this I had to hack my Java inspect the XML it was about to return and make sure any URL values sent back were set as strings, like so:

   1: xml.replaceAll(
   2:         "<name>url</name><value>(\\S*)</value></member>", 
   3:         "<name>url</name><value><string>$1</string></value></member>"
   4:     );

Problem 2 solved.

Problem 3 - Character Encoding.

I've yet to confirm or work out exactly what's happening but it looks like the title of a post from Word 2007 uses a character set different to the body. More on that once I've worked it out.

In summary, as you might expect, writing code to work with Word 2007 is a major pain in the back-side. Give my Windows Live Writer any day!

I leave you with a photo of Felix uploaded via a newMediaObject call from WLW and formatted using the brilliant Polaroid picture plugin.

Comment Icon No Comments Read - Add | Wed 16 Jul 2008 | Open »

 icon Page 1 of 226Last » | Next ›