Monday, December 29, 2008

2008 - My blogging year in review

Statistics


23,629 visits (85% increase from 2007)
37,498 pageviews (70% increase from 2007)

44.81% come to my site from a search engine
33.83% are referred traffic (Thanks Planetlotus and Codestore)
21.36% are direct traffic

Top five visiting countries: USA, United Kingdom, Germany, Australia and my home country, Norway.

Top five browser profiles: 53.58% Firefox, 40.38% Internet Exploder, 2.84% Opera, 1.39% Safari, and 1.34% Chrome (impressive, considering it's time on the market).

Top 5 content of 2008

  1. Cross client (Web/Notes) validation using JavaScript

  2. Slick integration between FCKeditor and Lotus Domino

  3. Using embedded view in $$ViewTemplate to control count

  4. Embed multiple standalone forms on a domino form using DHTML

  5. String to char-array in Java

Comment on Content


I would think the most useful thing I blogged about in 2008 would be the "slick" integration between FCKEditor and Lotus Domino. Although FCKEditor is a bloated beast compared to some of the other Rich Text editors out there, it is still a great editor.

I was probably most excited about my speedy templating engine. I still haven't released a demoapp due to laziness, and a couple more reasons.

  1. I have a feeling that the templating engine is a solution to a niche problem. I use it as a way to go beyond some of the restrictions of Views in Domino (being aware of the index of the document you're rendering, what level it is on, etc, and rendering different output based on that information).

  2. Templating engines are becoming less important/need to be rewritten when Domino 8.5 is released, and we have XPages.

  3. There really hasn't been much response to my templating blogposts. It could be because I haven't been able to communicate what benefits there are in templating, or that the problems I've had with Domino/Views aren't common problems.



Missing content


I've still to publish a demoapp of the improved templating engine due to above mentioned reasons. If I get really motivated/someone motivates me, I may release a demoapp in 2009.

The beautiful URLs experiment got dropped once I found out that the technique generates a lot of errors in the domlog. Maybe XPages will make the URLs more flexible (it would be great to be able to map URLs per application/in the NSF).

2009


If everything goes as planned, I'll be at Lotusphere 2009. The hotel is booked, and flight/conference tickets are paid for. I'll depart from Norway January 17. and return January 23. Hopefully I'll arrive in time for BALD!

Blogging-wise, I'm becoming less and less motivated to do experiments. This is mostly because I can't come up with new, exciting (to me) problems to solve. With XPages and Lotusphere 2009, this will hopefully change.

Thursday, December 4, 2008

(Click to) Sort view after search



Expand "More", to the right of the search-field. Select "keep current order", then click Search.

Wednesday, December 3, 2008

Simple way to ensure scheduled agents run on the correct server

This tip is probably only valid for agents that run by document selection.

We recently added clustering to one of our environments. This resulted in some replication conflicts/problems due to agents modifying (new and modified) documents on two servers.

The simplest solution to the problem I could come up with was to add a computed field, originating_server, with value=@ServerName.

Then in the agent:
'Get unprocessed-collection..
Set doc = col.getFirstDocument()
While Not doc Is Nothing
If Join( Evaluate( |(originating_server = @ServerName)|, doc ) ) = 0 Then
'Document modified on another server
Else
'Do processing
End If
Call session.updateProcessedDoc( doc )
Set doc = col.getNextDocument( doc )
Wend

Update: To avoid leading people on a harder track than need be. If your app is located on a single server/single cluster, use Run on -> [Your server] in the Agent Schedule settings.

Or, as DiDu suggests, check if the agent is running on the Administration Server/stop it if not. Set the Administration Server in the ACL-dialogue, under Advanced.

The apps I had problems with are all from the same template, running in two distinct environments (separate NAB/Notes domain).

Friday, November 28, 2008

@GetField accepts text-list as input

Just out of curiosity, I tried using a text-list as input for @GetField. It works.
When a text-list is the input, a text-list of the field values is the output.

fields := "subject" : "com_unique" : "relation";
@Implode( @GetField( fields ) ; "|" )

The above code gives me a pipe-separated string of the values in the subject, com_unique and relation-field.

Not the most useful tip I've blogged about, but probably nice to know.. :)

Friday, November 21, 2008

Avoid save-conflicts in web applications

I had a problem with save-conflicts today.

In an app I work with, the users can modify the category a document belongs to from the document-editing interface. When the category is saved, all documents that belong to the category are updated with new paths.

Sometimes, when a user saved the document after modifying the category, a save-conflict would occur (I couldn't recreate the save conflict in Firefox for some reason, only IE).

I searched the forums for help, and found it (thanks, Erik C. Brooks). If you remove the %%ModDate front-end with Javascript, you avoid save-conflicts. I tested it, and it seems to work. To be somewhat on the safe side, I only remove the field when a category is being modified from the document-editing interface.

Simple code to remove the field:
var f = document.forms[0];
f.removeChild( f['%%ModDate'] );

Beware of NotesView.getColumnValues

It's probably undocumented for a reason (not complete?).

If you use this in a webapp, and run as web user, it ignores readers-fields, and returns all column values. This is similar to the LS Evaluate-behavior when you do @DbLookup/@DbColumn.

Also.. If you do NotesView.FTSearch, getColumnValues returns all values, instead of only the ft-search filtered values.

Simple function that does the same as NotesView.getColumnValues, safer, but a little slower:

Function columnValues( view As NotesView, Byval columnIndex As Integer ) As Variant
On Error Goto bubbleError

'Create empty dynamic array
columnValues = Split( "" )

Dim entryCol As NotesViewEntryCollection, entry As NotesViewEntry
Set entryCol = view.AllEntries
Set entry = entryCol.GetFirstEntry
While Not entry Is Nothing
If entry.IsValid Then
columnValues = Arrayappend( columnValues, entry.ColumnValues( columnIndex ) )
End If

Set entry = entryCol.GetNextEntry( entry )
Wend

columnValues = Fulltrim( columnValues )

Exit Function
bubbleError:
Error Err, Err
End Function

Friday, November 14, 2008

Firefox plugin for testing XSL - XSL Results

I don't work much with XML transformations. Therefore I don't have any commercial XML tools available at my place of work.

I'm currently working with XML->DXL transformations. I scoured the web for some XSL tools. I found this nifty plugin for Firefox. It's fast, and works great. Highly recommended.

Tuesday, November 11, 2008

Added translation tool to my blog

Inspired by the code poet, I added a google-translate tool at the top right side of my blog.

Hopefully, this will let more people find a solution to their problem.

Regarding the templating-class/-demoapp.. I've implemented it in a couple of projects at work. It works great.. I'll post the demo-app when someone (the tooth ferry fairy?) has documented the code.

Monday, October 27, 2008

Domino Developer Plugin for Aptana Studio - Version 0.6.0 Now Available

Just got a mail about a new version of the plugin.

This update features:
* Full compatibility with Aptana Studio 1.2
* Support for path/folder information in design element names
* Support for local databases
* Support for drag & drop copying to and from the file system
* Fixed last byte truncation bug affecting certain file resource lengths
* Improved performance retrieving design elements from large databases

For a complete list of changes in this release:
http://code.google.com/p/domino-aptana/issues/list?can=1&q=milestone:Release0.6

To install or upgrade to this version, point your Eclipse/Aptanaupdate manager to:
http://www.jeffgilfelt.com/eclipseplugins/

For further information and complete installation instructions:
http://www.jeffgilfelt.com/DominoAptana/

To participate in the project or to access the source code:
http://code.google.com/p/domino-aptana/

I know I'm downloading it. If you work with CSS and/or Javascript-files for Notes/Domino applications, I recommend that you try it out.

Tuesday, October 21, 2008

Lotus Domino Designer Wiki - XPages

If you're not already subscribing to the RSS-feed, you should. Plenty of new content is being written at the moment.

The XPages-part

Friday, October 17, 2008

The new templating engine - Preliminary testing

I decided to do some testing of the engine (currently in alpha-stage). At work, I mainly work on content management.

One of our databases has ~5000 (5321) documents (content and categories). They reside in a CMS-type of application.

The current render-engine (using LS/Forall) in the CMS can't deal with that amount of data, when printing the entire structure. After 55 seconds, the rendering stops with no output. Of course, the old engine isn't perfect, and it wasn't built to deal with that amount of data.

When rendering the same markup, with the engine I'm currently working on, it renders in 2.3 seconds, with maximum data being processed in Evaluate (the more data formula can process in a run, the faster it runs). The resulting HTML weighs in at 704KB.

Both tests were done on my 2GHz/2GB RAM laptop.

YAY!

I thought it would be fast, but this is way better than I expected..

If IBM upped the amount of data formula/evaluate could handle, the engine could probably render the structure in just above one second.

Update: Did a little testing in N8.5/XPages (same markup as in the above tests). As other bloggers has mentioned, XPages are fast. What I really don't like about the current version in Beta2 is that you can't control if Dojo is loaded with the page. As far as I know, there is no way to make a regular webpage without client-side JS (Dojo). I hope they fix that in the final version.

Hopefully they implement some kind of dependency-system. It loads what it needs for the widgets you use, then you can decide on the rest yourself (using checkboxes/etc).

Wednesday, October 15, 2008

High performance readable conditional templates

My search for the perfect templating solution for webapps seems to be neverending.

In my latest concoction, I feel I've got a decent mix between readability, speed and flexibility.

Readability


It started out with my feeling that using "readable lookup columns" was a bit to verbose/data-heavy for templating.

I like pipe-separated column values, but on their own, in a template, they can be hard to read.

E.g.
template = "<tr><td>$1</td><td>$2</td></tr>"


What I want to write is something like this:
template = "<tr><td>$name</td><td>$address</td></tr>"


In the demoapp I'm currently working on, I pick the field-labels (e.g. $name) from the first line in the lookup-column (fields := ...) using the Formula-property of NotesColumn.

fields := "firstName" : "lastName" : "company" : "address";
@Implode( @Transform( fields ; "fieldname" ;
@Text( @GetField( fieldname ) ) ) ; "|" )


This way, I don't have to maintain documentation of the indexes of the different values, nor the position of the values.

Performance


To get the most performance out of the rendering-engine, one more or less has to use evaluated formula-code. Iterating over an array of column-values using Forall/Replace is slower. It's more and more noticable the more documents/values one is putting through the rendering engine. If it's slow with one user, it's going to get a lot slower with hundred users/etc.

If all you were rendering were static content, and have a decent caching-engine, performance per render would be less of a problem.

My current solution is to join the array of column-values to a formula-interpretable-list. Due to Evaluate's limit on length of returned data, I have to process the data in snippets.

E.g. LS-array:
array = ["Party","Hard"] '(brackets to indicate an array)
formulaListString = |"| + Join( array, |":"| ) + |"|
'result: "Party":"Hard"


All the templates are "compiled" to @ReplaceSubstring. You can see how I compile/process the templates and data when the demoapp is complete.

Flexibility


Another downside to using pure LS-processing of templates is that it's hard to do conditional templating without slowing down the rendering-engine/writing a lot of code.

Since all data are being processed by Evaluate/Formula, it's quite easy to add conditional compiling as well (@If condition -> conditionalTemplate ). If you want, you can use one template for all the data, or you can have multiple conditions, and a default template.

Example-code/results


Example-code from an agent printing a table from 499 documents

Result - odd/even-template

Result - mixed conditional template

At the bottom of the above HTML-files, you can see the amount of values/rendering-time. The time includes all processing, getting field-indexes, columnvalues, rendering, etc. The processing is done on localhost, on my laptop (Core 2 Duo 2GHz/2GB RAM) in Windows XP.

I'm not done yet with the implementation. The current engine is comprised of two classes. One that renders arrays of pipe-separated values (could also process CSV-files), and a subclass that processes the columnvalues of a view.

I plan to add another subclass to the first. This would let you use ft-search on a view, and sort the resulting columnvalues on a field/value in the resulting columnvalues.

If you have further suggestions/wishes/are curious about the code, contact me. Preferably throught comments. :)

I can't say when the demoapp is complete. I'll probably have another update or two before the app is ready for the masses.

Friday, October 10, 2008

Domino Developer Plugin for Aptana Studio

In my previous post, I mentioned that I'd like to see Aptana in DDE. Well.. It seems that we're not far away. Nick commented that there is a project named Domino Developer Plugin for Aptana Studio. Aptana is quantum leaps better the integrated JS-editor in Domino. You get code complete for Js, DOM and JS-frameworks, etc.

I've only tested it for fifteen minutes or so, but so far it seems excellent. One of my applications is more or less pure JS/CSS. Not having to deal with Domino Designer (Open/Refresh) is excellent. When you save, the change goes directly to the NSF.

I had a little trouble with it at first. I added the Notes path to the PATH environment variable. But I got an error message whatever I did.

I restarted the computer, and now it works fine. If you get this error message, "java.lang.UnsatisfiedLinkError: no nlsxbe in java.library.path" after setting PATH, then you probably need to restart your computer.

Highly recommended!
(Unless there are some horrid bugs I'm not aware of)

Update: It seems Jeff Gilfelt, the author of the "Domino Developer Plugin for Aptana Studio" might be getting some competition from an IBM employee, Niklas Hedloff. He's working on a plugin that goes one step further than Jeff's. It allows you to import Javascript frameworks to NSF's directly from within Aptana, using drag'n drop. Read more about it in his article, "Lotus Notes plugin for Aptana Studio".

The future for Domino web developers looks promising, even if your company isn't of the first to adopt Notes/Domino 8.5 and XPages. :)

Update 2: I've found one re-creatable bug in the current version. When saving a file-resource, the last character is cut. I've reported the error to Jeff. Hopefully he can get a fix out in the nearest future.

Thursday, October 9, 2008

ArrayAppend accepts single values

From the documentation, it looks like you need two arrays to use ArrayAppend. In Notes 8.02, at least, you can also use this function to add single values at the end of a dynamic array (arrays created using Split, NotesItemValues, etc).

Dim i As Integer, numberlist As Variant
numberList = Split( 1 )
For i = 2 To 20
numberList = Arrayappend( numberlist, i )
Next
Print Join( numberList, ", " )
'Prints 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20

It seems to work in the same way as Array.push in Javascript for adding single values to an array, and Array.concat for combining two arrays.

Update: To do the equivalent of Array.unshift in Javascript (add a value to the start of an array), it seems you need to split the value (the first parameter has to be an array).

Dim arr As Variant, i As Integer
arr = Split( 20 )
i = 20
While i > 0
i = i - 1
arr = Arrayappend( Split( i ), arr )
Wend

Print Join( arr, ", " )
'Prints 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20



I would think there would be a performance hit when dealing with a lot of values, but at least it's possible.

If IBM would just give us the Eclipse Java editor inside DDE, I could finally stop dealing with the crappy array-implementation in LS.

What would be even better was if IBM teamed up with Aptana for editing server-side Javascript agents/script libraries.. Throw in E4X for dealing with XML.. One can always dream.. :D

Thursday, September 25, 2008

The HttpRequest class updated - Get cookie values

I've updated the HTTPRequest-class so that you also can use it to pick cookies of a request.

Example code:

Dim request As New HttpRequest()
Dim sessionCookie As String
sessionCookie = request.cookie( "DomAuthSessId" )
Print "I've got your cookie!"


Although I link to Jake's excellent demonstration of how simple it is to get the session cookie, I wrote the cookie-bit a couple of days before, in work relation.

>> HttpRequest Source code

Tuesday, September 23, 2008

Templating: Generating HTML from document collections

Sometimes it can be useful to render HTML from document collections. I believe one of the fastest ways to get values from Notes documents is using Evaluate. As a lazy person, I like to avoid writing formula code, as it's somewhat verbose compared to modern templating languages like PHP.

To avoid this, make your own template interpreter using simple formula code.

Example:
str := "Date: $#dayOfMonth#$. $?month?$.";
'"' + @ReplaceSubstring( str ;
"$?" : "?$" : "$#" : "#$" ;
'" + ' : ' + "' : '" + @Text( ' : ') + "'
) + '"'


Result: "Date: " + @Text( dayOfMonth) + ". " + month + "."

The result can be used in an evaluate statement.

A great thing about templating is that it is very maintainable/reusable. If you want to change the output, all you have to change is the template-string. To generate XML and HTML from the same collection, all you have to do is to have two different string templates. The conversion logic can be the same.

In the simple demoapp I've included, a collection of 300 documents is processed, extracting five fields. A HTML table row is generated per document. class="even"/class="odd" is added so that one can style odd/even rows. If you're somewhat familiar with LotusScript, it should not be hard to make a template-class to fit your needs.

On localhost (my laptop) it takes about 100 milliseconds to generate the HTML/write it to a PassTruHTML NotesRichTextItem.

>> Demoapp

If you want a sorted collection, you can use NotesView.AllEntries, and use NotesViewEntry.Document, to get to the document. Beware of categorized views. Test NotesViewEntry.isValid = True to be certain that the entry represents a NotesDocument. There may be other problems with using NotesViewEntry.Document as well. Proceed with caution/good error handling. :)

?OpenField - get HTML from a RichText-item

In case you missed it in Yellowcast, episode five. There's an undocumented feature in Lotus Domino that lets you pull HTML from a RichText field. This is a very useful feature for me at least.

Example url: http://www.notes.net/secrets/undocumented.nsf/documentation/easteregg?OpenField

I believe Carl Tyler was one of the first to blog about this.

Why not document such a great feature? Why, IBM?!

Friday, September 19, 2008

Processing documents by category in categorized views

Problem: How to process documents in a category (categorized view) in a simple way?

This is a problem I've heard a couple of times at work from the Notes-guys (I'm a Domino/Web-guy). I've tried a couple of approaches/googled, but found nothing. Therefore I archived this problem in my "experiment ideas"-folder.

Today I had a little time to play around, and found a very simple solution.

When you click an item in a view, a couple of properties is set in the NotesUIView. CaretNodeId points to the NoteId of the document that is selected. The gem is that when you click a category, an "invalid" NoteId is set. If you can't get a document from from the db using that NoteId, the item clicked is probably a category.

Now you know the user clicked a category.

Combining the ViewSelection-formula of the view, and the Formula-property of the first column (the column has to point to one field in the document for this to work), you have a valid search-formula for db.search.

Now you know which documents the user want.

Of course, the user don't want to modify documents by category each time they click the category. Add a button that sets an environment variable, and test for the value of that variable in the onSelect-event in the view. If you want to make available several types of action on the documents, use more values.

Now you know when/how the user wants to modify the documents

Problem solved..? :)

As always, blogposts like this are useless without a simple demoapp..
>> DemoApp

If you like/dislike this workaround/hack, please leave a comment.

Update: The onselect event is new in Notes 8. So if you're running on older versions, I guess there's no simple solution. Thanks to Theo for giving me a heads up!

Wednesday, September 17, 2008

Json-search on Domino views

Domino (at least pre 8.5) doesn't let you use $$SearchTemplate forms as anything other than vanilla Domino-generated html forms.

This makes it somewhat difficult to search/getting Json-data in return.

The simplest workaround I've found is to make a view with one column. The data is formatted as Json, with a trailing comma. In the search-template, I wrap the $$ViewBody-field like this:
<div id="result">[|$$ViewBody-field|,""]</div>

When I want to search, I use a temporary iframe, added to the DOM using Javascript. To make it as simple as possible, browser compatibility-wise (events), I add a the iframe inside a temporary div using innerHTML. The iframe has an onload-event. When that triggers, I know that the Json is loaded.

I'm terrible at explaining code (terrible at explaining anything), so I've made a simple demo-app you can try. Remember to create a full-text index on the demo-db before trying it out.

Flash demonstration of the app (I parse the Json to HTML):




>> Demoapp

The app is tested, and works in Opera 9.5, FF3 and IE7 on Vista x64.

Wednesday, September 10, 2008

Fun with the WebBrowserControl - DemoApp release

I think I've cleaned up the demo-app good enough now.

I added another demo, showing the browser on the onHelp event in Notes/offline Mootools accordion.

Apart from that, I've changed the way I store JS. I now store it in a page, and use the NotesNoteCollection of the db to fetch it (look in the code). This makes it easier to implement in a Template-context.

If you test the app on something else than Notes 8, I'd appreciate feedback (preferably in the comments) on the stability of the demoapp.

To test out making friends, edit a document in the DiscworldCharacters-view (make sure the HTTP-task is running). If you're running the demo on a server, edit the Add friend-button, so that the domain is correct.

The other demos are in the BrowserDemo-form.

>> The WebBrowserControl demoapp

The flash-demos are here

Update: Martin tested the app in N6.53. That makes it at least compatible with N6.5 to N8.02.

Tuesday, September 9, 2008

Fun with the WebBrowser Control - Finishing up

I'm more or less satisfied with what I've accomplished with my experiments.

I'm now able to "bootstrap" (in lack of a better term) a JS-library/HTML to a WebBrowser control. This enables you to write truly offline widgets (no HTTP-task involved) for your notes-apps.

I'm also able to bind LS-functions to the events available in the WebBrowser-control using environment variables and Execute.

I won't be able to clean up the demo-app today. Hopefully I'll be done tomorrow if nothing else comes up.

Until then, I've made another flash showing a youtube-movie inside Notes (for instruction videos/etc.), and logging of navigation (for tracking of online wizards inside Notes/etc.) in the webbrowser using the BeforeNavigate2-event.

If you want me to do make another example of integration with the WebBrowser-control, leave a comment, and I'll see what I can do.

All the flash-demos:
Demo 1 - simple usage
Demo 2 - Web autocomplete to Notes
Demo 3 - Offline Mootools-demo
Demo 4 - Web Autocomplete - Add friends
Demo 5 - Loading Youtube-movies/logging navigation

Monday, September 8, 2008

Fun with the WebBrowser Control - Making friends

The demoapp has gone through serious a serious rewrite to make the code more reusable/flexible. I've also worked a little bit on dynamically binding events through environment variables and execute. Sadly I haven't found a way of truly dynamically binding events (On Event..).

Some of the methods for the browser-class I use are now also chainable. E.g.
Dim browser As New Browser
Call browser.setSize(300, 400).openUrl("http://google.com")


Todays flash shows a little friend-making:



The document being edited has a hidden multi-value field that holds ID's to other "Discworld Characters". When a character is selected in the lookup, the id of that document is put inside a hidden field in the browser document.

When the browser is closed, the values is picked out of the browser document and added to the "friends-list". Then the document is refreshed/a lookup is run to update the list of friends (a computed for display field). This is achieved by binding a function-call to the windowclosing-event of the control.

I'm still not ready to release the demoapp. If I do that, I'm probably going to stop experimenting on the WebBrowser, and start mucking with XPages..

Tuesday, August 26, 2008

Manga Me



Joining in on the fun..

Make your own

Monday, August 25, 2008

Workaround: ViewRefreshFields does not trigger WQO

A form I'm working on gets a combobox (or select list, if you prefer) generated by a WebQueryOpen agent. The content of the combobox is generated on the basis of another field.

I use one combobox to select a db, then the second shows documents in the selected db.

When I select a db, I want to generate the select list with documents in the selected db. I first considered running ViewRefreshFields, but WQO-agents don't get triggered by this.

It seems that running agents with the ToolsRunMacro @Command keeps the form as it was before the running of the agent.

The workaround, then, is quite simple. Add a hidden button (style="display:none") on the form. OnClick on the button, run the agent generating HTML (and ViewRefreshFields, if you need to refresh the form). Onchange in the db-combobox, click the button.

document.getElementById('your-id-in-the-button').click()


If you've used a similar approach, and found weaknesses with it, please let me know in a comment.

Friday, August 22, 2008

Fun with the WebBrowser Control pt 3

I've come a little farther in my experimentation.

Today's flash show offline DHTML. The flash doesn't do the FX justice btw. When I'm done tinkering, you'll get to see the real deal.




I put the mootools framework JS inside a RichText-item on a document. Fetch the js as a string. Write the string and some HTML to the WebBrowser-control. Hey Presto! Offline DHTML.

My brain is fried right now, so no more programming today.. :)

Wednesday, August 20, 2008

Fun with the WebBrowser Control pt 2

Getting to know the object more and more.

Today's flash shows capturing events from the browser-control, and picking values from the document inside the control.



This resource from Microsoft has helped me along somewhat. Only a fraction of the methods/events/properties documented is available in Notes though.

The demoapp is still not ready, as I'm still not finished investigating possibilities.. :)

Tuesday, August 19, 2008

Getting relative dbpath on localhost

Update, 25.08.08: It appears I'm not the only one struggling with this issue. Jack Ratcliff had a similar issue. The difference being that he didn't have a document to process.

Since all databases are required to have one view, and all design-elements basically are NotesDocuments, it's easy to rewrite the code to work without needing a "real document".

Function WebFilePath( db As NotesDatabase ) As String
Dim view As NotesView
Set view = db.Views(0)

Dim viewDoc As NotesDocument
Set viewDoc = db.GetDocumentByUNID( view.UniversalID )

WebFilePath = Join( Evaluate( |@WebDbName|, viewDoc ) )
End Function



If for some reason you're not working in a HTTP-environment, use this instead of @WebDbName:
@ReplaceSubstring( @DbName[2] ; "\\" ; "/" )

--

Today, I had to make a multi-db search agent for web localhost compatible. When printing the path to the db, I had used NotesDatabase.FilePath. The problem with this is that it returns the filesystem path to the db when running locally. E.g. c:\lotus\notes\data\db.nsf

I could use NotesDatabase.ReplicaID, but I prefer somewhat readable URLs.

My solution was using evaluate @DbName[2] (the path to the database), with a document from the db I was currently processing. Without the document as second argument, evaluate would return the path to the search database.

Function printPath(resultDoc As NotesDocument) As String
Dim dbpath As String
dbpath = Join( Evaluate( |
@ReplaceSubstring(@DbName[2];"\\";"/")|,resultDoc))

printPath= "/" + dbpath + "/0/" + resultDoc .UniversalID
End Function

Friday, August 8, 2008

Fun with the WebBrowser control

I'm messing around with the WebBrowser Control again.. :)

I planned on getting a demoapp together today, but I had to find a workaround for showing/hiding the layer holding the webbrowser, and that took most of my time. Hide-when doesn't work properly with the WebBrowser-object.

At least I want to show some fruits of my labor. A small flash animation showing scripting between Notes and the control/putting the WebBrowser control inside a layer.




The promt/messagebox in the demo are Notes'. You can tell from how they look, compared to the browser's alert/prompt.

I'll probably post a demoapp/more info when I have something more useful..

Wednesday, July 2, 2008

Simple trick to add PNGs as image resource

In the Open-dialog you get when you click New Image Resource, type *.png and press enter. Then you filter the "view" so that only PNG images show. You can use this trick to add other file-types as well.

I've used this trick in other applications for years, and thought it was common knowledge. A colleague of mine wasn't aware of this the other day, so I thought I should share it with you as well.

I don't know if this works on Mac/in Linux as I'm not familiar with their file system browsers.

Friday, June 20, 2008

6 methods to control what and how your content appears in search engines

I've had some trouble recently with Yahoo trying to index links in a view that contains agent-generated documents. This gives the Domino-error, "Special database object cannot be located". All errors are sent to my mailbox (an error-folder). I try to remove as many error-sources as possible, to avoid having to read hundreds of mails, or even worse, deleting all error-messages because there are too many.

Found a nice piece on avoiding indexing.

Added this to the head content of $$ViewTemplateDefault
<meta name="robots" content="noindex" />
<meta name="robots" content="nofollow" />

I could filter/hide the view, but it's nice to have around for debugging purposes, and it doesn't contain confidential information.

I'm lazy :)

Wednesday, June 18, 2008

Quick reminder - new LS methods in Notes 8

I re-read a slide from LS-08, and (re-)discovered a couple of new (and undocumented) LS methods in Notes 8. The methods should also be available in Java.

NotesDatabase.CreateDocumentCollection -> Empty NotesDocumentCollection

NotesView.CreateViewEntryCollection -> Empty NotesViewEntryCollection

NotesView.GetColumnValues( columnIndex ) -> LS @DbColumn without 64k limit

Test it for yourself (with the debugger running):

Sub Initialize
Dim s As New NotesSession
Dim db As NotesDatabase
Dim view As NotesView
Dim col As NotesDocumentCollection
Dim eCol As NotesViewEntryCollection
Dim colValues As Variant

Set db = s.CurrentDatabase
Set view = db.GetView( "someViewInTheDatabase" )

Set col = db.CreateDocumentCollection
Set eCol = view.CreateViewEntryCollection

colValues = view.GetColumnValues( 2 ) 'a valid colIndex
End Sub


Friday, June 13, 2008

Slick integration between FCKeditor and Lotus Domino

Update 16.08.08: According to a commenter, Sinisa, the notes.ini modification is not needed (tested on Domino R7), so even less requirements.. :)

Before you read on. The integration requires you to set DominoDisableFileUploadChecks=1 in notes.ini on the Domino Server.

I found no satisfying integration-examples that included upload-support for FCKeditor, so I decided to try to make the integration myself. As far as I've tested, you can upload whatever you want, only limited by settings in FCK and Domino restrictions.

Flash animation of the integration in action. Shows upload of an image and upload of a flash game.

Although it was a bit hard to hack around Domino restrictions regarding uploading files, I finally found out how to make it work.

All that was needed was a bit of try, fail, retry, repeat, a simple upload-form and a little bit of Javascript.

Demo application


>> Download

The interesting bits are the fckconfig.js page, the demo form, and the fckupload form. Read Ferdy's article to understand how you can upload files to Domino without using a Domino generated File Upload Control.

Requirements for the demoapp:
fckeditor in [Notes/Domino-path]\data\domino\html\js\fckeditor
If you're not running the demo on localhost, DominoDisableFileUploadChecks=1 in notes.ini on the server.

Tested with FCKeditor 2.6.1 in FF2.0, IE7(Vista) and Opera 9.5 (go try it out).

Friday, May 30, 2008

A couple of handy cheat sheets

I use both of these on a regular basis. I've printed them out on paper, so they're always withing arms reach.

Domino URL cheat sheet

Domino supported syntax for Full Text searching

For web development, ILoveJackDaniels.com has a few nice ones (the site seems to be down at the moment).

Monday, May 26, 2008

Upcoming experiment - Beautiful URLs with Domino



Just a little preview.. It's a simple "hack". No admins involved :)

More info about well designed URLs

Wednesday, May 14, 2008

StrToken/@Word in JavaScript


function strToken( string, separator, position ){
//If the string doesn't contain the separator, return
//empty string
if( string.indexOf( separator ) == -1 ){ return ''; }

var arr = string.split( separator );

//If the position is larger than the number of elements,
//return empty string
if( position > arr.length ){ return ''; }

//Return found item
return arr[ position - 1 ];
}



Tested in IE6/FF2

Thursday, May 8, 2008

@Unique in Javascript

Update, 16.06.09: As Ronaldo points out, this doesn't work in IE6, as it doesn't have the Array.indexOf-method (I probably tested the function in a Prototype JS-enviroment). To make the function work for browsers that doesn't have the aforementioned method:
if(!Array.indexOf){
Array.prototype.indexOf = function(obj){
for( var i = 0, len = this.length; i < len; i++){
if(this[i]==obj){
return i;
}
}
return -1;
}
}




function arrayUnique( array ){
var uniqueArray = [];

var item;
for( var i = 0, len=array.length ; i < len ; i++ ){
item = array[i];
if( uniqueArray.indexOf( item ) == -1 ){
uniqueArray.push( item );
}
}

return uniqueArray;
}



Tested in IE6/FF2

Friday, April 25, 2008

Setting charset in pages/forms with content-type: text/javascript

The Character set-setting for a page/form doesn't seem to do anything more than to set the character encoding for the text if content-type is text/javascript.

With character set set to Unicode (UTF-8), and content-type to text/javascript, Norwegian letters look like this:


If I set content-type to: text/javascript; charset=utf-8



Much better.. :)

Monday, April 7, 2008

How I got started with Lotus Notes

Quite a coincidence actually. After I graduated as a Bachelor of Information Technology in 2004, it was quite hard to get a programming-job in my part of Norway.

I applied for every interesting/uninteresting job I found (including project manager/etc :D), and got some interviews, but never got called back.

While still applying for programmer positions, I had a small job, setting up ADSL in private homes. The hourly pay was ok, but I only got one or two jobs per month.

Got tired of that, and started working at NorDan in Egersund, January 2005, putting together balcony doors/etc. While the job itself sucked, it was an overall fun time I had there, because of great colleagues (I'm more blue-collar than white-collar).

I had an interview with Compendia, my current employer. During the interview I got asked about Lotus Notes, which I'd never heard about. A month later, I got a call from my current boss.. I think my first words was something along the lines of "Ehhrmm... Who?". Having had several interviews that seemed to go ok before/not getting called back, I'd almost forgotten that I'd applied for a job. July 2005, I first put my hands on Lotus Notes (6.5).

If I hadn't gotten the job, I'd probably have gone back to school again..

Wednesday, April 2, 2008

A small simplification to "lookups" on the web

In this post, I referred to a method of looking up one document in a view, using the parameters startkey and untilkey.

E.g.

http://server/db.nsf/view?openview
&startkey=alex
&untilkey=alex_



I discovered today (wonder why it took so long) that the good old count-parameter works just as well as the untilkey-parameter. Personally, I think that the count-parameter is more readable than the previous method.

Simplified url:

http://server/db.nsf/view?openview
&startkey=alex
&count=1



I don't know if there is a performance-difference between the two methods. I tried a couple of benchmarks in firebug, but got nothing conclusive.

Wednesday, March 26, 2008

Simple function to trim JS string arrays

Update, 08.05.08:
A better trimming-function:

function trimArray( array ){
var trimmedArray = [];

var item;
for( var i = 0, len=array.length ; i < len ; i++ ){
item = array[i];

if( item !== '' ){
trimmedArray.push( item );
}
}

return trimmedArray;
}



Had a case at work where I needed to make a breadcrumb-like thingy.

The breadcrumb consisted of either two or three elements, and got it's values from form-fields. Instead of checking the fields for values, I put the values in an array, trim the empty values, and join the array with a separator.

The trimming-function:

/* Trim empty strings from JS string-Arrays */
function trimStringArray( array ){
return array.join( ',' ).replace( /,{2,}/g, ',').split( ',' );
}



Testcode for the Firebug-console.

Result (in Firebug):

Tuesday, March 25, 2008

Improved HttpRequest class - Handles request_content above 64k

For those of you that haven't heard, in Domino 7.01, IBM implemented a workaround for (HTTP POST) request-content above 64k.

I've updated the HttpRequest class, so that it handles requests above 64k automagically.

>> My testbed for the class
The test-app has a page that posts ~260k of data to an agent. This results in 4 request_content fields. The agent prints a report of the request ( [HtppRequest].printHtmlReport ).

The class (txt-file)

Let me know if there are errors.

Friday, March 14, 2008

Simple function to test if a list is empty

I needed a function to test if a list is empty today. This is of course something IBM doesn't think we developers have to test for, so it's not in the LS API.

Here is what I came up with.
Function listIsEmpty( listToTest As Variant ) As Boolean
If Not Islist( listToTest ) Then_
Error 1001, "The variable tested is not a list"

Forall item In listToTest
'If there is one or more items, the Forall-loop runs
' -> The list is not empty
listIsEmpty = False
Exit Function
End Forall

listIsEmpty = True
End Function

Thursday, March 13, 2008

Notes 8 Classic - working undercover?

I just started using Notes 8.0.1 this week. Some things I love, and some things annoy me.

Like this one:


I was working on a calendar-entry, when suddenly the classic client popped up. I can't close it by itself. When I close the standard client, this closes as well.

Don't know if this means that the classic client is used as a go-between for Domino Designer/old Notes apps and Notes 8 Standard Client?

Thursday, March 6, 2008

Internet Explorer 8 beta out

Looks like we don't have to wait another 5-6 year for IE.Next.

IE8 Beta here

Information about the beta

Wednesday, March 5, 2008

Gzipping/serving gzipped static content (JS/CSS)

Bandwidth is still a concern for a big part of the web users. To save bandwidth, the simplest job is to compress the content that "never" changes. For instance JS-frameworks.

There are a couple of approaches to this, "packing" utilities like Dean Edward's packer. This compresses the JS in such a way that it's still "compilable" in it's raw form. Another way is to gzip the file. This requires that the browser unpacks the file before it is sendt to the JS "compiler".

I prefer to use both packed and gzipped JS frameworks in production. This guide will hopefully help you serve gzipped JS-files without too much hassle.

The JS-lib I use in the accompanying demo, MooTools is 72KB "unzipped" (the version that is packed using JsMin). Gzipped, it's just shy of 20KB.

Preparing the Domino server


To enable gzipped content on the server, create a web site rule (requires restart of the HTTP-task).

Example of rule:

The above rule works like this: For all files in a subfolder named gz, the domino-server will add a HTTP-header, Content-Encoding, with the value of gzip. This tells the browser that the content it is downloading is compressed.

At Compendia, where I currently work, we've set this as a global rule, as it doesn't affect any other content.

Compressing files


To compress files on your machine, you need an application that can gzip files. I use gzip for DOS.

Download gzip for DOS:
32-bit (doesn't work on 64-bit Vista)
64-bit

Extract the downloaded utility to a folder of your choice. At work, I have it on c:\gzip.

Copy the file(s) you want to compress into the folder you extracted the gzip-utility.
Open command-line. cd into the folder you've extracted the gzipping-utility.

For the 64-bit utility: minigz64 name_of_file.js
For the 32-bit utility: gzip name_of_file.js

Be aware of that the dos application overwrites the original file by default.

Adding the files to a DB


Add the uncompressed/compressed file (New File Resource) to the DB of your choice. Rename the uncompressed file to lib\[filename].js, and the compressed to gz\[filename].js

With the aforementioned web site rule in place, browsers opening the gzipped file will get the correct HTTP-header, indicating gzipped content.

What about users that doesn't have gzip-enabled browsers/etc?


Some proxies/browsers/thin clients doesn't support/allow gzipped content. It's easy to identify if a browser supports gzipped content:

supportsGzip := ( @Contains( @GetHTTPHeader( "Accept-Encoding" ) ; "gzip" ) );



In the index-page of the simple demo-app, I compute the folder in a script-tag by the presence of Accept-Encoding: gzip header.

"<script src=\"/" + @WebDbName + "/" +
@If( supportsGzip ; "gz" ; "lib" ) + "/mootools-release-1.11.js\"></script>"



>> Download demo-app with gzip for Dos/mootools compressed/uncompressed.

PS! The index-page needs to run on a server with gzipping enabled, like in the picture at the top of this post. Either that, or you can run the browser in HTTP 1.0-mode. HTTP 1.0 doesn't have the Accept-Encoding header.

Good luck!

Tuesday, March 4, 2008

"Advanced" usage of the Print statement

I discovered this a couple of months ago, by coincidence (I actually read the documentation, instead of jumping to the example).

Punctuation characterPrint statement behavior
Semicolon or space in exprListThe next data item is printed with no spaces between it and the previous data item.
Semicolon at end of exprListThe next Print statement continues printing on the same line, with no spaces or carriage returns inserted.
Comma in exprListThe next data item is printed beginning at the next tab stop. (Tab stops are at every 14 characters.)
Comma at end of exprListThe next Print statement continues printing on the same line, beginning at the next tab stop. (Tab stops are at every 14 characters.)


In most of my previous code, when debugging, I wrote something like this:
Print "Field contents: " + doc.stringField(0) + ", " & doc.numberField(0)

Using the comma separated syntax, you can get away with:
Print "Field contents:", doc.stringField(0), doc.stringField(0)

I've started using the comma-separated syntax, when debugging, simply because it's less verbose/faster to write.

I did a little test to see how the different "methods" of using Print worked. It seems like the semicolon/comma a the end of the line doesn't work (see picture below). Either that, or I've misunderstood the documentation.

Monday, February 18, 2008

XML Nodetype Constants

Update, 04.03.08: It appears I was fooled by Prototype. The Node-interface/object is not available in IE.
..if (!window.Node) var Node = { };

if (!Node.ELEMENT_NODE) {
// DOM level 2 ECMAScript Language Binding
Object.extend(Node, {
ELEMENT_NO..


From reading JS-code in different places, it doesn't look like many people are aware that there are constants for the different node-types in a HTML/XML-document.

Most code I've come over tests the type like this:
if( xmlNode.nodeType == 1 )

Which is ok, if you remember that 1 corresponds with element-nodes. The Node-interface in DOM level 1 contains a property/constant for each of the nodetypes.

A list of the constants:
ELEMENT_NODE: 1
ATTRIBUTE_NODE: 2
TEXT_NODE: 3
CDATA_SECTION_NODE: 4
ENTITY_REFERENCE_NODE: 5
ENTITY_NODE: 6
PROCESSING_INSTRUCTION_NODE: 7
COMMENT_NODE: 8
DOCUMENT_NODE: 9
DOCUMENT_TYPE_NODE: 10
DOCUMENT_FRAGMENT_NODE: 11
NOTATION_NODE: 12

A more readable version to the previous test:
if( xmlNode.nodeType === Node.ELEMENT_NODE )

Monday, February 11, 2008

I'm somewhat addicted

60%How Addicted to Blogging Are You?

Tuesday, February 5, 2008

ReadableLookups: Global constants for forms/subforms

This technique is probably overkill on smaller applications. It's quite verbose compared to writing the values directly into fields/computed texts. Long living, ever changing applications may benefit using this technique though, maintainability-wise.

Basically, the technique is a shared, computed for display field that you put in all your forms. This field may contain the names of lookup-views, index of lookup-columns, replica-id's, filepaths to co-existing applications, et cetera.

Due to the nature of the readable lookups, there's no problem with inline comments in the field, or re-organizing the "constants".

Example of field

To extract a value from the field, you have to use quite a verbose syntax, but if the constant is used a lot of places, the added verbosity is worth it, as you only have to change the value one place, in the shared field.

Example of extraction of value from constants-field:

(name of shared field, SF_CONSTANTS)

If you want the Notes/Domino constants available to Javascript/etc on the web as XML, simply put the field in a form with content-type: text/xml. Add a document-node (APPLICATION_CONSTANTS in the example below), and you have access to the constants on the web as well.

The form
Example of output XML

Since the constants are in a field, the values are also available to LotusScript when working with documents.

Simple function to extract values in LS.

PS! Remember to put the field at the top of the form, so that it is available to all the other fields in the form

Thursday, January 31, 2008

No more coding PassThruHTML for RT-fields

Update, 04.02.08: It seems ReplaceItemValue on NotesRichtextItems is limited to dreadful 64k limit. Undocumented as far as I can tell.

When storing HTML in documents, I've always done the .RemoveItem .. CreateStyle .. PassThruHTML .. CreateItem... body.AppendStyle ..body.AppendText..

It can be simpler (WebQuerySave):
Dim session As New NotesSession
Dim doc As NotesDocument
Set doc = session.DocumentContext

Dim body As NotesRichTextItem
Set body = doc.GetFirstItem( "body" )
Call doc.ReplaceItemValue( "body", body.GetUnformattedText() )
Call doc.Save( False, False )


Where "body" is the name of you RichText-field.

The RichText-fields needs to be marked as PassThruHTML in the form. The easiest way to do this is to add a space before and after. Select space[RichText]space. Text -> Pass-Thru HTML.

This turned out to be a great day of discovery for me :D

Simple demo-db to test the difference between saving the document with above code on WQS/no WQS.

--
(Original post, Flush NotesRichTextItem on one line)

In the past, I haven't found another way to flush content in a RTI than to remove the existing, and create a new one.

I did a little testing today, and found out you can use ReplaceItemValue to flush the content:
Call [NotesDocument].ReplaceItemValue( "[RT-fieldname]", "")

If you want to replace the content, with something else from a String:
Call [NotesDocument].ReplaceItemValue( "[RT-fieldname]", newContentString)


Leaner code.. Yay!!

Wednesday, January 30, 2008

Embed multiple standalone forms on a domino form using DHTML

By standalone I mean that when you submit a form, you don't submit all the others/the main form.

I decided to make this when I read a question in Jake's latest article, How to Embed a Login Form on All Lotus Domino Pages.

The question is not directly connected to this blogpost, but somewhat.

The question:
Hi Jake,

We are getting two HTML form tag in view source of page in browser, one form tag for main form and other which is login form.

My question is, How will I handle when we are submiiting the Login form ? Will it submit the main form as well ?

Could you please help me

Regards
Ajay B Mali


The demoapp loads another form using an iframe. Using DHTML I pick the form out of the iframe and inject it at the top or bottom of the form that is open. The reason for either top or bottom is that forms can't be nested (read Jake's excellent article for more info). If you want it in a specific position on the page, CSS may help you.

The reason for iframe and not XHR (Ajax), is that XHR returns text. With the iframe, I can more or less pick what I want out of the "iframed" document, the way I would on the "main" document.

>> The demoapp (Names-form)

Tested in Opera 9, IE7 (Vista) and Firefox 2

Tuesday, January 29, 2008

Readers-/Authors field one liner

While doing som googling today I discovered that NotesDocument.ReplaceItemValue returns NotesItem. The reason I haven't discovered this before is probably because I've been lazy, and used the "dot" notation.

What I was googling for was actually advantages of using Get-/ReplaceItemValue versus the dot-notation. I've mostly used those two methods in loops, when the dot-notation would be horrid and evil to use (the copy-paste-then-edit-index-method). My quest recently is to create readable code, so I'll try using getters/replacers ( :| ) over dot-notation, and see how I feel about that.

To the point of this post.. I discovered the return value in a comment to Andre Guirard's blogpost/article, GetItemValue and ReplaceItemValue vs. "dot-notation".

Since Get-/ReplaceItemValue returns a NotesItem, you can write:doc.ReplaceItemValue( "read_access",_
"Arthur Dent/Earth" ).IsReaders = True

doc.ReplaceItemValue( "write_access",_
"Ford Prefect/Megadodo Publications" ).IsAuthors = True


I'm not totally confident with the readability of the above. A little more readable approach (?):Dim readAccessItem As NotesItem
Set readAccessItem = doc.ReplaceItemValue(_
"read_access","Arthur Dent/Earth" )
readAccessItem.IsReaders = True

Dim writeAccessItem As NotesItem
Set writeAccessItem = doc.ReplaceItemValue(_
"write_access", "Ford Prefect/Megadodo Publications" )
writeAccessItem.IsAuthors = True


Maybe none of the above is easy to read to people unfamiliar with ReplaceItemValue, but the Lotus Notes LotusScript API doesn't always make it easy to write readable code. I wish all chainable methods were as readable as this:Call session.currentDatabase.allDocuments.removeAll()

Thursday, January 24, 2008

Serving Gzipped content - Test for browser-support

In the newest version of the CMS I'm working on/maintaining, a couple of customers got strange errors. The culprit was that for some reason or other, their browser didn't accept gzipped content.

The workaround


acceptsGzip := ( @Contains( @GetHTTPHeader( "Accept-Encoding" ) ; "gzip" ) );

{<script src="/} + @WebDbName +
@If( acceptsGzip ; "/gz/mootools.js" ; "/lib/mootools.js" ) + {"></script>}

Wednesday, January 23, 2008

A thing to be aware of when creating FT-index with LotusScript

The agent must run locally. This is achieved by [agent].RunOnServer.

This is probably public knowledge.

A thing I just discovered is that the database you're creating the index for has to contain at least one document. I stumbled onto this when working with an agent that creates a "history" database (empty on creation) from a template.

Another application copies documents into the history DB when changes are made. The history DB has a search interface, and therefore I create the the FT-index.

It's a simple workaround, create a dummy-document, create ft-index, delete dummy-document, but WHY should I have to do that?!?

Wednesday, January 16, 2008

Coping with caching of JS-libraries

Update, 17.01.08: If you want to play it safe, follow Vitors advice to me (in the comments), and use versioning on the file-names of the scriptlibs. My experiment was fun, but it's bad to put the "responsibility" of handling change in the codebase, on the customer.

At work today, a customer couldn't get the CMS I am responsible for to work properly. The reason being that I recently updated some markup and JS in the design-template for the CMS. The client had the old JS library cached in the browser. Due to the markup-change, a function in the old JS library failed, which resulted in a JS error, and things not working properly.

This got me thinking on how to avoid this in the future/let the user know what failed, and if a caching issue, how to force a refresh.

My so far best solution is to have two javascript-variables (strings). One in the markup (above all the external JS-libs), and one in an external JS-library. If the two doesn't match, inform the user.

The downside to this approach is if you have several non-static JS-libraries on one page. You can't know which one is going to be cached, so you have to add the check to all of them.



[Flash Animation. If not visible in your RSS-reader, open blogpost]


The accompanying demo application is a mess, but hopefully it's easy for you to play with. Tested in IE6/7, FF2 and Opera 9.

>> Simple demo application

Friday, January 11, 2008

Taking visual control of Domino generated views

This is somewhat related to my previous post, as that is what sprung this idea forward, adding a class-attribute per even row/colgroup-nodes per column, dynamically.

In the downloadable demo application, I've made a $$ViewTemplateDefault-form with a JS-function that does the above, onload.

Every even row has class="even". The generated colgroup is structured like this, a <colgroup> containing <col id="column[number]"/> nodes, where [number] corresponds to the column number it represents.

To get a fix on the Domino-generated table, I've added a span around an embedded view (to easier control count). id="domino_view", class="@ViewTitle[1]".

I don't know what more to say than take a look at the screenshots, and download the app, look at the code/css if you think it's something you can use.

Some screenshots


Categorized view, no CSS
Categorized view, styled

Flat view, no CSS
Flat view, styled

>> The demo application

Tested in IE 6/7, FF2 and Opera 9.

If you discover bugs/find something strange in the demo-app/etc, let me know. Preferably through comments.. :)

Wednesday, January 9, 2008

CSS - Browser independent column styling

Styling on a column level can be a really powerful tool to create great looking tables. I'm no designer, so I can't show you how to create great looking columns, but I can show you the points of attack.

By using only "one point of attack" in the markup per column makes for leaner pages. By that, I mean that you could put a class on each td (first, second, etc) on every row, but that isn't really necessary, and with big tables, big html.

Internet Explorer supports a lot of styling opportunities with the colgroup element. With Firefox and Opera (not sure about Safari), you can only apply a handful of styles using this element, but on the other hand, they fully support CSS 2.1 which has some really powerful selectors.

Example of unstyled columns

Styling for < IE 7


I have three colgroups, one for each column. The first for column one, second for...
<colgroup id="name" />
<colgroup id="address" />
<colgroup id="guild" />

Using the CSS 1 id selector, #, it's quite easy to style columns in older IE browsers:
#name { font-weight: bold; width: 200px; background: #eee; }
#address { width: 150px; color: #888; }
#guild { font-style: italic; }

Example of styling using colgroup

Styling for modern browsers (FF, Opera, Safari)


To get a reference to specific columns, I use the :first-child selector, and the adjacent sibling selector.

First column in the table body (the table has id="css21"):
#css21 tbody td:first-child { font-weight: bold; width: 200px; background: #eee; }

Second column:
#css21 tbody td:first-child + td { width: 150px; color: #888; }

Third column:
#css21 tbody td:first-child + td + td { font-style: italic; }

For more columns, simply add + td per column.

Example of styling using CSS 2.1

The CSS combined


#name { font-weight: bold; width: 200px; background: #eee; }
#css21 tbody td:first-child { font-weight: bold; width: 200px; background: #eee; }

#address { width: 150px; color: #888; }
#css21 tbody td:first-child + td { width: 150px; color: #888; }

#guild { font-style: italic; }
#css21 tbody td:first-child + td + td { font-style: italic; }

Example using both colgroup and CSS 2.1 styling

The reason for the redundancy is that IE6 seem to ignore styles that uses the above CSS 2.1 selectors, instead of ignoring the selector.

This would be ignored by IE6:
#guild, #css21 tbody td:first-child + td + td { font-style: italic; }

To remove the redundancy from the rendering engine of the browser, you could exploit the star html bug in pre IE 7 browsers. Add * html before each colgroup-styling. Then the rendering engines of IE 6 and older browsers see those styles, whereas modern browsers only see the CSS 2.1 selector styles.

Example:
/* Only visible to pre IE 7 */
* html #guild { font-style: italic; }
/* For modern browsers */
#css21 tbody td:first-child + td + td { font-style: italic; }

Great CSS 2.1 information


456 Berea Street CSS Selectors Part 1
456 Berea Street CSS Selectors Part 2
456 Berea Street CSS Selectors Part 3

Comments, critique, etc. are as always appreciated!

Wednesday, January 2, 2008

2007 - A(lmost a) year in review

The blog started off with a small post, January 17. 2007.

When I started the blog, I thought I'd mostly post about Web/Domino-stuff. As the year progressed, I believe I ended up with more Notes content than Web content.

My idea-well isn't as full as it was half a year ago, but hopefully I will be able to find/create something useful to share with you in 2008.

Instead of posting my favourite posts of the year, I'll leave you with the most visited content.

Top 5 visited content of 2007

  1. Rant about Notes 8, Lotus/IBM

  2. (mis?)Using $$ReturnGeneralError as a shortcut for search

  3. Simplifying Evaluate

  4. Ajax-like live-search in the client

  5. Cross client (Web/Notes) validation using JavaScript

On a more personal level, the highlight of 2007 was definitely my trip to South Korea.