The Beginnings
15 June 99
contact
Dialogs are alive and somewhat well in Mozilla, and this document attempts to explain their current status.
Change history
1 Dec 99 - This document is dead. Long live the windows document.
15 June 99 - Documented the C++ version of window.openDialog.
17 May 99 - Documented alternative JavaScript methods for opening dialogs, using window.open and window.openDialog, rather than Toolkitcore. Updated API descriptions.
1 Apr 99 - Updated the examples to mention window titles and sizing, and resisted documenting the new AI features that I didn't check in.
15 Mar 99 - Changed name of onconstruction
tag
to onload
.
2 Mar 99 - Updated use of namespaces in the examples..
25 Feb 99 - More careful about case sensitivity in the examples. Updated example of creating a dialog from C++.
24 Feb 99 - More careful about namespaces in the examples. Added a note about XUL Document method getElementByID
23 Feb 99 - AppCoresManager name changed to XPAppCoresManager
18 Feb 99 - Updated to new command architecture and initialization code.
17 Feb 99 - Revised JavaScript for debriefing dialog elements at dismissal time. Added note about DOM interfaces available to XUL widgets (also in the Examples section).
16 Feb 99 - Revised JavaScript for initializing dialog elements at window load time
This document is not intended to be a comprehensive syntax or reference manual. With care and time, it should grow into a useful starting place for making windows and writing dialogs. The author thinks that feedback on what's missing from the document would help build a better document, so the quality of this thing is in the reader's hands. Keep that in mind while you're cursing me.
In the meantime, this document also serves as a roadmap of XPFE's dialog story. Interested parties would please review it to determine whether the package we plan to deliver will meet their needs.
Current XPFE design goals state that dialogs get no special treatment; they're just windows like any other. Windows can have parent windows, and be displayed modally, and have control callbacks, and so behave like dialogs. But our goal is that the same application code that instantiates a browser window will serve equally to instantiate a dialog window. The difference lies in the window content, and a couple of parameters controlling modality and that sort of thing.
Currently, all window control mechanisms flow through JavaScript. That is, any capabilities a window may have besides taking up space on the desktop are specified in the XUL describing the window, and the binding between the window content and the application is done through JavaScript in the XUL. That binding can be very short: it can fall through to application C code very quickly, but JavaScript will be part of the process. (Note that we have not yet run into any threading problems with this model, but we kind of expect to.)
JavaScript is easily added to a window: just include it in the XUL window description and follow a short but peculiar dance to hook it up. Hooking up C code involves writing a C++/JavaScript interface, and calling it from JavaScript. That interface is an AppCore, and there's a separate and much more impressive document describing them and explaining how to build them.
So, there are two ways to make a window or dialog: directly through C++ and
from JavaScript. The resulting window is an instance of nsWebShellWindow
.
This means it currently contains a hulking bunch of hackery for hooking up browser-window
specific contents. This causes no harm, and will be cleaned up in the future.
Note that modal dialogs or alerts are currently working only on Windows and the Macintosh, and even then, only for modal dialogs created on the UI thread. That is to say, they're only halfway there. Also note that windows are not sized to content. These are merely somewhat glorified browser windows, to which automatically sizing themselves to match their content is something of an eyebrow-furrowing concept. Specifying the size of a dialog is another future enhancement.
window.open
will open a XUL window if given a XUL URL to load.
It will open the given URL as the contents of a browser window, unless the chrome
feature is specified in window.open
's third ("features")
parameter. Mozilla supports an extended form of window.open
which
can be used to pass parameters back and forth to the dialog window. It's named
window.openDialog
, reasonably enough, for now. Both window.openDialog
and chrome
are described in JavaScript
Extensions.
Another JavaScript interface is a Mozilla AppCore named ToolkitCore
.
It contains a handful of windowing utility functions which were more useful
before window.open
was working properly. ToolkitCore
will probably be deprecated soon.
function MakeDialog() {
var newWindow = window.open("resource://res/samples/madedialog.xul",
"itsname", "chrome");
}
or, alternatively for now,
function MakeDialog() {
var toolkitCore = XPAppCoresManager.Find("ToolkitCore");
if (!toolkitCore) {
toolkitCore = new ToolkitCore();
if (toolkitCore)
toolkitCore.Init("ToolkitCore");
}
if (toolkitCore)
toolkitCore.ShowWindow("resource://res/samples/madedialog.xul",
window);
}
An XPAppCoresManager
is pre-constructed; you can access it without
any more preparation than shown above. ToolkitCore
is not; it requires
that small bit of preparation.
The primary C++ interface is nsIAppShellService::CreateTopLevelWindow()
.
It's currently very clunky and will change (you'll see that statement many times
in this document). But at time of writing, you create a new window like this:
nsresult rv;
nsIAppShellService *appShell;
nsIURL *url;
nsIWebShellWindow *parent, *window;
nsIStreamObserver *observer;
nsIXULWindowCallbacks *callbacks;
PRInt32 width, height;
window = nsnull;
rv = nsServiceManager::GetService(kAppShellServiceCID,
kIAppShellServiceIID,
(nsISupports**) &appShell);
if (NS_SUCCEEDED(rv)) {
appShell->CreateTopLevelWindow(parent, url, PR_TRUE,
window, observer, callbacks, width, height);
nsServiceManager::ReleaseService(kAppShellServiceCID,
appShell);
}
At time of writing, only url
, showWindow
, window
,
aInitialWidth
and aInitialHeight
are actually used.
url
is an URL describing the contents of the window. window
is returned from the function, set to the created window. See the code for descriptions
of the other parameters. This interface is a little rife with silly parameters,
and is slowly being cleaned up.
There is another very similar method nsIAppShellService::CreateDialogWindow()
.
It takes the same parameters and currently functions exactly the same. These
two methods are planned to differ only in the kind of border used on the window
created. At time of writing, this decision is made at the time the window is
created, before any instructions in the file behind the URL could be read and
used to help make the decision.
At this time, window descriptions must be loaded from an URL. There will additionally be methods for loading windows from a stream. In fact, XPFE want to say the stream will be the preferred method, though the URL method will probably remain. Streams will disengage the toolkit further from the source of the window description, and will be our method for creating windows whose XUL is calculated at runtime, rather than being distributed as a file.
The other C++ interface is nsIDOMWindow::OpenDialog
. It has the
advantage that it behaves exactly like the JavaScript window.openDialog
function, being the same code used by that function, after all.
The close association between this function and JavaScript places constraints on its usage. Any parameters sent to this function must be ready for use in JavaScript handlers built into the new window, and so must be fitted into JavaScript wrappers. There must also be a "parent" window available to do the window opening and provide a JavaScript context.
The function to wrap a series of arguments is called JS_PushArguments
,
and is defined in jsapi.h
. Its mysterious third argument is explained
in that same header file. The sample code below will call OpenDialog
,
given an nsIDOMWindow
to work with. The arguments to OpenDialog
are exactly those from its JavaScript definition, explained in JavaScript
Extensions. The first three are standard Open
arguments. The
remainder are parameters for the new window. That new window will have a property
named arguments
. It's an array, and arguments[0]
is
the string "I was born a C string", while arguments[1]
is the number 298.
DoOpenDialog(nsIDOMWindow* aParent) {
if (!aParent)
return NS_ERROR_NULL_POINTER;
nsresult rv = NS_ERROR_FAILURE;
JSContext *jscx = 0;
nsIDOMWindow *newWindow;
// get the parent window's JS Context (this is not really a blessed thing
// to do, but I believe it's all we have. it wants to be a real interface
// someday.)
nsCOMPtr<nsIScriptGlobalObject> scriptGlobalObj = do_QueryInterface(aParent);
if (scriptGlobalObj) {
nsCOMPtr<nsIScriptContext> scriptcx;
scriptGlobalObj->GetContext(getter_AddRefs(scriptcx));
if (scriptcx)
jscx = (JSContext *) scriptcx->GetNativeContext();
}
// if everything went as planned...
if (jscx) {
// build a JavaScript "arguments" array for the OpenDialog function
void *mark;
jsval *argv;
argv = JS_PushArguments(jscx, &mark, "ssssu",
"resource://res/samples/paramdialog.xul",
"windowName",
"chrome",
"I was born a C string",
(uint32) 298);
// if that worked, call the function.
if (argv) {
aParent->OpenDialog(jscx, argv, 5, &newWindow);
JS_PopArguments(jscx, mark);
rv = NS_OK;
}
}
return rv;
}
Between making and showing the window, there is a callback. The resulting nsWebShellWindow
comes with an associated DOM content model. Any initial control settings or
content changes which must be made before the window is actually shown can be
done at the time of this callback.
This callback is any JavaScript specified as the value of the onload
attribute of the <window>
element. In the example below,
the window start tag is declared
<window ... onload="Startup()">
During the onload
callback, dialog authors are free to
alter control settings from JavaScript:
function Startup() {
// yellowize and check the "ow" button
document.bgColor = "yellow";
var checkbox = document.getElementByID("ow");
if (checkbox)
checkbox.checked = true;
}
Except that not all properties are hooked up yet. The change to background color in the example won't do anything. But the important example does work!
Dialog preprocessing can also be done in C by defining an AppCore, loading
and calling it from the Startup()
method, as the ToolkitCore
AppCore was called to create the window.
After the dialog has run -- a dismissal button has been clicked, say -- dialog users will need a callback in which to query the current control settings. As always, this will be done by walking the DOM content model, and accessed through a JavaScript hook.
We are uncertain whether a specific callback need be added: the event handler for the dismissal buttons may be entirely sufficient. But as the reader has no doubt guessed, there is no specific debriefing hook implemented at this time.
Data can be passed back to the calling window in more than one way. The dialog
window and opener can agree to share information as properties added to the
JavaScript dialog window object. Alternatively, the two windows could agree
that the dialog window will call some function of its opener. A third way could
be for the opener to use window.openDialog
to pass parameters by
reference to the dialog window. Any changes made by the dialog to these parameters
would be visible in the opener window. See JavaScript
Extensions or the examples for details. Two files
in the source code at mozilla/xpfe/browser/samples/dexparam*.xul
are a kind of test suite for parameter passing, and consequently spell out more
details than will this document.
Note at time of writing, a dialog window, once closed, cannot be successfully
accessed from JavaScript through the variable which was returned by window.open
.
This is a known bug, and wants fixing.
The following XUL describes a browser window with a simple toolbar containing
a single button which will open another window: a nonmodal dialog, with a bit
of imagination. It uses the extended window.open
syntax to pass
parameters to the dialog window. The dialog window uses these parameters to
initialize its settings. It then passes the state of its checkbox back to the
caller, in that same parameter.
<?xml version="1.0"?>
<?xml-stylesheet href="xul.css" type="text/css"?>
<!DOCTYPE window>
<!-- Simple sample interface for bringing up a nonmodal dialog -->
<xul:window
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul ="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
title = "Dialog creation sample">
<html:script>
<![CDATA[
var dialogWindow = null;
var ioSettings = new Boolean();
// create a newdialog window, passing a couple of named parameters
function MakeParamDialog() {
var newWin = window.openDialog("resource://res/samples/madedialog.xul",
"New", "chrome",
{remind:true, prompt:"Give me your money and convertible bonds"});
return newWin;
}
// cheesy demonstration that values are being returned
function DumpObject(what) {
dump("dumping object: " + what + "\n");
for (prop in what)
dump(" property '" + prop + "' = '" + what[prop] + "'\n");
dump("dumped.\n");
}
]]>
</html:script>
<xul:toolbox>
<xul:toolbar>
<xul:titledbutton
value="Make Dialog" onclick="dialogWindow=MakeParamDialog()"
style="background-color:rgb(192,192,192);"/>
<xul:titledbutton
value="Dump Window"
onclick="DumpObject(dialogWindow.arguments[0])"
style="background-color:rgb(192,192,192);"/>
</xul:toolbar>
</xul:toolbox>
</xul:window>
Note that only html widgets will have the expected html-specific DOM attributes
like "checked" for checkboxes. XUL buttons will respond to their own
APIs (to be documented in separate, dedicated papers), as well as to the core
DOM interfaces. Generally, as in titledbutton
above, attribute
names match their HTML counterparts. But see specific widget documentation to
be certain.
The above code will produce a live window with a functional button if placed
in a file named, perhaps, makedialog.xul
in the res/samples
subdirectory within the directory containing apprunner
. (Placing
it in that directory allows it to find the stylesheet xul.css
mentioned
in the stylesheet processing instruction.) Launch apprunner
pointing
at that URL to load it into the main window.
apprunner -url resource:/res/samples/makedialog.xul
A suitable dialog description XUL file (named madedialog.xul
in
the above code) will be loaded when the Make Dialog
button is pressed.
The following example contains initialization code as mentioned in this document.
The authors have turned a jaundiced eye toward any sense of aesthetics in the design of this dialog. It's ugly. It will show up directly on top of the main "browser" window. I plan to rely on the estimable work of a colleague to provide some documentation for what sorts of visual effects can be accomplished with dialog controls and provide a link to a separate document with prettier samples.
<?xml version="1.0"?>
<?xml-stylesheet href="xul.css" type="text/css"?>
<!DOCTYPE window>
<!-- dialog containing a control requiring initial setup -->
<xul:window
xmlns:html="http://www.w3.org/1999/xhtml"
xmlns:xul ="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
onload = "SetFromParams()"
title = "Things to do"
height = "200" width = "300">
<html:script>
<![CDATA[
// Initialize controls from parameters sent through openDialog
function SetFromParams() {
// look in arguments[0] for interesting properties and values
// set checkbox from that, if present
if (window.arguments && window.arguments[0]) {
var setting;
var control;
// set checkbox from the value of argment[0]'s "value" property
if (window.arguments[0].remind) {
setting = window.arguments[0].remind;
control = document.getElementById("remind");
if (control)
if (typeof setting == "boolean")
control.checked = setting;
else if (typeof setting == "object")
control.checked = setting.valueOf();
}
// set prompt from the value of argment[0]'s "prompt" property
if (window.arguments[0].prompt) {
setting = window.arguments[0].prompt;
if (typeof setting == "string") {
control = document.getElementById("prompt");
if (control) {
control = control.firstChild;
if (control && control.nodeType == 3) // TEXT_NODE
control.data = setting;
}
}
}
}
}
// OK button handler
// return the setting of the "remind" checkbox in the arguments
// and then close the window (disabled for now, since that crashes)
function DoOK() {
var checkbox = document.getElementById("remind");
if (checkbox) {
// if we were given an openDialog parameter, set its value
if (window.arguments && window.arguments[0])
window.arguments[0].remind = checkbox.checked;
}
// window.close(); (crashes, at time of writing)
}
]]>
</html:script>
<html:table>
<html:tr>
<html:td html:id="prompt">Give me your money</html:td>
</html:tr>
<html:tr>
<html:td>
<!-- note the html namespace on the id attribute, which
seems at this time to be required by getAttribute() -->
<html:input type="checkbox" html:id="remind"/>Remind me
</html:td>
</html:tr>
<html:tr>
<html:td>
<html:button onclick="DoOK()">OK</html:button>
</html:td>
</html:tr>
</html:table>
</xul:window>
Note the window will not be dismissed when the OK button is pressed. The current state of the code will require that you hit "OK" and then the window close box.