Forms 'n' Views

  • Sep 10, 2012

One of my (many) for-fun projects lately has been a design-element editor named "Forms 'n' Views". Though it's not really release-quality, it's been coming together enough to toss it up on GitHub and make a post about it. Basically, it's meant to serve a couple purposes:

  • Help mitigate the "Windows problem" - when all you want to do is make a quick change to a legacy design element (say, a stylesheet), but doing so would mean firing up a Windows VM, launching Designer, and opening the DB. For quick edits, the amount of time that takes wildly eclipses (heh) the time spent actually coding.
  • Experiment with a data-focused way to edit forms and views. The legacy editors are geared towards a Notes client target, dedicating most of their space to aspects that have no meaning when the design element is intended just for backing an XPage.
  • Eventually, provide a way to grant "sudo"-style access to other users to modify certain design elements without granting full designer/manager access, setting up WebDAV, or exposing a poor web designer to Designer.
  • Let me experiment with a different setup for an XPages app - Forms 'n' Views consists of a Dojo BorderContainer- and TabContainer-based UI on a single XPage, which loads up tabs containing custom controls on the fly. None of it is unexplored territory, but it's good to write different kinds of apps sometimes.
  • Let me try out some more backing-class architectures. Each editor is backed by a DOM representation of the exported DXL, which is manipulated through wrapper classes and then re-imported back directly. Things like getting lists of columns are done via XPath queries without storing the data redundantly.

In its current incarnation, it's taken on the pale complexion of Dojo's "claro" theme - I've been going back and forth between that and the Notes-like "soria" theme:

Forms 'n' Views

It should do for now, but I'll likely fiddle with it more to make it look better.

In any event, this has been a fun project so far, and hopefully it'll shape up into a useful tool.

Building XPages servlets with FacesContext access

  • Sep 6, 2012

I have a confession to make: I'm not crazy about XAgents. Don't get me wrong - they do everything they're supposed to and do it well. However, it's always kind of bothered me that you take a visual design element like an XPage and turn off all the higher levels to get back down to the core servlet. Plus, it muddies the list of XPages in the DB - some are actual XPages, some are just wrappers for scripts. So my objection is essentially pedantry.

However, the fact that my objection is wildly exaggerated has never stopped me from sinking a lot of time into finding the "right" answer before, and it hasn't stopped me now. When what I want to build is, say, an Excel exporter for a particular type of document in a reporting DB, what I really want is a basic servlet that exists only in the database and has access to the surrounding XSP environment and custom classes. Fortunately, between a post by Sven Hasselbach, the referenced Chinese-language developerWorks article, the XSP Starter Kit, and a bit about facesContext.release() on a JSF-focused article and reinforced by the FacesContextServlet class from the ExtLib, I made it work.

The developerWorks article covers the bulk of the work - creating the Factory, setting up the file in META-INF, etc.. After that, you can set up your servlets by extending (using the Starter Kit method) DesignerFacesServlet. It turns out that the important thing to remember is to close out your context when you're done - I ran into a lot of trouble from not doing this, which would break XPages visited after the servlet. As an example, here's the test class I ended up building while figuring out how to make it not blow up my application:

import java.io.*;
import java.util.*;
import com.ibm.commons.util.StringUtil;
import com.ibm.xsp.webapp.DesignerFacesServlet;
import javax.faces.context.FacesContext;
import javax.servlet.*;
import javax.servlet.http.*;

public class TestServlet extends DesignerFacesServlet implements Serializable {
	private static final long serialVersionUID = -1152176824225969420L;

	@SuppressWarnings("unchecked")
	@Override
	public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
		// Set up handy environment variables
		HttpServletRequest req = (HttpServletRequest)servletRequest;
		HttpServletResponse res = (HttpServletResponse)servletResponse;
		ServletOutputStream out = res.getOutputStream();
		FacesContext facesContext = this.getFacesContext(req, res);

		try {
			res.setContentType("text/plain");

			out.println("start");

			// The sessionScope is available via the ExternalContext. Resolving the variable
			//	would work as well
			Map<Object, Object> sessionScope = facesContext.getExternalContext().getSessionMap();
			sessionScope.put("counter", sessionScope.containsKey("counter") ? (Integer)sessionScope.get("counter") + 1 : 1);
			out.println("Counter: " + sessionScope.get("counter"));

			// A query string map is available via the request. This method, as opposed to
			// 	getting the "param" variable, returns arrays of strings, allowing things like
			//	"?foo=bar&foo=baz" properly
			Map<String, String[]> param = req.getParameterMap();
			for(String key : param.keySet()) {
				out.println(key + " => " + StringUtil.concatStrings(param.get(key), ';', false));
			}

			out.println("done");

		} catch(Exception e) {
			e.printStackTrace(new PrintStream(out));
		} finally {
			out.close();

			// It shouldn't be null if things are going well, but a check never hurt
			if(facesContext != null) {
				facesContext.responseComplete();
				facesContext.release();
			}
		}
	}
}

I set up my ServletFactory to use this one for "/test", and so it's available via a URL like "/database.nsf/xsp/test?foo=bar". As with an XPage, you can also chain more path bits on after the servlet name, like "/database.nsf/xsp/test/some/other/stuff" and get to that via req.getPathInfo() - though with the caveat that, unlike with an XPage, the servlet path is included in the path info, so it would return "/xsp/test/some/other/stuff".

So long as I don't run into any other app-exploding problems, I plan to go this route for non-UI requests like exports and actions. When I have a use for it, I'll also go down the related path of writing custom services, for which the ExtLib provides an extensive foundation.

A Custom Control for dojox.widget.Toaster

  • Sep 5, 2012

Update: Looks like Chris Toohey beat me to this by a couple months: http://www.dominoguru.com/pages/04092012113950.html

Marky Roden did a presentation at MWLUG that included, among other things, a demonstration of Pines Notify, a jQuery plugin that provides Growl-style notifications - something that could come in tremendously handy in a lot of situations.

I wanted to use something like this, but I decided to check to see if there's an equivalent in Dojo already, so I don't have to start including jQuery as well. Fortunately, there is... ish. dojox.widget.Toaster serves a similar purpose, though it takes its design cues from the little "toaster" notifications common on Windows. However, their default appearance is the worst thing in the entire world:

GAH MY EYES

Fortunately, that can be cleaned up with a little CSS to be a crummy version of Growl:

A little better

With a bit more work and attention to detail, it could end up pretty classy. I think it's still not as featureful as Pines Notify (no "clickthrough" options, its handling of multiple messages with different timeouts is not nuanced, etc.), but it's serviceable.

The API to use this widget is actually pretty straightforward: you run a bit of code at page load to create a toaster object to listen to a specific channel name (e.g. "/save/success") and then publish messages whenever you want something to appear. The code for sending a message is pretty simple, and there are two forms, depending on whether or not you want to include additional parameters:

dojo.publish("/test/channel", ["This is message one, lasting five seconds"])
dojo.publish("/test/channel", [{ message: "This is message two, lasting 10 seconds", duration: 10000 }])

I decided to wrap the instantiation and required resources up into a custom control and make another demo DB:

http://frostillic.us/tests/djToaster.nsf

The "toaster.css" page is the CSS I wrote to override the standard behavior. The margins are intended for the top-right positioning, but could be changed readily to equivalents for other corners.

The control exposes five properties: messageTopic (the channel name, required), defaultType ("message", "error", etc.), duration (in milliseconds), positionDirection (the position and slide direction, defaulting to up from the bottom right), and separator (the text that appears between two simultaneous messages).

Quick-and-Dirty CKEditor Toolbar Setup for XPages

  • Sep 4, 2012

Update: Looks like I was late to the game on this as well: http://www.intec.co.uk/xpages-8-5-2-rich-text-extending-the-ckeditor/

A while back, I got annoyed by the lack of a "Source" button in the default rich text editor in XPages. After a bit of digging, I found that there is indeed one (not to mention a whole world of CKEditor toolbar plugins and enhancements) - just not by default. I think there's a way you can pick from a couple named "stock" toolbar layouts, but I ended up going the route of defining my own, naming each button in a group.

In CKEditor, at least pre-3.6 (and I guess it works later too), toolbars can be defined inline via a JSON array-of-arrays in the "toolbar" property of the editor. A hopelessly simple toolbar definition would look something like this:

[
	["Bold", "Italic", "Underline", "Strike", "-", "TextColor", "BGColor" ],
	["Indent", "Outdent"]
]

That would define two button groups with the named controls present. The "-" is a way to manually place a separator - think of them like non-breaking spaces: they look the same as the divider between groups, but will not cause the group to be split onto a second line if the toolbar is too wide for a single row.

For my purposes, I went a bit nuts:

[
	["Format", "Font", "FontSize"],
	["Bold", "Italic", "Underline", "Strike", "-", "TextColor", "BGColor", "-", "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock", "NumberedList", "-", "BulletedList"],
	["Indent", "Outdent"],
	["Subscript", "Superscript"],
	["RemoveFormat", "-", "MenuPaste", "-", "Undo", "Redo", "Find", "LotusSpellChecker", "-", "Image", "Table", "Link", "Flash", "-", "PageBreak", "HorizontalRule", "SpecialChar", "Blockquote", "Smiley", "ShowBlocks"],
	["Maximize", "Source"]
]

Inside an XPage, you can set this using the "attrs" property in 8.5.3 - though "dojoAttributes" would probably work fine in 8.5.3 and earlier as well:

<xp:inputRichText value="#{doc.Body}">
	<xp:this.attrs>
		<xp:attr name="toolbar"><xp:this.value><![CDATA[
			[
				["Format", "Font", "FontSize"],
				["Bold", "Italic", "Underline", "Strike", "-", "TextColor", "BGColor", "-", "JustifyLeft", "JustifyCenter", "JustifyRight", "JustifyBlock", "NumberedList", "-", "BulletedList"],
				["Indent", "Outdent"],
				["Subscript", "Superscript"],
				["RemoveFormat", "-", "MenuPaste", "-", "Undo", "Redo", "Find", "LotusSpellChecker", "-", "Image", "Table", "Link", "Flash", "-", "PageBreak", "HorizontalRule", "SpecialChar", "Blockquote", "Smiley", "ShowBlocks"],
				["Maximize", "Source"]
			]
		]]></xp:this.value></xp:attr>
	</xp:this.attrs>
</xp:inputRichText>

It looks like the syntax changed for 3.6, but not too much.

If you want to look into more specifics, you can take a look at the examples and un-minified code in (data)/domino/html/ckeditor/_samples and _source.

A Couple Updates to My Domino-One-Offs Repository

  • Sep 4, 2012

I realized earlier that I let a couple of the classes in Domino-One-Offs stagnate relative to the versions I currently use. Since originally posting my convenience XML library and, more usefully, the DynamicViewCustomizer, I've tweaked both in my various projects to add more features I ended up needing.

The DynamicViewCustomizer has gone further down the path of Notes-client fidelity at the expense of a bit more overhead, doing things like setting column widths (except where the column is set to extend to the window width, either specifically or by virtue of being the last column, as appropriate). It's still not perfect, as I ran into a bit of trouble with, I think, HTML in category columns (and you can see commented-out remnants of various other changes), but it's potentially useful.

For my "man, I hate dealing with all these stupid orthogonal Java XML libraries" XML library, I've added a number of functions to support my Forms 'n' Views app (coming Soon™), namely better attribute handling, a simple NodeList wrapper (that actually acts like a List), and a basic getXml() method for when you just want a string version.

Finally, I tossed in the current version of my SortableMapView class, meant to gobble up Views or ViewEntryCollections and make them sortable by all columns. That one uses Lombok, but I think you could just remove the bits about making it List-compatible and still be fine.