Debugging Tacos
Apart from actually setting up your tacos installation, debugging will probably be one of the first things you'll need to get used to in order to efficiently understand what's going on in your particular scenerio. Combined with the existing mechanisms available through Tapestry/Hivemind/dojo, Tacos has built a considerable amount of infrastructure/tools to help the debugging process.
JavaScript Logging
Most people should already be familiar with server side logging, such as the use of log4j/others to do common log operations via Logger.debug()/warn()/etc..Dojo has a similar concept built into it's core that uses a client side object to do this. Tacos has built on and continued the use of this concept so that debugging statements are available to trace almost every single operation and the values they work with on the client side.
To enable debugging you have to set the global dojo configuration variable, isDebug:true. This will turn on tacos and dojo client side debugging statements. One such statement might look like this.
function clientSideValidate(value) { dojo.debug("Received incoming value of " + value); //do stuff with value if (error) { alert("The value you input wasn't valid " + value); } }
You can add even more debugging statements to your own html pages/javascript via the use of these dojo.debug/dojo.info debugging statements.
Debug Console
Add a tacos:DebugConsole in your page and you're done (to improve presentation, you might like to include the stylesheets that follow).
Debug Console (old and manual way)
After witnessing a couple of tacos async io request debug statements it will quickly become apparent that there may be a little too much information being dumped out onto your page. This is why the debug console was developed for tacos.
Installing
Like other examples, this one will build upon previous concepts of having one main Border component that all of your tapestry pages is wrapped by. Even if you use a different method, this setup assumes that you have control over the head/end of your html pages through some mechanism. Below is the snippet of html used in the tacos demo application to get the initial console setup.
<html jwcid="shell" > <script type="text/javascript"> djConfig = { isDebug: true, baseRelativePath: "js/dojo/", preventBackButtonFix: false }; </script> <div id="debugContainer" style="display:none;"> <div id="dojoDebug">This console displays your debug information</div> </div> <script type="text/javascript" src="js/dojo/dojo.js"></script> <body jwcid="@Body" > <!-- The actual floating console that will contain our debug statements --> <span jwcid="debugconsole@tacos:FloatingPane" hasToolbar="false" title="Debug Console" minHeight="22" style="z-index:2000;background:#efefef;position:absolute;top:40px;right:20px;margin:0px;padding:0px;width:500px;height:300px;"> <div dojoType="LayoutPane" layoutAlign="top" id="debugNav" > <a href="javascript:tacos.noReturn();" id="dclear">Clear</a> <a href="javascript:tacos.noReturn();" id="dhide">Hide</a> <a href="javascript:tacos.noReturn();" id="dshow">Show</a> </div> </span> //Lots of body content here <!-- Very end of the page, at the bottom of the Border component. --> <script type="text/javascript"> try { if (djConfig["isDebug"]) { dojo.event.connect(window, "onload", function(e) { dojo.event.connect(dojo.byId("dclear"), "onclick", function(evt) { dojo.dom.removeChildren(dojo.byId("dojoDebug")); }); dojo.event.connect(dojo.byId("dhide"), "onclick", function(evt) { dojo.byId("dojoDebug").style.display = "none"; dojo.byId("dhide").style.display = "none"; dojo.byId("dshow").style.display = "inline"; if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").resizeTo( dojo.style.getOuterWidth( dojo.byId("debugconsoleContentId")), 22); dojo.widget.byId("debugconsole").onResized(); } dojo.io.cookie.setCookie("dcdisplay", "none"); }); dojo.event.connect(dojo.byId("dshow"), "onclick", function(evt) { dojo.byId("dojoDebug").style.display = ""; dojo.byId("dshow").style.display = "none"; dojo.byId("dhide").style.display = ""; if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").resizeTo( dojo.style.getOuterWidth( dojo.byId("debugconsoleContentId")), 300); dojo.widget.byId("debugconsole").onResized(); } dojo.io.cookie.setCookie("dcdisplay", ""); }); var dd = dojo.io.cookie.getCookie("dcdisplay"); dojo.debug("dcdisplay cookie found with value:" + dd); if (dd && dd == "none") { dojo.debug("Found dcdisplay cookie, sizing console to 22"); dojo.byId("dojoDebug").style.display = "none"; dojo.byId("dshow").style.display = "inline"; dojo.byId("dhide").style.display = "none"; if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").resizeTo( dojo.style.getOuterWidth( dojo.byId("debugconsoleContentId")), 22); } } else dojo.byId("dojoDebug").style.display = ""; dconsole = dojo.byId("debugconsoleContentId"); if (dconsole) dconsole.style.zIndex = "2000"; if (djConfig["isDebug"]) { dojo.dom.moveChildren(dojo.byId("debugContainer"), dojo.byId("debugconsoleContentId"), false); } else { dojo.dom.removeNode(dojo.byId("debugContainer")); } if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").onResized(); } dojo.debug("Completed window.onload setup..."); }); } } catch (e) { dojo.debug("ERROR " + e); } </script>
That is quite a bit to digest all at once! The following sections will hopefully clarify what is going on here.
debugContainer
The djConfig variable setup is already explained in the Setup section so we will skip over that here.
<div id="debugContainer" style="display:none;"> <div id="dojoDebug">This console displays your debug information</div> </div>
This is the target node that dojo will write to by default. We put it at the very top, before the dojo javascript includes to ensure that no spurious writes ocurr before we've had a chance to capture them. The alternative is that you will see a few debug statements written to the top of your pages.
debugConsole
The debug console area defines a dojo FloatingPane widget. The basic premise is that we want all of the debug statements to appear inside of this floating window on the screen that we can control via various link actions.
<span jwcid="debugconsole@tacos:FloatingPane" hasToolbar="false" title="Debug Console" minHeight="22" style="z-index:2000;background:#efefef;position:absolute;top:40px;right:20px;margin:0px;padding:0px;width:500px;height:300px;"> <div dojoType="LayoutPane" layoutAlign="top" id="debugNav" > <a href="javascript:tacos.noReturn();" id="dclear">Clear</a> <a href="javascript:tacos.noReturn();" id="dhide">Hide</a> <a href="javascript:tacos.noReturn();" id="dshow">Show</a> </div> </span>
Most of the parameters to this component are dojo specific, but there are a few things that aren't obvious from looking at the code. The main non-obvious part is that the unique node id of the area inside the FloatingPane that will render content isn't debugconsole, but is more of a conglomeration of the component id plus ContentId. So, in this instance, the actual unique node id that content will be written to in this floating pane is debugconsoleContentId. This will become important later when we go over the javascript used to control some of the console actions.
If we did nothing else here besides the javascript then the console being displayed probably wouldn't look very pretty. Here is the css we use to style the console in the tacos demo. I will let you sort out which css attributes apply to which sections of the console.
#debugNav { display: block; background: #222; overflow: hidden; } #debugNav a { color: #fff; margin-right: 15px; font-weight: bold; text-decoration: none; margin-left: 6px; } #debugNav a:hover { color: #fc0; text-decoration: underline; } .tacosFloatingPaneToolBar { background: transparent; overflow: hidden; position: absolute; width:95%; text-align:right; font-weight:bold; } .tacosFloatingPaneContent { height: 100%; width: 100%; margin: 0px; padding: 0px; overflow: auto; } #dshow { display: none; } #debugconsoleContentId { background:#000000; height: 100%; width: 100%; margin: 0px; padding: 0px; color: #33FF00; overflow: auto; } DIV.dojoFloatingPaneTitle { color: #fff; } .dojoFloatingPane { border: 1px solid #aaa; background-color: #222; } .dojoFloatingPaneDragbar { color: #000; } DIV.dojoFloatingPaneClient { margin-top: 0px; margin-bottom: 0px; padding: 0px; border: none; }
JavaScript Manipulation
Last but not least is the javascript code required to manipulate some of the console settings and provide additional functionality. Like previously stated, it is planned that in the next release all of this functionality will be provided by a drop in component like all the other tacos components, this one is just a little hard to write the standard way without providing a real dojo widget implementation.
<script type="text/javascript"> try { if (djConfig["isDebug"]) { dojo.event.connect(window, "onload", function(e) { dojo.event.connect(dojo.byId("dclear"), "onclick", function(evt) { dojo.dom.removeChildren(dojo.byId("dojoDebug")); }); dojo.event.connect(dojo.byId("dhide"), "onclick", function(evt) { dojo.byId("dojoDebug").style.display = "none"; dojo.byId("dhide").style.display = "none"; dojo.byId("dshow").style.display = "inline"; if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").resizeTo( dojo.style.getOuterWidth( dojo.byId("debugconsoleContentId")), 22); dojo.widget.byId("debugconsole").onResized(); } dojo.io.cookie.setCookie("dcdisplay", "none"); }); dojo.event.connect(dojo.byId("dshow"), "onclick", function(evt) { dojo.byId("dojoDebug").style.display = ""; dojo.byId("dshow").style.display = "none"; dojo.byId("dhide").style.display = ""; if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").resizeTo( dojo.style.getOuterWidth( dojo.byId("debugconsoleContentId")), 300); dojo.widget.byId("debugconsole").onResized(); } dojo.io.cookie.setCookie("dcdisplay", ""); }); var dd = dojo.io.cookie.getCookie("dcdisplay"); dojo.debug("dcdisplay cookie found with value:" + dd); if (dd && dd == "none") { dojo.debug("Found dcdisplay cookie, sizing console to 22"); dojo.byId("dojoDebug").style.display = "none"; dojo.byId("dshow").style.display = "inline"; dojo.byId("dhide").style.display = "none"; if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").resizeTo( dojo.style.getOuterWidth( dojo.byId("debugconsoleContentId")), 22); } } else dojo.byId("dojoDebug").style.display = ""; dconsole = dojo.byId("debugconsoleContentId"); if (dconsole) dconsole.style.zIndex = "2000"; if (djConfig["isDebug"]) { dojo.dom.moveChildren(dojo.byId("debugContainer"), dojo.byId("debugconsoleContentId"), false); } else { dojo.dom.removeNode(dojo.byId("debugContainer")); } if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").onResized(); } dojo.debug("Completed window.onload setup..."); }); } } catch (e) { dojo.debug("ERROR " + e); } </script>
This block could probably technically go anywhere you want in the page, but IE occassionaly has problems with code referencing dom nodes that don't exist yet, so it's probably safest to put it at the very bottom of your page.
if (djConfig["isDebug"]) { ... }
This section checks if we've enabled debug logging on dojo at all before attempting to do anything else. It is the same thing dojo does to check for debug in most of it's statements.
dojo.event.connect(window, "onload", function(e) { ... });
If you tried to start hooking dojo event logic up to elements/widgets before they existed you'd get tons of errors. Because of the way that dojo parses your html pages to find and create widgets it is necessary/safer to be sure that the page is completely loaded before attempting to connect things together. This function uses dojo's javascript event connection infrastructure to be sure that our code isn't executed until everything else is done loading.
dojo.event.connect(dojo.byId("dclear"), "onclick", function(evt) { dojo.dom.removeChildren(dojo.byId("dojoDebug")); }); dojo.event.connect(dojo.byId("dhide"), ....
This should start to become more obvious by now, we are saying that when a user clicks on the link with id "dclear"/etc perform this action. Most of these functions have to do with clearing debug statements or hiding/resizing the widget. The cookie portions attempt to provide intelligent state logic that remembers where the dialog was on a previous request, and whether it should be displayed or hidden.
... dconsole = dojo.byId("debugconsoleContentId"); if (dconsole) dconsole.style.zIndex = "2000"; if (djConfig["isDebug"]) { dojo.dom.moveChildren(dojo.byId("debugContainer"), dojo.byId("debugconsoleContentId"), false); } else { dojo.dom.removeNode(dojo.byId("debugContainer")); } if (dojo.widget.byId("debugconsole")) { dojo.widget.byId("debugconsole").onResized(); }
This last section does one tiny tricky little thing, which is picking up any debug statements dojo may have written to our catch-all debugContainer element before it found our much nicer FloatingPane, and moves the statements to our FloatingPane console widget. The onResized widget method calls cause the widget to re-initialize it's ui state which normally gets rid of any funkyness created by messing around with the widgets contents.
Finishing Up
You should now have a successfully installed debug console in your web application. It is worth any pain you may have gone through to get there, because the pain of not knowing what's going on during ajax requests(without downloading firefox plugins or other methods) when you have a problem with your code will make this pale in comparison ;)