So let's say you're happily developing along with Webcrossing and after your latest cache reset you happen to notice that your toolbar has gone from this:
to this:
Congratulations! You've just encountered the dreaded Square Toolbar. Often there is a lot more than that amiss, but sometimes the Square Toolbar is the most obvious thing wrong. What it means is that you've got a compile error - somewhere in your code you likely have a missing or an extra WCTL %%, or a missing or extra SSJS curly brace { }. Or perhaps your SSJS functions are not surrounded by the requisite %%'s.
Technically, what has happened is that the script version of the toolbar, either a custom one or one of the toolbar plugins, is failing to load because of your compile error, so the square toolbar is coming directly from the code in the binary - a fallback, if you will.
The first thing to do is to check webx.log. The exact compile error should be listed there, with at least some clue about what is wrong in what macro or function. Usually from that you can figure out what to fix. Rarely, though, especially if you have two offsetting errors (two separate extra %%'s, for example, in different places), you will find there is no actual complile error.
What to do then? The most logical place to look, of course, is the file or files you've just updated. Often a quick glance is all it takes to find the error. But sometimes it's not all that obvious what's wrong. Maybe someone else just handed something off to you and you're not at all sure what they just did, or where to start looking. If you have any suspicions at all, it's worth trying to turn off those suspicious template files by renaming them (say, myfile.auto becomes myfile.autoOFF). If your error goes away when you reset the cache, you know where to look.
But sometimes you simply have no clue at all.
Then you have to resort to the brute force method. It's ugly, but it works. First, you need to identify the file the error is in, and secondly, where it is in the file. To do this, create a small text file with only this in it:
%% macro hello %%
Hello, world!
%% endmacro %%
Name it with a name which will ensure it loads alphabetically BEFORE any of your other template files, something like aaaaaaHello.auto. Put it in your templates directory and reset the cache. Then, in the browser location bar, call your macro hello:
http://yoursite.com?hello
You should see "Hello, world!" on the screen. Good. (If not, your problem is probably in webx.tpl itself, or perhaps one of the handful of template files that load unconditionally before anything else. If you encounter this, the log file webxIncludes.log, which is rewritten after each cache reset, can be helpful to show you what's loading and in what order.)
If all was well with your aaaaaaHello.auto file, rename it so that it loads after some of your template files, maybe mmmmmmHello.auto. Test again. If you see "Hello, world!", you'll know that everything alphabetically up to your mmmmmmmHello.auto file is OK, no errors. Your error is in one of the other files further down alphabetically. Otherwise, your error is somewhere before. Rename your file again and try again, until you narrow down a single file with the error. Rename that suspect file so it doesn't load, as described above. Test again, and you shouldn't have an error.
Now that you've identified the problem file, you've got to find the exact function or macro with the error. If it's not obvious looking at the file, remove your small text file with macro hello in it, and copy macro hello into the middle of the problem file. Test again. If all is well, your error is further down the file. Keep moving macro hello until you identify exactly which function or macro is causing trouble. If you can't find it by eyeballing it at that point, remove chunks of code from the function or macro in logical blocks (like an entire "if" structure, etc.) so that the removal itself doesn't cause a complile error. Eventually you will find the chunk of code that is causing issues and from there, you can fix it.
Whew. And I bet you've still got hair left!
Join us for a grand tour of Webcrossing - it's our favorite platform for web application development, and we're dying to tell you why we think so.
Creating Attachments Manually
In a Webcrossing standard install, the forms to add a folder, discussion, or message all have a field to attach a file. Attached files show as hotlinks when the node is displayed. (There's also a configuration option to show images inline rather than as a download.)
While there are commands in both WCTL and SSJS to add an attachment to a node, the command takes as an argument an already-existing attachment on another node. One might reasonably ask, how do you script the creation of a new attachment from scratch? It's far from unusual to have to home-brew your node-creation process; sometimes a spec is odd enough that not even with the use of the pre- and post- filters can you shim the program's default behavior. There are scripting commands that directly replicate every part of the node-creation process, except for this one.
Fortunately, it only takes a little extra work. What's really going on is that when attachments are uploaded, they get saved with a MIME wrapper. That sounds like voodoo, but in truth all it means is that a couple of lines of plaintext are prepended to the file's binary content. The content of that header will be familiar to anyone who's ever perused the full headers on an email message. It might look something like this:
Content-Disposition: form-data; name="enclosure0"; filename="IMG_0002heads.jpg"
Content-Type: image/jpeg
This information gets sent by the browser when the upload is transmitted, and it's used to determine how the file should be handled by other clients.
The first step to capturing this is to make sure that your <form> element includes the attribute enctype="multipart/form-data". Leave that off, and only the file names will be uploaded, and you'll be snatching yourself bald trying to figure out why. But once you do that, all you need is to include this SSJS in your processing routine:
var z = form["inputName"];
var h = Mime.formHeader("inputName");
var attach = new ByteBuffer();
attach.append( h );
attach.crlf();
attach.crlf();
attach.append( z );
if (z.length > 0) var attachmentLocation = location.nodeAddAttachment( attach );
This code takes the upload from an file input field whose name is "inputName" and extracts the MIME wrapper, then concatenates it with the binary data to make a well-formed attachment. And that's all there is to it.
While there are commands in both WCTL and SSJS to add an attachment to a node, the command takes as an argument an already-existing attachment on another node. One might reasonably ask, how do you script the creation of a new attachment from scratch? It's far from unusual to have to home-brew your node-creation process; sometimes a spec is odd enough that not even with the use of the pre- and post- filters can you shim the program's default behavior. There are scripting commands that directly replicate every part of the node-creation process, except for this one.
Fortunately, it only takes a little extra work. What's really going on is that when attachments are uploaded, they get saved with a MIME wrapper. That sounds like voodoo, but in truth all it means is that a couple of lines of plaintext are prepended to the file's binary content. The content of that header will be familiar to anyone who's ever perused the full headers on an email message. It might look something like this:
Content-Disposition: form-data; name="enclosure0"; filename="IMG_0002heads.jpg"
Content-Type: image/jpeg
This information gets sent by the browser when the upload is transmitted, and it's used to determine how the file should be handled by other clients.
The first step to capturing this is to make sure that your <form> element includes the attribute enctype="multipart/form-data". Leave that off, and only the file names will be uploaded, and you'll be snatching yourself bald trying to figure out why. But once you do that, all you need is to include this SSJS in your processing routine:
var z = form["inputName"];
var h = Mime.formHeader("inputName");
var attach = new ByteBuffer();
attach.append( h );
attach.crlf();
attach.crlf();
attach.append( z );
if (z.length > 0) var attachmentLocation = location.nodeAddAttachment( attach );
This code takes the upload from an file input field whose name is "inputName" and extracts the MIME wrapper, then concatenates it with the binary data to make a well-formed attachment. And that's all there is to it.
Webcrossing Subscriptions
Webcrossing Community allows registered users to "subscribe" to various folders and discussions. What this means is that the software will track which messages they've read and show them only the new messages. What happens after that depends on the type of subscription the user signs up for.
You can allow users to do any (or none!) of the following:
Users can subscribe to everything, nothing, or any combination of subscription methods - to the entire site, or to just a subset of areas that interest them. You could subscribe by "check messages" to the Dogs folder and by email digest to the Cats folder. The only limitation is that full notifications vs. individual post notifications is a sitewide setting for the user.
Subscriptions are definitely a "power feature" of Webcrossing Community. Webcrossing Neighbors has some additional subscription features by virtue of its social networking emphasis.
You can allow users to do any (or none!) of the following:
- Subscribe by web - users either go to the Message Center for a list of their new messages, or click Check Subscriptions in the toolbar on any page. Each time Check Subscriptions is clicked, the users will be taken directly to the next new message on their list without having to return to the Message Center in between.
This is really an amazing "power tool" that many sites don't understand and subsequently turn off. It allows somebody to instantly view all the new material on the site (in the areas they're interested in) without clicking around or being inundated by email notifications. - Subscribe to email for individual posts - users receive whole posts in email
- Subscribe to email notification for individual posts - users receive links back to the forum and notification that a post has been made, but must visit the forum to read the new messages. Users will not receive further notification until they have visited the forum and read the unread messages. This prevents spamming people with notification messages before they are ready to visit.
- Subscribe to email digests - users receive an email digest of all new posts, arranged by discussion, emailed at the time of their choice. The interesting thing about this is that digests don't come at some time determined by the site owner or whenever there is enough material, but instead at the time the user chooses. You an get 24 digests a day if you want - one an hour. Or just one a day. Or just one a week.
Users can subscribe to everything, nothing, or any combination of subscription methods - to the entire site, or to just a subset of areas that interest them. You could subscribe by "check messages" to the Dogs folder and by email digest to the Cats folder. The only limitation is that full notifications vs. individual post notifications is a sitewide setting for the user.
Subscriptions are definitely a "power feature" of Webcrossing Community. Webcrossing Neighbors has some additional subscription features by virtue of its social networking emphasis.
My Favorite Commands - Filter userChangeFilterPost
This command is a personal favorite of mine, not just because it's uniquely useful but because it was the first major piece of code that I wrote in the binary all by my lonesome. :-)
This SSJS filter gives you a real-time hook into any change of a user record, in much the same way that the *editFilterPre and -Post give you a real-time hook into changes of nodes in the folder/discussion/message hierarchy. When is this useful? Well, originally it was implemented to keep an external authentication database in sync with Webcrossing's database. For various reasons the client needed to be able to change user data from either direction so we wrote this filter so that it would fire whenever a user changed any field in their preferences or any other field in the user record was changed by a system process.
The filter also proved useful in a realtime forensics situation where a client suspected unauthorized access; with a userChangeFilterPost filter configured to send alerts it was possible to watch events as they unfolded rather than spelunking the logs afterwards.
The filter works simply: when it fires, two special globals are defined: changedUserId is the userId that was changed and changedFieldName is the name of the property that was altered; that includes both the built-in and custom properties. So the basic syntax is
var theUser = User.open(changedUserId);
theUser[changedFieldName]
to access the altered property. (Note the brackets syntax; theUser.changedFieldName treats the variable as a string literal which is not what you want.)
Like always, the User.open should be inside a try/catch structure because if the user has been deleted by some other process, it will throw an exception and halt processing.
This SSJS filter gives you a real-time hook into any change of a user record, in much the same way that the *editFilterPre and -Post give you a real-time hook into changes of nodes in the folder/discussion/message hierarchy. When is this useful? Well, originally it was implemented to keep an external authentication database in sync with Webcrossing's database. For various reasons the client needed to be able to change user data from either direction so we wrote this filter so that it would fire whenever a user changed any field in their preferences or any other field in the user record was changed by a system process.
The filter also proved useful in a realtime forensics situation where a client suspected unauthorized access; with a userChangeFilterPost filter configured to send alerts it was possible to watch events as they unfolded rather than spelunking the logs afterwards.
The filter works simply: when it fires, two special globals are defined: changedUserId is the userId that was changed and changedFieldName is the name of the property that was altered; that includes both the built-in and custom properties. So the basic syntax is
var theUser = User.open(changedUserId);
theUser[changedFieldName]
to access the altered property. (Note the brackets syntax; theUser.changedFieldName treats the variable as a string literal which is not what you want.)
Like always, the User.open should be inside a try/catch structure because if the user has been deleted by some other process, it will throw an exception and halt processing.
Subscribe to:
Posts (Atom)


