Blogs

Blog Tags Help

Enter a tag to filter the current view

All entries tagged with domino

PNG support in the Notes client? Maybe they should start with full support for GIF...

Tim Tripcony  

A couple of disturbing observations:

First, in a 2006 post from Julian about animated favicons, the little animated banana dances at the exact same tempo as the song "Your Star" by the All-American Rejects. Try it... it's mesmerizing.

But secondly, to explain the title of this post, I noticed an odd behavior related to image resources in the Notes client: GIF support is incomplete. ICO files are actually stored in the same format as GIF, just with a different extension. So another approach to creating animated favicons is to simply rename an animated GIF to .ICO. That's how Dean Edwards created a favicon that periodically blinks. Theoretically, then, we should be able to download any site's favicon, rename it to .GIF, import it as a file resource, and then display it in the Notes client. No such luck.

Since real browsers (read: Firefox, Safari, and Opera... of course, as always, IE fails) don't distinguish between the two formats and simply trust the server-supplied MIME type, navigate to the same image resource via a URL and the image displays as expected. But try to reference the same image resource anywhere in the Notes client (or even just preview it in the image resource list), and you get nothin' but gray.

Sorry, Bruce, I tried...

Magic Button

Tim Tripcony  

For as long as I can remember (possibly 2001) I've always maintained some variation on a SmartIcon toolbar button that a former coworker called the "magic button": it allows new fields to be added to a document and existing fields to be overridden or deleted from the document, either while editing the document or while it's selected in a view. I've seen various approaches to this basic concept over the years, and each time I install Notes on another computer, I either consult the Google to find one I've used before or just reconstruct it from memory. It finally occurred to me that if I just posted the one I'm currently using here, not only would it be easier for me to find later, but some of you might find it useful as well. So here's the current incarnation of said magic button:

targetField := @Prompt([OkCancelEditCombo]; "Select Field"; "Select a field to override:"; ""; @DocFields );
updateTypes := "Text":"Number":"Time":"Delete Field";
updateType := @Prompt([OkCancelList]; "Select Type"; "Choose a type or action:"; "Text"; updateTypes );
@If(updateType = "Delete Field"; @Return(@SetField(targetField;@DeleteField)); "" );
newValue := @Prompt([OkCancelEdit];"New Value";"Enter the new value:"; @Text(@GetField(targetField )));
newTypeValue := @Select(@TextToNumber(@Replace(updateType; @Subset(updateTypes;3);"1":"2":"3" )); newValue; @TextToNumber(newValue); @TextToTime(newValue));
@SetField(targetField; newTypeValue)

I'm sure you can decipher the above, but here's what it does:

  1. Asks you which field you want to override. Type in a new one if you're adding a field. I've seen other versions where something like "---NEW---" is included in the field list prompt, and if you select that, it asks for the new field name... figured I might as well skip that extra step by just using [OkCancelEditCombo].
  2. Asks what data type the new value will be (text, number or time), with an option to delete the field instead - in which case the formula just deletes the field and exits.
  3. Asks what the new value will be, defaulting to the current value (if any).
  4. If the new value's data type is not text, converts the value to the selected type.
  5. Writes the new field value to the document.
This little button has saved me oodles of time over the years...

Cheap date

Tim Tripcony  

I just had an epiphany... or maybe just an apostrophe. I'm in the middle of some code that does some date matching (determines which document to process based on date information posted from a web application). I don't have control over the date format posted to the agent, and currently the format is [Month abbreviation][unpadded day number][day number suffix] - i.e., Apr15th. I already knew that passing "April 15th" to the constructor of a NotesDateTime object would create an instance representing [04/15/2008 12:00:00]. But did you know that "Apr15th" will too? As it turns out, it actually ignores the suffix: "Apr15bogus", for example, creates a date with the same properties. Always good to find these things out before I resort to string parsing...

Uptime

Tim Tripcony  

Yesterday (April 1... I know it's technically April 3 now, but for me it's still Wednesday) was mildly momentous. Although the following graph hasn't quite caught up yet (UPDATE: now it has), the server that hosts TimTripcony.com broke its uptime record:



As the graph indicates, the red X's (which hover, on average, closer to 0) are from the server's stint as a Windows box; the blue represent its shiny new Linux identity. Since the conversion in early December, it's only had one reboot... and that's only because we had a brief power outage (bad Timmy, no UPS). Other than replacing the hard drive (with one that was actually older than the Windows drive, just hadn't ever been used), the hardware is the same - same motherboard, processor, NIC, RAM, etc.... it just stays up now. No babysitting: no weekly defrag, no chkdsk to try to keep it from tanking, no weird behavior if I ignore a security update on a server that's only externally accessible on ports that I'm securing via Domino anyway... it just stays up. Is Linux administration "harder" than Windows administration? Sure... if, like me, you don't originally come from a UNIX background, there's a lot that may seem counter-intuitive about it. You may spend a bit more time on "the Google" looking for the exact syntax of various commands. But I'd personally rather support something I rarely have to touch - even if that occasional interaction isn't quite as intuitive as its Windows equivalent - than have to constantly babysit a system just to keep it operational.

Consuming web services in Notes and Domino 8

Tim Tripcony  

I haven't seen much discussion about how easy it really is to consume web services in Notes/Domino 8 (perhaps precisely because it is so easy), so I thought I'd outline exactly how one goes about providing and consuming services now.

Let's use a very basic example. Create a script library and toss this in the Declarations:

Public Class House
    Private currentColor As String
    Private currentValue As Long
    
    Public Sub buy (price As Long)
        Let Me.currentValue = price
    End Sub
    
    Public Function color() As String
        Let Me.color = Me.currentColor
    End Function
    
    Public Function value() As Long
        Let Me.value = Me.currentValue
    End Function
    
    Public Sub sell (price As Long)
        Let Me.currentValue = price
    End Sub
    
    Public Sub paint (newColor As String)
        Let Me.currentColor = newColor
    End Sub
End Class



Save that library, and create a web service. Include the library you just created via a Use statement in the Options, then in the properties of the service, specify House as the PortType class. The service doesn't need any code of its own, because it's aware of the House class via the Use statement and knows that's the class that defines the service methods.

Now it's time to consume the service. From within the service, click "Export WSDL", and save the file anywhere you can get to it later. In any Domino database (including, but not limited to, the database containing the web service), create a new script library (can be Java if you prefer, but in this example, we'll use LotusScript). At the bottom of the window you'll see a WSDL drop-down button. Click that and select "Import WSDL". It'll warn you that this will overwrite your script library (which is fine, since that's precisely what we want); click OK and select the file you just exported. Here's what you'll see:

%INCLUDE "lsxsd.lss"
Class House As PortTypeBase
    
    Sub NEW
        Call Service.Initialize ("UrnDefaultNamespaceHouseService", _
        "HouseService.Domino", "http://localhost", _
        "House")
        
    End Sub
    
    Sub BUY(PRICE As Long)
        Call Service.Invoke("BUY", PRICE)
    End Sub
    
    Function COLOR() As String
        Let COLOR = Service.Invoke("COLOR")
    End Function
    
    Function VALUE() As Long
        Let VALUE = Service.Invoke("VALUE")
    End Function
    
    Sub SELL(PRICE As Long)
        Call Service.Invoke("SELL", PRICE)
    End Sub
    
    Sub PAINT(NEWCOLOR As String)
        Call Service.Invoke("PAINT", NEWCOLOR)
    End Sub
    
End Class



Any of this seem familiar?

Only one change to the library is needed: replace the reference to "http://localhost" (in the Service.Initialize call) with "http://server/path/db.nsf/servicename?OpenWebService" (where server is the IP or DNS address of your Domino server, path/db.nsf is the full filepath of the database containing the service, and servicename is the name of the web service design element).

At this point, you can save the new library and include it anywhere you need to consume the service. And actually consuming it is this easy:

Dim myHouse As New House()
Call myHouse.buy(200000)
Call myHouse.paint("Purple")
Call myHouse.sell(5000000) 'Keep dreaming



But the real beauty of web services is that it allows you to execute functions against an application you know nothing about, whether it's inside your network or not. For example, if you download this file and import it into a script library in any of your applications, you can pull a random quote from my site as easy as this:

Dim Quotes As New Quotes()
Dim Quote As Quote
Set Quote = Quotes.getRandomQuote()
Msgbox Quote.Content & Chr(13) & "- " & Quote.Source



Tee hee

Tim Tripcony  

import lotus.domino.*;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
Session session = getSession();
AgentContext agentContext = session.getAgentContext();
DxlFactory.createAgent("James Bond", DxlAgent.LOTUSSCRIPT).addCodeEvent("Initialize", "Sub Initialize\n'Shaken, not stirred.\nEnd Sub").toDesignElement(agentContext.getCurrentDatabase());
} catch(Exception e) {
e.printStackTrace();
}
}
}

Disappointed in SearchDomino

Tim Tripcony  

Last month, Greyhawk  expressed frustration with a pattern he'd detected in the whitepaper emails they'd been sending, and unsubscribed from their distribution lists (as did many of us). Ed later took them to task on the same issue. I'm keeping them in my RSS strictly because I've also noticed a decline in the quality of tips submitted, and I would like to occasionally serve as some small bulwark against the dissemination of bad advice to noobs. Messiah complex? Yeah, maybe a little. Earlier this week, I spotted a real winner.

In a remarkable display of over-engineering (sorry, Christian), the tip linked to above demonstrates one approach to downloading the contents of a server's INI file. It uses approximately 220 lines of code (including comments and whitespace) to issue a remote console command and store the response in a text file... using Notes API calls to issue the command and Windows API calls to write the response to the file using Notepad. Assuming you're using one of the last three major releases of Notes/Domino, the same exact operation can be accomplished with 8 lines of code. And (gasp) it'll work on all supported platforms.

Sub downloadServerINI (Byval server As String, destination As String)
    Dim session As New NotesSession
    Dim stream As NotesStream
    Set stream = session.CreateStream()
    Call stream.Open(destination)
    Call stream.WriteText(session.SendConsoleCommand(server, "Show Config *"))
    Call stream.Close()
End Sub



I used their comment link (a mailto:... they haven't quite gotten around to implementing the comment feature you'll now find on almost any blog) to send Gervais a comment summarizing the above. He responded less than an hour later, thanking me for the feedback and indicating that he'd update the tip as soon as he got a chance. I just checked, and although it's been over two days since then, there's been no change... except that it now shows that, thus far, it's been rated a 5.00 out of 5.00. So anyone reading this tip that doesn't know any better might think this is the most elegant approach available. I rated it a 1, but apparently the average isn't real-time.

NULL and void

Tim Tripcony  

By now, we all know (hopefully) not to use GetNthDocument to loop through document collections, but here's something that often gets overlooked: don't use NULL in formula to determine if a field is blank. NULL doesn't exist.

In LotusScript, the keyword Null basically means "unknown" or "invalid". For example, ArrayGetIndex returns Null if the search value does not exist in the source array. You can manually assign Null to a variable - but only if it's a Variant - and then use IsNull to see if it's still Null, but I'd advise against that, since the only way a value can be Null is if you specifically tell it to be or assign it to an expression that returns Null. IsNull( "" ), for example, returns False.

In formula, although @IsNull does exist, NULL simply has no meaning. It's not a reserved keyword, so much like extended syntax in LotusScript, it doesn't know what it means, so it assumes you're referring to a field. Since there is no field (again, hopefully) named NULL, NULL returns "". So technically you get the same result, just more slowly.

This reminds me of trying to navigate one of those voice-activated menus when I try to call the customer service department of an ISP, utility company, insurance agent, etc. After trying unsuccessfully to determine how to contact an actual human, I'll just say "human". More often than not, the response is: "I'm sorry, I didn't understand you. I'll transfer you to a representative." The result was precisely what I wanted, it just took longer than it would have if I'd known to just push 0 at the start of the call.



In other words, the following formulae all return the same Boolean value:

Subject = NULL
Subject = ""
@IsNull(Subject)
@Length(Subject) = 0

The last is the most efficient, but arguably the least readable. I'd still recommend it, though, for use in column formulae for potentially large views and view selection for any view in a potentially large database. For performance purposes, hide-whens on forms/subforms should be kept to a minimum anyway, but the more you have the more noticeable it becomes if you're asking if some field value = NULL in each: Notes has to check each time whether a field named NULL exists, find out that it doesn't, evaluate NULL to "", and then do a string comparison between "" and the field you've specified. With @Length(FieldName) = 0, it's just checking the length of the field value, then comparing one number to another. Again, slightly less readable, but faster every time.

Go Go Gadget Replication

Tim Tripcony  

A friend of mine recently set up a Domino server at home running on Fedora, and is currently replicating a few databases with my personal server. He's locked out of the rest of the databases on my server, of course, so the connection document is set to only synch the ones he has access to. But in order to trigger a manual replication (outside of the schedule defined in the connection), he had to issue a separate Replicate command for each database to avoid getting errors about all the databases with shared replica IDs that his server can't access. He wanted a single command that would allow him to just type "go go gadget replication" and have it do everything he wanted, but only what he wanted. As it turns out, IBM posted instructions for this just last Friday.

If you issue a console command with the following syntax:
< "filename"
...Domino looks for the specified filename relative to the data directory, and treats each line in the specified file as a separate console command. So, for example, a file named "go go gadget replication" (no extension necessary) in the data directory containing:

Replicate Coyote/ACME names.nsf
Replicate Coyote/ACME mail/coyote.nsf
Replicate Roadrunner/ACME stuff.nsf
Broadcast "(!)Selective replication manually initiated." "Wile E Coyote/ACME"

Wile E can now issue the following console command (the < indicates the rest of the command is a filename):

< "go go gadget replication"

The server will kick off three replications, then pop up a message confirming the comands.

NOTE: on Linux, the file must be owned by notes:notes (or whatever user:group Domino is running under) in order for this to work.

Even Red Hat bleeds yellow

Tim Tripcony  

An announcement during Lotusphere that grabbed some media attention but didn't seem to even get mentioned at Lotusphere is that Red Hat is now offering a version of RHEL client and server that comes bundled with Notes and Domino, respectively. Our general manager is quoted in the BusinessWire press release, but it doesn't make it clear what (for us, at least) is so exciting about this: if you want to try this out but aren't comfortable flying solo on the initial setup, you call them and they send us. That's right: we'll come install the whole package for (and with) you to get you up and running. I'm a bit fuzzy on the exact details, to be honest, but if this sounds tempting to you, send an email to services@lotus911.com and we'll get you the whole scoop.

(cross-posted from TimTripcony.com)

Purty charts in Domino

Tim Tripcony  

If necessity is the mother of invention, boredom must be the weird aunt that has 87 cats and always smells like cheese.TM

I was going stir crazy here in my hotel room, so I started playing with the XML/SWF Charts library I stumbled upon the other night. Here's a sample of what I came up with. That's live data, generated by 11 lines of code (not counting variable declarations). The sample database contains the chart library (comprised of various SWF files stored as file resources), an example page to demonstrate the markup syntax for embedding the main SWF, and an agent to demonstrate generating the source XML. In this case it's navigating the referrers by date view in my blog. Nothing too fancy in this example, but this library allows all sorts of wacky output formats. Check out the gallery to see some of what it's capable of.

(cross-posted from TimTripcony.com)

Create, edit and delete without agents

Tim Tripcony  

I'm really becoming a broken record, eh? Or, for you kids in the audience, a degraded MP3 file. By request, I put together a full demonstration of how documents can be created, edited, and deleted using AJAX, all without the use of any agents. You can try it out here. And you can download the demo database here.

Previously, I described how documents can be created and edited in a Domino database without calling agents. This demo combines the two - as well as deletion of documents - using a simple contact manager as an example. The UI is very primitive, but hopefully it illustrates the premise.

In this example, contact information can be entered and then posted to the server without a page refresh. The function that posts the document (thereby creating a new record in the database) in turn calls the function that loads the view data (thereby displaying the newly created record). The new data could of course be inserted into the table immediately instead of reloading the view, but this approach was intended to illustrate how much faster document creation is when agents aren't involved. Reloading the view also ensures that the new record will already be sorted correctly when it's added to the table. In addition to document creation, each view row includes a link to allow editing and deletion of existing documents.

Please feel free to post any questions or suggestions you may have about this... I was planning to write up a far more detailed explanation of how all of this is tied together, but I'm suddenly very sleepy.

Oh, one thing I should mention: the reference to Event.onDOMReady at the end of ajaxeditor.js isn't part of the core prototype.js. It's an extension I found on Vivabit. This allows functions to be defined in external js files that would otherwise need to be called inline or via window.onload... for example, functions that you want to run immediately, but not until some DOM element that the function is going to muck with is actually ready to receive said mucking. The primary advantage of this extension is that it triggers the functions as soon as it is safe to do so, instead of waiting for the entire page to load (images, for example).

(cross-posted from TimTripcony.com)