Tuesday, 22 April 2008

The sustainable enterprise (what is your company doing?)

I do not want to make a habit of doing semi-advertising on this blog, but I could not resist and let the coincendence pass by unnoticed.
Today being the international Earth Day and yesterday InterfaceFLOR, the company I work for, announced receiving a prestigious royal award in the UK for sustainability.
Led by our sustainability pioneer Ray Anderson, InterfaceFLOR are striving to be a fully sustainable enterprise by the year 2020 and the enthousiasm of this initiative is carried out by employees from the factory floor up to the board. And given the raw materials we use in our products are all based on oil, not an easy goal. But a goal well worth pursuing if we want to pass on a live-able earth to our children and grandchildren.
More on the sustainable efforts of InterfaceFLOR can be found here and here.

Thursday, 17 April 2008

I am on Planet Lotus

Thanks to Yancy Lent, this blog now feeds into the Planet Lotus site. Now the task is mine to keep adding content here at a regular basis.
Although there is not much here yet it wil grow.
At the moment I think my focus when blogging will mainly be Lotus Notes / Domino development, but who knows, this may change in the future.

I really like how Planet Lotus works. I can follow almost all (and more) of the blogs I am interested in on one page. Not a day goes by without picking up an interesting topic. And as a result I now only have a few feeds left in my Google reader.

Wednesday, 16 April 2008

I tweeked the default layout (a bit)

When setting up this blog I chose the standard available Demin Stretch. But I did not like the used colours in the sidebar header.
So I decided to make some changes to that and make it more in line with the rest of the colours used in this template. So here it is, a nice blue border.

And while I was at it, I also added some borders to the post headings and I added an image in the header reflecting my all time favorite hobby, kayaking.

Monday, 14 April 2008

detecting language settings for a website using @formula

The company I work for is a multinational in Europe and their website, reflecting Europe, is multilingual. The main url for this website however directs to a general page for all of Europe and is in English. And you get it, soon the request came for the site to be able to detect where the visitor is coming from and redirect this visitor to his local language homepage if it is available.
After some discussion we decided to use the setting for browser language rather than using the some IP-detect to determine the preferred language of the visitor.
Initially I started using the @LanguagePreference, which given the name of the function would be the right one to use. And it did for a while, until we started rolling out the website to countries where more than one language is spoken. Like Belgium with french and dutch or Switzerland with german, french and italian.
In these countries we would like the visitors to be redirected to the homepages of that country in the preferred language of the user. And that is where @LanguagePreference starts to fail.
Lets take an example: A Belgian visitor would most likely set their preferred language to french or dutch (vlemish), respectively fr-be & nl-be in the browser.
But @LanguagePreference respectively returns "fr" and "nl". And thus we do not have enough information to route the visitor to the proper homepage and they are redirected to the french or dutch homepage.
As I do not know all the @functions by heart, I turned to our admin (Dennis), to see if this could be solved in our firewall. But instead of configuring the firewall he pointed me to the @GetHTTPHeader function and its HTTP_Accept_Language parameter.
As it turns out, this function nicely returns a string of the set languages as set in the browser. Although the browsers have their differences, the major ones are comparable when languages are defined for countries that have multiple languages.

So here is how it works:
When opening the default homepage, before processing the rest of the page, some @function code determines the visitors language and does a dblookup to see if a homepage exists in visitors browser defined language. If so, a meta refresh is set to re-direct the visitor to the local language homepage.

This is the code that is used:
(Underneath the second rem block a keyword is retrieved as the naming of the languages in the site is not always in line with what the browsers use and somtimes the browsers are not using the same language coding.)


REM {== get the languagesettings from the browser (retrieve the first setting in the list) ==};
brlang := @LowerCase(@Subset(@Explode(@GetHTTPHeader("Accept-Language"); ",");1));
brlang_2 := @Left(brlang; 2);

REM {== get the Languages Browser keyword for languages that differ between browser and site ==};
REM {== and replace if preflang is found in browserlangs ==};
lb := @LowerCase(@DbLookup("Notes" : "NoCache"; ""; "luKeywords"; "languages browser"; 2; [FailSilent]));
browserlangs := @Left(lb; "~~");
sitelangs := @Right(lb; "~~");
preflang:=
@If(
@IsMember(brlang; browserlangs);
@Subset(@Subset(sitelangs; @Member(brlang; browserlangs)); -1);
@IsMember(brlang_2; browserlangs);
@Subset(@Subset(sitelangs; @Member(brlang_2; browserlangs)); -1);
brlang_2
);

REM {== try to get indexpage for user browserlanguage ==};
key := "index_" + @UpperCase(preflang) + ".html";
page := @DbLookup("Notes":"NoCache";""; "webpages"; key; 1; [FailSilent]);

REM {== if path_info ends in /, remove it, so it compares better to dbPath ==};
pathinf :=
@If(
@Right(Path_Info; 1) = "/";
@Left(Path_Info; @Length(Path_Info)-1);
Path_Info
);
REM {== show default homepage when path in url is not blank or identical to dbpath or ==};
REM {== if language homepage for browserlanguage is not found ==};
REM {== else reroute to language homepage ==};
@If(
pathinf != "" & pathinf != dbPath;
"";
page = "";
"";
"<meta http-equiv=\"refresh\" content=\"0;url="+dbPath + "/webpages/" + page +" />"
)

Wednesday, 26 March 2008

I stand corrected, form formula and Dojo Dialog does work !

It seems I made a small mistake with big consequences. And the good news is that the form formula does work with dojo dialog.
While I was preparing the functionality mentioned in yesterdays post for go live, I discovered it did not work after cleaning up (deleting) all the trial code.

The mistake was, I called the wrong view in the "/viewname/docid?OpenDocument" for the dojo dialog. This mistake was caused by the application having 2 views with an almost similar name and the view I called did not have a form formula, which is why it rendered the Notes format of the form in the dojo dialog instead of the webformat.

Fortunately this mistake was quick to fix, by pointing the url to use the correct view.
So when reading yesterdays post, please ignore the text about the form formula not working.

Tuesday, 25 March 2008

Form formula with Dojo Dialog

It has been a while since Lotusphere, but today I put some functionality live that I had created after being stunned at Lotusphere by the session (BP212) that displayed some of the features in Dojo together with Notes/Domino. In fact Viktor Krantz had made a real effort in creating seperate javascript objects for Domino to show in his demos at BP212.

Putting this code live, reminded me that I wanted to write something about Dojo and the fact that for some reason the form formula is not working when opening a document using dojo dialog.

So returning from Lotusphere I started looking into Dojo and implemented the Dojo grid and Dojo dialog. As I wanted to have some handson with dojo, I did not re-use the Domino objects from BP212.
I made a grid working using the JSON setup Scott Good had shown in his ajax/json session (JMP205). This was actually the most work and required the most lines of javascript (mainly building the search url and the grid layout).

I then created a simple html form to open in a dojo dialog that will be triggered by clicking on a row on the dojo grid. All works fine, except the form specified in the form formula of the view that is used to retrieve the document does not seem to work. It uses the Notes version of the form instead of the specified web format.
I double checked twice and using the same "viewname/docid?OpenDocument" directly in the browser does open the specified web format as specified in the form formula.
Searching the web (LDD a.o.) did not give any clues into this problem having occurred before, nor did posting this problem on the LDD forum result in any answers.
So in the end I solved this problem by using computed subforms, one for the Notes client and one for the web
.

Has anyone else come accross this problem? Forget this, it does work, see the post on 26-03-2008.

For samples of code look at the Lotusphere session:
JMP205 AJAX and JSON for IBM Lotus Domino Applications (Scott Good)
BP212 The Great Code Giveaway: "Beyond Cool" (Rob Novak & Viktor Krantz)

Sunday, 9 March 2008

Moving values up and down in multiple multivalue fields

A while ago I received a request to be able to maintain a table of values. In Notes that means creating several multivalue fields that are displayed in a table.
I had done this before using the tablewalker solution by Michael Rohrbach and it still works fine after all these years.
However the current user was not really satisfied with the sorting option in the Tablewalker solution and wanted to be able to visually move rows up and down. This is basically moving values up and down in multivalue fields. So I started thinking about how to do this while the user is viewing the table on the form using @functions.
Based on an application I found on OpenNTF by Tomas Ekström, TEd, Table Editor I build a fairly simple solution for this using @functions.

This is the solution:
To start I created a computed multivalue field in which the fields to sort are stored, I called this field "MoveFields".

On the form there are a couple of (hidden) multivalue fields to hold the user data and computed for display fields to display the values of the hidden fields neatly in the table (cut-off if need be for each seperate value to fit on one line).
To move the values up and down in the array I added an extra field in which the user can enter the row to move "CurrPos".
Finally two arrows (up and down) are added with action hotspots, in which the sorting is done. The code should be self-explanatory.

Screenshot of the (part of) the table and buttons:











Code for the up hotspot:

REM {CODE FOR MOVING UP };
Fields := @GetField("MoveFields");

NoOfFields := @Elements(Fields);
NoOfElements := @Elements(@GetField(Fields[1]));

@If(CurrPos < 1 | CurrPos > NoOfElements;
@Return(@Prompt([Ok];"move up"; "Number must be within range of available rows"));
""
);
@If(CurrPos = 1;
@Return(@Prompt([Ok];"move up"; "Cannot move the first row further up !"));
""
);
REM { ----------------------------------------------------------------------------------------------------------
move the current row one position UP
1st subset gets the elements above the current element and the element to switch (only when current position is below second row)
2nd subset gets the current element
3rd subset gets the element above the current element
4th subset subset gets the elements below the current element (only when current position is above the last position)
-------------------------------------------------------------------------------------------------------------------- };
@For(n:=1; n <= NoOfFields; n:=n+1;
@SetField(Fields[n];
@If(CurrPos <= 2; null; @Subset(@GetField(Fields[n]); CurrPos-2)) :
@Subset(@Subset(@GetField(Fields[n]); CurrPos);-1) :
@Subset(@Subset(@GetField(Fields[n]); CurrPos-1); -1) :
@If(CurrPos = NoOfElements; null; @Subset(@GetField(Fields[n]); CurrPos-NoOfElements))
)
);
REM { set currunt position to new rownumber };
@SetField("CurrPos"; CurrPos -1);
@Command([ViewRefreshFields])


Code for the down hotspot

REM {CODE FOR MOVING DOWN };
Fields := @GetField("MoveFields");

NoOfFields := @Elements(Fields);
NoOfElements := @Elements(@GetField(Fields[1]));

@If(CurrPos < 1 | CurrPos > NoOfElements;
@Return(@Prompt([Ok];"move down"; "Row number must be within range of available rows"));
""
);
@If(CurrPos = NoOfElements;
@Return(@Prompt([Ok];"move down"; "Cannot move the last row further down !"));
""
);
REM { ------------------------------------------------------------------------------------------
move the current row one position DOWN
1st subset gets the elements above the current element and the element to switch (only when current position is below first row)
2nd subset gets the element below the current element
3rd subset gets the current element
4th subset subset gets the elements below the elements to switch (current element +1) (only when current position is at least 2 rows the last row)
----------------------------------------------------------------------------------------------------- };
@For(n:=1; n <= NoOfFields; n:=n+1;
@SetField(Fields[n];
@If(CurrPos = 1; null; @Subset(@GetField(Fields[n]); CurrPos-1)) :
@Subset(@Subset(@GetField(Fields[n]); CurrPos+1);-1) :
@Subset(@Subset(@GetField(Fields[n]); CurrPos); -1) :
@If(CurrPos = NoOfElements-1; null; @Subset(@GetField(Fields[n]); CurrPos+1-NoOfElements))
)
);
REM { set the current position to the new row };
@SetField("CurrPos"; CurrPos +1);
@Command([ViewRefreshFields])