If you have an error in a WCTL script file, the error and its location will be written into the webx.log file, a warning message will appear on the cache reset screen, and as Sue has pointed out the existence of the error - if not its provenance - is often made obvious when your site appearance goes pear-shaped. Errors in SSJS syntax can be a little more difficult to track down, if for no other reason than JS is more frequently used for backend-type functions where nothing gets written to the screen directly. If you have an error in a filter, for example, the only sign of it may be that the filter doesn't give the expected results.
One way to deal with this is to put all the significant pieces of your code inside a try/catch structure and write an exception handler. That's certainly best-practice for production code, but during development the Webcrossing server will provide you with a wealth of information, really an embarrassment of riches, for any uncaught exception in your JS code.
All you have to do is look in your /system folder for files named in the format of logJsErrorN.html (where N is an integer). One gets written whenever an exception is thrown. It's an HTML file, it can run to be upwards of 3000 lines, and there will rarely be a time when anything past the first couple of dozen lines will be of use to you. Here's an example:
The heading gives you a timestamp for the exception, tells you the name of the routine that failed, and the type of exception it threw. Then the next section gives the values of the routine's arguments and any local variables when the exception occurred. In the next section is listed the exact line of the function where the exception occurred - so it's worthwhile to keep your code clean and well-blocked. The last likely-to-be-useful line is the stack trace, showing you exactly how you got to where the exception was thrown.
Now, the next 2000-odd lines are an enumeration of every single object in the execution context at the moment of the exception: every global, every function, every built-in method and property. I'm sure there are circumstances where this information can be useful, but I've never run into one!
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.
Showing posts with label SSJS. Show all posts
Showing posts with label SSJS. Show all posts
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.
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.
My Favorite Commands: Image manipulation
Way back in the dark ages when Webcrossing was "just" a message board, its developers came up with the radical idea of allowing users to upload little pictures to go with their user accounts. Wacky, right?
As an outgrowth of that, Webcrossing has a small but powerful set of image manipulation commands, so that users need not be limited to uploading files of only certain sizes or dimensions. Using these commands you can post-process any JPEG or GIF uploads to fit whatever requirements you choose to establish. The commands are:
image.imageCropToJpeg(top, left, width, height, quality)
top and left define the upper left corner of the portion of the image to crop, based on 0,0 as the upper left corner of the image. width and height are in pixels and define the size of the portion to crop, and quality is a percentage defining the compression and hence quality of the JPEG the command returns.
image.imageResizeToJpeg(percent, quality)
This command proportionally resizes your image proportionally. percent is the percent to scale (which can be larger than 100).
image.imageResizeWHJpeg(width, height, quality)
This command allows you to resize to a specific width and height (in pixels), and can be non-proportional.
image.imageRotateToJpeg(degreesCounterclockwise, quality)
This command rotates the image counterclockwise. To rotate clockwise, use 360-[clockwise rotation].
All of these commands return the altered image as a stream of binary data which you can save directly to file. The parameter image is the binary data of the original file, and getting that may not always be completely obvious. Here's a little helper function that will extract the binary data from an attachment. This function can also be used to get the data of a file attached to an incoming email message.
As an outgrowth of that, Webcrossing has a small but powerful set of image manipulation commands, so that users need not be limited to uploading files of only certain sizes or dimensions. Using these commands you can post-process any JPEG or GIF uploads to fit whatever requirements you choose to establish. The commands are:
image.imageCropToJpeg(top, left, width, height, quality)
top and left define the upper left corner of the portion of the image to crop, based on 0,0 as the upper left corner of the image. width and height are in pixels and define the size of the portion to crop, and quality is a percentage defining the compression and hence quality of the JPEG the command returns.
image.imageResizeToJpeg(percent, quality)
This command proportionally resizes your image proportionally. percent is the percent to scale (which can be larger than 100).
image.imageResizeWHJpeg(width, height, quality)
This command allows you to resize to a specific width and height (in pixels), and can be non-proportional.
image.imageRotateToJpeg(degreesCounterclockwise, quality)
This command rotates the image counterclockwise. To rotate clockwise, use 360-[clockwise rotation].
All of these commands return the altered image as a stream of binary data which you can save directly to file. The parameter image is the binary data of the original file, and getting that may not always be completely obvious. Here's a little helper function that will extract the binary data from an attachment. This function can also be used to get the data of a file attached to an incoming email message.
%% function getImageData(enclosureLoc) {
var image = Stored.lookup(enclosureLoc);
if (image.storedIsDocument && image.documentIsImage) {
return Mime.getBody(image.documentData);
} else {
return "";
}
} %%
Time and Date Formatting in Webcrossing
Webcrossing has built-in date formatting, which is identical in both WCTL and SSJS. This is date formatting not available in standard JavaScript. A time and date format is a list of time/date specifiers, mixed with literal characters such as : or @.
For example, in SSJS:
var now = new Date();
+ now.dateFormat( "H1:I2 a" );
This will produce the current time of day in Hours(one digit):Seconds(two digits) am/pm format, or, for example, 3:07 pm.
To do the same thing in WCTL:
%% set now date( dateObj ) %%
%% now.dateFormat( "H1:I2 a" ) %%
Alternatively in WCTL, if you don't care about the formatting particularly, you can just do %% date %% or %% time %% and get the default formatting settable in the Control Panel.
Here is the full list of the date formatting strings you can use.
For example:
For example, in SSJS:
var now = new Date();
+ now.dateFormat( "H1:I2 a" );
This will produce the current time of day in Hours(one digit):Seconds(two digits) am/pm format, or, for example, 3:07 pm.
To do the same thing in WCTL:
%% set now date( dateObj ) %%
%% now.dateFormat( "H1:I2 a" ) %%
Alternatively in WCTL, if you don't care about the formatting particularly, you can just do %% date %% or %% time %% and get the default formatting settable in the Control Panel.
Here is the full list of the date formatting strings you can use.
Y2 2 character year Y4 4 character year M1 1 or 2 digit month M2 2 digit month MMM 3 character month, all caps Mmm 3 character month, leading cap M full month name, leading cap mmm 3 character month, all lower case m full month name, all lower case D1 1 or 2 digit day of month D2 2 digit day of month D3 1st, 2nd, 3rd, 4th... WWW 3 character day of week, all caps Www 3 character day of week, leading cap W full day of week, leading cap H1 1 or 2 digit hour, 12 hour clock H2 2 digit hour, 12 hour clock H3 1 or 2 digit hour, 24 hour clock H4 2 digit hour, 24 hour clock I1 1 or 2 digit minutes I2 2 digit minutes S1 1 or 2 digit seconds S2 2 digit seconds L1 1, 2, or 3 digit milliseconds L3 3 digit milliseconds A AM/PM, upper case a am/pm, lower case N output a "-" if the date/time is negative toutc delta from local time to GMT/UTC, as +HHMM or -HHMM $c escape to embed "c" into the output string
For example:
| Format | Sample output |
|---|---|
| W, D2-Mmm-Y2 H4:I2:S2 | Monday, 05-Mar-11 09:52:13 |
| M D1, Y4 | Jan 4, 2011 |
| H2:I2a | 03:30pm |
One important difference between standard Javascript and the Webcrossing extension to the Date object is that in Webcrossing, the month is 1-based instead of 0-based. That is to say, if it's March, Date.getMonth() returns 2 while Date.dateFormat("M1") returns 3. The dateFormat is more oriented towards producing human-readable strings.
How to put HTML on the page with SSJS without quoting it
In the series of articles comparing and contrasting WCTL and SSJS, one of the points I made was that with SSJS, all html going to the response buffer has to be quoted, like this:
+ 'some text';
or this
var bb = new ByteBuffer();
bb += 'some text';
return bb;
But in the fine print on the summary table, I said there was an exception I would write about later. Well, later is now.
But first, let's talk best practices. As illustrated above, there are two ways to get HTML into the response buffer:
So with that out of the way, let's look at that exception. As it turns out, you can use pairs of %%'s (remember those from WCTL?) to delimit blocks of plain HTML to go to the response buffer. And of course, you can include client-side JavaScript in that block if you want to. But you can't replace any dynamic variables. (So you might as well put that client-side JS into a separate file, it seems to me, and save the bandwidth).
The other thing about using this method is that the HTML inside those pairs of %%'s goes immediately into the response buffer as if you had +'d or response.appended it. Try as you might, you can't do something like this, because the text "this will not work" will appear immediately on the page.
var myHTML = %% <b>this will not work</b> %%
So that means you can't use it with the better method #2 above. Keep it under your hat in case you run across the perfect use for it, but in my experience it's not all that helpful.
+ 'some text';
or this
var bb = new ByteBuffer();
bb += 'some text';
return bb;
But in the fine print on the summary table, I said there was an exception I would write about later. Well, later is now.
But first, let's talk best practices. As illustrated above, there are two ways to get HTML into the response buffer:
- response.append( 'some text' ), or use the shortcut for that, + 'some text';
- concatenate everything into a ByteBuffer() and return that at the end of the function or page
So with that out of the way, let's look at that exception. As it turns out, you can use pairs of %%'s (remember those from WCTL?) to delimit blocks of plain HTML to go to the response buffer. And of course, you can include client-side JavaScript in that block if you want to. But you can't replace any dynamic variables. (So you might as well put that client-side JS into a separate file, it seems to me, and save the bandwidth).
The other thing about using this method is that the HTML inside those pairs of %%'s goes immediately into the response buffer as if you had +'d or response.appended it. Try as you might, you can't do something like this, because the text "this will not work" will appear immediately on the page.
var myHTML = %% <b>this will not work</b> %%
So that means you can't use it with the better method #2 above. Keep it under your hat in case you run across the perfect use for it, but in my experience it's not all that helpful.
Passing Variables to Commands
One of the beauties of working with WebCrossing is that it handles a lot of the backend integration for you. One of those is the ability to pass "command line" or URL parameters to any scripting method that you create in either WCTL or SSJS. In order to continue with the authentication series, we must first give a quick lesson on how to pass information through URL's into your methods.
WebCrossing URL structure is varied due to some legacy issues in the code. I will start with the older methods of sending information into the methods and move up from there. The Original URL structure is like this:
http://yoursite.com/webx?CC@xxxxx@location!key1=val1&key2=val2
The CC is the command code or the name of your macro/command that you create in the scripting files.
The xxxxxx is the "certificate" which lets WebCrossing track users across various pages without the need for cookies.
location is a unique ID of some "node" in the database which corresponds usually to a folder, discussion, or message.
The key1=val1 are & separated key value pairs of information that your macro/command can read and use for whatever you want.
The key value pairs are separated from the rest of the URL by the use of an exclamation point ( ! ).
Lets say that you wanted to display your own simple calendar and wanted your URL structure to have the month and year passed into the calendar so that a specific month/year can be displayed to the user. Your URL might be something like this:
http://yoursite.com/webx?my_cal@@!month=1&year=2011
This would mean that 2 variables would be available in the macro "my_cal". Your macro then might look like this to process these types of URL's in WCTL:
%% macro my_cal %%
%% set month form.month %%
%% set year form.year %%
%% // use month and year to display a calendar %%
%% //… left as an exercise for the reader :-) %%
%% endmacro %%
The key value pairs are passed in through an object called "form" where the dotted property is the same as the key in the URL. These are all strings when coming into your macro, so appropriate conversions may be necessary. These values are also URL encoded, so it may also be necessary to decode them to convert %20's to spaces, etc.
This same snippet of code to process the key value pairs in SSJS would look like this:
%% command my_cal(){
month = form.month - 0;
year = form.year - 0;
// use month and year to display a calendar
// etc.
} %%
You might ask why I subtracted 0 from the form.* variables. In normal Javascript, if you subtract a 0 from a string, then it will convert the value of the string into an integer type without modifying what the value was. For instance, if form.month held the string "11", then subtracting a 0 from that would convert that value into the numeric value 11 so that subsequent calculations could be done on the number.
The "New" URL structure
One of the more recent URL structures in WebCrossing allows a more SEO friendly representation of the commands and locations. This structure is laid out like this:
webx/location/macroOrCmd/cert.certificate/name.value[/name.value.. .]
The example URL above would be represented by this:
http://yoursite.com/webx/my_cal/month.1/year.2011
Note that the certificate can be eliminated completely as long as cookies are available to track the user session instead. This query-string-less version of the WebCrossing URL is much more friendly to search engines and humans alike.
Now that you know a little about the URL structure and passing information to your macros that you write, we can continue along with the authentication filters.
Dave Jones
dave@lockersoft.com
http://www.youtube.com/lockersoft
WebCrossing URL structure is varied due to some legacy issues in the code. I will start with the older methods of sending information into the methods and move up from there. The Original URL structure is like this:
http://yoursite.com/webx?CC@xxxxx@location!key1=val1&key2=val2
The CC is the command code or the name of your macro/command that you create in the scripting files.
The xxxxxx is the "certificate" which lets WebCrossing track users across various pages without the need for cookies.
location is a unique ID of some "node" in the database which corresponds usually to a folder, discussion, or message.
The key1=val1 are & separated key value pairs of information that your macro/command can read and use for whatever you want.
The key value pairs are separated from the rest of the URL by the use of an exclamation point ( ! ).
Lets say that you wanted to display your own simple calendar and wanted your URL structure to have the month and year passed into the calendar so that a specific month/year can be displayed to the user. Your URL might be something like this:
http://yoursite.com/webx?my_cal@@!month=1&year=2011
This would mean that 2 variables would be available in the macro "my_cal". Your macro then might look like this to process these types of URL's in WCTL:
%% macro my_cal %%
%% set month form.month %%
%% set year form.year %%
%% // use month and year to display a calendar %%
%% //… left as an exercise for the reader :-) %%
%% endmacro %%
The key value pairs are passed in through an object called "form" where the dotted property is the same as the key in the URL. These are all strings when coming into your macro, so appropriate conversions may be necessary. These values are also URL encoded, so it may also be necessary to decode them to convert %20's to spaces, etc.
This same snippet of code to process the key value pairs in SSJS would look like this:
%% command my_cal(){
month = form.month - 0;
year = form.year - 0;
// use month and year to display a calendar
// etc.
} %%
You might ask why I subtracted 0 from the form.* variables. In normal Javascript, if you subtract a 0 from a string, then it will convert the value of the string into an integer type without modifying what the value was. For instance, if form.month held the string "11", then subtracting a 0 from that would convert that value into the numeric value 11 so that subsequent calculations could be done on the number.
The "New" URL structure
One of the more recent URL structures in WebCrossing allows a more SEO friendly representation of the commands and locations. This structure is laid out like this:
webx/location/macroOrCmd/cert.certificate/name.value[/name.value.. .]
The example URL above would be represented by this:
http://yoursite.com/webx/my_cal/month.1/year.2011
Note that the certificate can be eliminated completely as long as cookies are available to track the user session instead. This query-string-less version of the WebCrossing URL is much more friendly to search engines and humans alike.
Now that you know a little about the URL structure and passing information to your macros that you write, we can continue along with the authentication filters.
Dave Jones
dave@lockersoft.com
http://www.youtube.com/lockersoft
Writing client-side JavaScript inside server-side JavaScript without losing your mind
When most people think of JavaScript, they think of client-side JavaScript. However, JavaScript is also used as a scripting language in Webcrossing. Here are a few tips for staying sane while using client-side JavaScript inside a server-side JavaScript environment.
Keep your client-side and your server-side code straight. Be careful to not get your client side variables or functions mixed up with your server-side functions. It seems like a forehead-slapping no-brainer, but we've all done it, often accompanied by a bit of head-against-brick-wall banging before the light dawns.
Writing client-side JavaScript inside server-side JavaScript can be tedious, especially if it is halfway complex, because server-side JavaScript requires that client-side JavaScript be quoted to get it to appear on the page as a literal. For best result, you'll want to write the client-side JavaScript first, by itself. Then put it into the server-side code and quote it (escaping what you need to) appropriately. In my experience, even that sometimes produces four letter words, usually when server-side variables values needs to be inserted into the client-side code.
Debugging the client-side code later is much simpler if you also insert line breaks into the SSJS code using \r\n at the ends of the client-side function lines:
bb += 'function myFunction() {\r\n';
bb += 'var somevar = 1;\r\n';
bb += 'return somevar;\r\n';
bb += '}\r\n';
Without this, it all runs together on one line and browser debug utilities won't be much help telling you what line the problem is on.
When you can, break out your client-side JavaScript into a separate file. Whatever is in a separate file won't need to be quoted. Duh :)
However, sometimes this is impossible because you need to set client-side JavaScript variables based on server-side values that are only known on the page being constructed. For example, your client-side code might need to know the username and for whatever reason, you can't just send it as a parameter. In this case, there are a couple of different things you can do. One thing is to put a small client-side function on your page like this (this is server-side code):
bb += 'function getUserName() { return ' + user.userName + '; }';
Then in your client-side JavaScript in your separate file, all you need to do is call your getUserName function to retrieve the name. In some cases you can also do something similar using hidden form fields (again, server side code):
bb += '<input type="hidden" name="userName" value="' + user.userName + '">';
Another thing you can do is use the special %% syntax in SSJS whereby anything inside pairs of %% gets delivered immediately to the response buffer. However, that only works if you don't have any server-side variables inside your client-side script. If you don't, you might as well put it in a separate file. And it also only works if you are not concatenating your response into a ByteBuffer and returning it all at the end of your function.
Hopefully these tips will keep your brain from becoming scrambled while using JavaScript in two simultaneous contexts.
Keep your client-side and your server-side code straight. Be careful to not get your client side variables or functions mixed up with your server-side functions. It seems like a forehead-slapping no-brainer, but we've all done it, often accompanied by a bit of head-against-brick-wall banging before the light dawns.
Writing client-side JavaScript inside server-side JavaScript can be tedious, especially if it is halfway complex, because server-side JavaScript requires that client-side JavaScript be quoted to get it to appear on the page as a literal. For best result, you'll want to write the client-side JavaScript first, by itself. Then put it into the server-side code and quote it (escaping what you need to) appropriately. In my experience, even that sometimes produces four letter words, usually when server-side variables values needs to be inserted into the client-side code.
Debugging the client-side code later is much simpler if you also insert line breaks into the SSJS code using \r\n at the ends of the client-side function lines:
bb += 'function myFunction() {\r\n';
bb += 'var somevar = 1;\r\n';
bb += 'return somevar;\r\n';
bb += '}\r\n';
Without this, it all runs together on one line and browser debug utilities won't be much help telling you what line the problem is on.
When you can, break out your client-side JavaScript into a separate file. Whatever is in a separate file won't need to be quoted. Duh :)
However, sometimes this is impossible because you need to set client-side JavaScript variables based on server-side values that are only known on the page being constructed. For example, your client-side code might need to know the username and for whatever reason, you can't just send it as a parameter. In this case, there are a couple of different things you can do. One thing is to put a small client-side function on your page like this (this is server-side code):
bb += 'function getUserName() { return ' + user.userName + '; }';
Then in your client-side JavaScript in your separate file, all you need to do is call your getUserName function to retrieve the name. In some cases you can also do something similar using hidden form fields (again, server side code):
bb += '<input type="hidden" name="userName" value="' + user.userName + '">';
Another thing you can do is use the special %% syntax in SSJS whereby anything inside pairs of %% gets delivered immediately to the response buffer. However, that only works if you don't have any server-side variables inside your client-side script. If you don't, you might as well put it in a separate file. And it also only works if you are not concatenating your response into a ByteBuffer and returning it all at the end of your function.
Hopefully these tips will keep your brain from becoming scrambled while using JavaScript in two simultaneous contexts.
Passing Variables to Commands
One of the beauties of working with WebCrossing is that it handles a lot of the backend integration for you. One of those is the ability to pass "command line" or URL parameters to any scripting method that you create in either WCTL or SSJS. In order to continue with the authentication series, we must first give a quick lesson on how to pass information through URL's into your methods.
WebCrossing URL structure is varied due to some legacy issues in the code. I will start with the older methods of sending information into the methods and move up from there. The Original URL structure is like this:
http://yoursite.com/webx?CC@xxxxx@location!key1=val1&key2=val2
The CC is the command code or the name of your macro/command that you create in the scripting files.
The xxxxxx is the "certificate" which lets WebCrossing track users across various pages without the need for cookies.
location is a unique ID of some "node" in the database which corresponds usually to a folder, discussion, or message.
The key1=val1 are & separated key value pairs of information that your macro/command can read and use for whatever you want.
The key value pairs are separated from the rest of the URL by the use of an exclamation point ( ! ).
Lets say that you wanted to display your own simple calendar and wanted your URL structure to have the month and year passed into the calendar so that a specific month/year can be displayed to the user. Your URL might be something like this:
http://yoursite.com/webx?my_cal@@!month=1&year=2011
This would mean that 2 variables would be available in the macro "my_cal". Your macro then might look like this to process these types of URL's in WCTL:
%% macro my_cal %%
%% set month form.month %%
%% set year form.year %%
%% // use month and year to display a calendar %%
%% //… left as an exercise for the reader :-) %%
%% endmacro %%
The key value pairs are passed in through an object called "form" where the dotted property is the same as the key in the URL. These are all strings when coming into your macro, so appropriate conversions may be necessary. These values are also URL encoded, so it may also be necessary to decode them to convert %20's to spaces, etc.
This same snippet of code to process the key value pairs in SSJS would look like this:
%% command my_cal(){
month = form.month - 0;
year = form.year - 0;
// use month and year to display a calendar
// etc.
} %%
You might ask why I subtracted 0 from the form.* variables. In normal Javascript, if you subtract a 0 from a string, then it will convert the value of the string into an integer type without modifying what the value was. For instance, if form.month held the string "11", then subtracting a 0 from that would convert that value into the numeric value 11 so that subsequent calculations could be done on the number.
The "New" URL structure
One of the more recent URL structures in WebCrossing allows a more SEO friendly representation of the commands and locations. This structure is laid out like this:
webx/location/macroOrCmd/cert.certificate/name.value[/name.value.. .]
The example URL above would be represented by this:
http://yoursite.com/webx/my_cal/month.1/year.2011
Note that the certificate can be eliminated completely as long as cookies are available to track the user session instead. This query-string-less version of the WebCrossing URL is much more friendly to search engines and humans alike.
Now that you know a little about the URL structure and passing information to your macros that you write, we can continue along with the authentication filters.
Dave Jones
dave@lockersoft.com
http://www.youtube.com/lockersoft
WebCrossing URL structure is varied due to some legacy issues in the code. I will start with the older methods of sending information into the methods and move up from there. The Original URL structure is like this:
http://yoursite.com/webx?CC@xxxxx@location!key1=val1&key2=val2
The CC is the command code or the name of your macro/command that you create in the scripting files.
The xxxxxx is the "certificate" which lets WebCrossing track users across various pages without the need for cookies.
location is a unique ID of some "node" in the database which corresponds usually to a folder, discussion, or message.
The key1=val1 are & separated key value pairs of information that your macro/command can read and use for whatever you want.
The key value pairs are separated from the rest of the URL by the use of an exclamation point ( ! ).
Lets say that you wanted to display your own simple calendar and wanted your URL structure to have the month and year passed into the calendar so that a specific month/year can be displayed to the user. Your URL might be something like this:
http://yoursite.com/webx?my_cal@@!month=1&year=2011
This would mean that 2 variables would be available in the macro "my_cal". Your macro then might look like this to process these types of URL's in WCTL:
%% macro my_cal %%
%% set month form.month %%
%% set year form.year %%
%% // use month and year to display a calendar %%
%% //… left as an exercise for the reader :-) %%
%% endmacro %%
The key value pairs are passed in through an object called "form" where the dotted property is the same as the key in the URL. These are all strings when coming into your macro, so appropriate conversions may be necessary. These values are also URL encoded, so it may also be necessary to decode them to convert %20's to spaces, etc.
This same snippet of code to process the key value pairs in SSJS would look like this:
%% command my_cal(){
month = form.month - 0;
year = form.year - 0;
// use month and year to display a calendar
// etc.
} %%
You might ask why I subtracted 0 from the form.* variables. In normal Javascript, if you subtract a 0 from a string, then it will convert the value of the string into an integer type without modifying what the value was. For instance, if form.month held the string "11", then subtracting a 0 from that would convert that value into the numeric value 11 so that subsequent calculations could be done on the number.
The "New" URL structure
One of the more recent URL structures in WebCrossing allows a more SEO friendly representation of the commands and locations. This structure is laid out like this:
webx/location/macroOrCmd/cert.certificate/name.value[/name.value.. .]
The example URL above would be represented by this:
http://yoursite.com/webx/my_cal/month.1/year.2011
Note that the certificate can be eliminated completely as long as cookies are available to track the user session instead. This query-string-less version of the WebCrossing URL is much more friendly to search engines and humans alike.
Now that you know a little about the URL structure and passing information to your macros that you write, we can continue along with the authentication filters.
Dave Jones
dave@lockersoft.com
http://www.youtube.com/lockersoft
Filters, part 2: Email Filters
As you may know, Webcrossing users can subscribe to individual discussions or entire folders. They can read their subscriptions either through the web interface or via email. As an admin you can configure the system to send individual email notifications for each new posting or to accumulate and send digest emails at intervals. Individual notifications can contain the full text of the posting or only a link back to the conference. Whatever options you enable, you can also allow your users to choose their preferred method.
That's a lot of flexibility, but there is a class of filters that can give you complete control over the email notification system (and indeed over all email processing). These filters are SSJS only; unlike many other filters they cannot be written in WCTL. (Although even that is not an absolute prohibition; see Sue's post about mixed-mode coding.)
The filters are:
emailFilterNotify fires whenever an individual email notification is ready for delivery. Inside this filter you have access to three global variables:
emailFilterIndividual has a more extensive set of globals:
That's a lot of flexibility, but there is a class of filters that can give you complete control over the email notification system (and indeed over all email processing). These filters are SSJS only; unlike many other filters they cannot be written in WCTL. (Although even that is not an absolute prohibition; see Sue's post about mixed-mode coding.)
The filters are:
emailFilterNotify fires whenever an individual email notification is ready for delivery. Inside this filter you have access to three global variables:
- filterMessagePath (read-only) is the storedUniqueId of the item which the notification is for
- filterMessageFull (read/write) is the complete text of the full-message notification
- filterMessageUrl (r/w) is the complete text of the linkback notification
emailFilterIndividual has a more extensive set of globals:
- filterMessageFrom (r/w) is the bounce address for the message. By default it is set to bounce.userId.msgCounter@firstDomain where firstDomain is the first entry in the list of email domains available for the message location.
- filterMessagePath (r-o) is, as above, the id of the item
- filterMessageFull (r-o) is the normal full-message notification
- filterMessageUrl (r-o) is the normal linkback notification
- filterMessageFullPlain (r-o) is the normal full-message notification converted to a plaintext format
- filterMessageUser (r-o) is the userId of the recipient
- filterMessageRcpt (r/w) is the recipient email address
- filterMessageIndividual (r/w) is the content that will ultimately be sent. It is empty when the filter is called; if it is nonempty when the filter exits then it is what will be sent instead of the default format
- filterMessageSuppress (r/w) is a flag to prevent the message from being sent. It is empty when the filter is called; if it is nonempty when the filter exits then no notification will be sent
%% function emailFilterIndividual() {
filterMessageIndividual.append( filterMessageFull ).crlf().crlf();
filterMessageIndividual.append( 'This is an extra line.' ).crlf();
} %%
This trivial example takes the default full-text notification and adds some text to the end of it. That is what the user will receive, overriding whatever preferences they have set for email format.
Some other noteworthy points:
All the writeable globals are ByteBuffers. A ByteBuffer is a stringlike object but it comes with a suite of helper methods that make it easy to compare, parse, and convert text.
The filterMessageFull, filterMessageUrl, and filterMessageFullPlain globals are actually in MIME format. That is, they contain all the message headers followed by two crlf pairs, followed by the message content. You can use the built-in Mime object to parse and manipulate this, and even create multipart messages containing inline attachments or other non-text content.
Thirdly, there is also emailFilterDigest. As its name suggests, it fires whenever a digest-style email notification is ready for delivery. I won't discuss it in detail now, just to keep this post a manageable length. But like emailFilterIndividual it gives you complete control over the content of the outgoing email, its recipients, or even whether to send it at all.
Thirdly, there is also emailFilterDigest. As its name suggests, it fires whenever a digest-style email notification is ready for delivery. I won't discuss it in detail now, just to keep this post a manageable length. But like emailFilterIndividual it gives you complete control over the content of the outgoing email, its recipients, or even whether to send it at all.
Getting the intermix just right (mixing SSJS and WCTL)
It's possible to call WCTL macros from a page being constructed in SSJS, and vice-versa. Here's how, along with a few tips to avoid the potholes on your journey:
Calling WCTL from SSJS:
Calling SSJS from WCTL:
And now, the potholes:
Calling WCTL from SSJS:
- To call a WCTL macro from SSJS, use the wctlEval syntax:
bb += wctlEval( 'use myWCTLMacroName' );
- Remember that you can't send function parameters in WCTL, so you may need to set a WCTL variable before calling your WCTL function:
setWctlVar( 'myWctlVarName', value );
- Because WCTL has only one variable type, strings, make sure that the value in your setWctlVar() statement is a string. WCTL can do integer math on strings, so don't worry that point if your WCTL macro needs to do any simple calculations.
- You can also get a WCTL variable value from JS using wctlVar:
wctlVar( 'myWCTLVarName' );
- If, for some reason, you need to evaluate a chunk of WCTL which includes %%'s, you can use an alternative wctlEvalTemplate syntax:
wctlEvalTemplate( "%" + "% if red %" + "%" + "red" + "%" + "% endif %" + "%" );
(In other words, evaluate: %% if red %%red%% endif %%) As you can see, the syntax is a bit ridiculous because %% has a specific meaning within SSJS, so you have to divide up the individual %'s in order to get it to work. This isn't used much because it's usually easier to just make it a macro and call with wctlEval.
Calling SSJS from WCTL:
- To call an arbitrary SSJS expression from WCTL, us the jsEval syntax:
%% "if ( user ) { + 'Hello ' + user.userName; }".jsEval %%
- But if you want to specifically call an SSJS command or function, it's more efficient to use the jsCall syntax:
%% "mySSJSCommandOrFunctionName".jsCall( "param1", param2 ) %%
You can send as many parameters as you need to. Parameters can be either WCTL variables or literals.
- If your SSJS is a command and doesn't have any parameters (this won't work with functions), you can alternatively call your SSJS like you would any WCTL macro:
%% use mySSJSCommandName %%
- If your SSJS function or command is returning a numeric value, convert that number to a string in the SSJS function before returning it to WCTL. If you don't, it will be blank because WCTL won't know what to do with it.
And now, the potholes:
- Any SSJS function mixed with WCTL macro needs to be constructed so that it concatenates the page response to a ByteBuffer and then returns the ByteBuffer at the end of the function. If you don't do that, you run the risk of A Very Weird Thing(tm) when the page elements come out in the wrong order because of differences in the way the response buffer is handled in WCTL and SSJS.
So do this:
var bb = new ByteBuffer();
bb += "Hello, world!";
return bb;
And not this:
+ "Hello, world!";
- Usually, WCTL and SSJS will track the "current location" such that the value of %% location %% in WCTL will be the same as the SSJS location.storedUniqueId. However, once in a while, they can get out of synch. In any case, if you are having problems with the current location not being what you think it should be, you can set it in WCTL setPath( someLocationId ) or in SSJS location = someNodeObject. In SSJS if you only have the unique ID of the location you'll need to get the object from the unique ID first:
location = Node.lookup( 'someUniqueId' );
Function vs. command?
You may have noticed that in some of our SSJS examples, we are writing functions named "function xyz()" and sometimes we are writing functions called "command xyz()" and you may be wondering what the difference is.
It's actually pretty simple. Commands can be called from the browser location bar in an URL and functions can't. In nearly all other respects they are identical.
This has some usage implications, though. If you want to add an extra level of security to a routine you wrote, name it as a function, and people won't be able to execute it from the browser directly.
And there are two more trivia facts you should know about functions and commands:
And that's it. As you can see, in most cases it doesn't make a huge amount of difference which you use.
It's actually pretty simple. Commands can be called from the browser location bar in an URL and functions can't. In nearly all other respects they are identical.
This has some usage implications, though. If you want to add an extra level of security to a routine you wrote, name it as a function, and people won't be able to execute it from the browser directly.
And there are two more trivia facts you should know about functions and commands:
- Some filters have to be named as commands or they won't work.
- Commands can be called from WCTL with the usual WCTL "use" syntax rather than the more convoluted JS call syntax:
%% use myCommand %%
rather than
%% "myCommand".jsCall() %%
And that's it. As you can see, in most cases it doesn't make a huge amount of difference which you use.
Inheritance and the forum hierarchy
Sue wrote an earlier post about Webcrossing's object-oriented database, and the built-in objects that support forums. Objects representing folders, discussions, and messages descend from the parent Stored, and they inherit properties and methods from it that describe each object and its content and make manipulation easy.
There's another kind of inheritance available to you, one that depends on how you decide to structure your forum hierarchy. Every node you create, whether a folder, or a discussion, or an object you prototype yourself, has a parent in the forum hierarchy, analogous to the directory structure on a hard drive. You can define custom properties for any individual node. And then it's trivial for the node's descendents to access those properties.
Using Webcrossing's server-side javascript you could, for example, associate a folder with an external url, just by naming the property and setting it to a value:
myNode.externalUrl = 'http://www.someotherplace.com/somewhere';
Using Webcrossing's hierarchy support, that property is now available to any child of the node without looping. The special global function $() makes this possible. $() first looks for a method at the current location. If not found, it looks for a property at the current location. If not found, it checks the immediate parent location for a method, and if not found, a property. If neither are found, it checks the next-higher parent, etc. For example,
$( 'nodeBanner' )
will show the banner for the current node, if one is defined. If not, the function looks at the parent, then the parent's parent, and so on until it finds a node for which the property is defined, or it reaches the root of the site. As this example suggests, this works with built-in properties and methods as well as those you have defined yourself.
You can also call the function on a specific node, rather than the current location:
someplaceElse.$( 'someProperty' )
Built-in support for a hierarchical forum, custom properties and methods that are defined by reference, and the $() function all work together to unlock the power of Webcrossing in your scripting.
There's another kind of inheritance available to you, one that depends on how you decide to structure your forum hierarchy. Every node you create, whether a folder, or a discussion, or an object you prototype yourself, has a parent in the forum hierarchy, analogous to the directory structure on a hard drive. You can define custom properties for any individual node. And then it's trivial for the node's descendents to access those properties.
Using Webcrossing's server-side javascript you could, for example, associate a folder with an external url, just by naming the property and setting it to a value:
myNode.externalUrl = 'http://www.someotherplace.com/somewhere';
Using Webcrossing's hierarchy support, that property is now available to any child of the node without looping. The special global function $() makes this possible. $() first looks for a method at the current location. If not found, it looks for a property at the current location. If not found, it checks the immediate parent location for a method, and if not found, a property. If neither are found, it checks the next-higher parent, etc. For example,
$( 'nodeBanner' )
will show the banner for the current node, if one is defined. If not, the function looks at the parent, then the parent's parent, and so on until it finds a node for which the property is defined, or it reaches the root of the site. As this example suggests, this works with built-in properties and methods as well as those you have defined yourself.
You can also call the function on a specific node, rather than the current location:
someplaceElse.$( 'someProperty' )
Built-in support for a hierarchical forum, custom properties and methods that are defined by reference, and the $() function all work together to unlock the power of Webcrossing in your scripting.
WCTL and SSJS duke it out [Part 3]
OK, here's where it gets fun. Part 1 was about WCTL (Webcrossing Template Language) and Part 2 was about Webcrossing's newer, more powerful scripting language, SSJS (server-side JavaScript). Now they'll both put on their boxing gloves and show us their stuff.
| WCTL Webcrossing Template Language | SSJS Server-side JavaScript | |
|---|---|---|
| tight integration with OODB | yes | yes |
| easy for beginners | yes | no |
| good for experienced programmers | if you can get used to its limitations | definitely |
| tag-based | yes | no, every string going to the response buffer must be quoted (with one exception I will write about later in a full post) |
| use with client-side JS | easy-peasy | can be a bit of a hair-puller |
| case sensitivity | no | yes |
| number crunching ability | limited, integers only | full suite of JavaScript Math methods |
| execution speed | very fast | fast |
| use directly in Control Panel HTML textareas | yes | no |
| error handling | graceful, but little information | not always graceful, but great stack traces |
| out-of-box scripts | most are WCTL | a few are SSJS |
| object-oriented | pseudo, not really | yes |
| has arrays | no | yes, plus StoredArray() objects stored in DB |
| function parameters | no | yes |
| variable scope | very broad, across entire execution request | normal JavaScript variable scope |
| have multiple database location objects open at once | no, just current location | yes |
| have multiple user objects open at once | just user and author | yes |
| create new object types | sort of | yes |
| bottom line | blazingly fast, good for beginners, a little quirky | powerful, not so forgiving, just a trifle slower than WCTL |
SSJS: powerful and full-featured, not so forgiving [Part 2]
Webcrossing's second scripting language is server-side JavaScript (SSJS), sometimes referred to Webcrossing JavaScript (WCJS). What Web Crossing did along around version 3 was add support for the Mozilla Spidermonkey implementation of JavaScript.
JavaScript isn't usually thought of as a server-side language, but it's really quite rich and powerful on its own, and coupled with the OODB, you can do some really cool things. Since it's tightly tied to the object-oriented database (OODB), it's trivial to set properties on built-in forum objects.
Like WCTL, SSJS starts with the concept of "location" as the forum object at which the script is being run. From there, you can look up any other objects you want to work with. Any properties you set are automagically just saved in the database.
var someMessage = Stored.lookup( uniqueId );
someMessage.lastEditor = somebody;
But, unlike WCTL, you aren't stuck with a single current location. You can have as many Node objects available as you like:
var theFirstFolder = Node.lookup( uniqueId );
var theSecondFolder = Node.lookup( uniqueId );
var theThirdFolder = Node.lookup( uniqueId );
From there, you can do whatever you like with the first, second, and third folder objects you have open.
Same with users - no user/author dance like with WCTL.
var someUser = User.open( uniqueId );
var someOtherUser = User.lookup( name );
You can also create your own new object types based on the built-in primitive forum objects, prototype them as you see fit, and store them in the database.
As you can see, SSJS is really just plain old JavaScript, but there are some special methods added just for use within Webcrossing, like the $( someproperty ) hierarchical property-fetcher, date formatting methods, and more.
But SSJS might not be the best for beginners without any programming experience: as you would expect from a "real" programming language which is CaSe sensitive, has arrays and more than one variable type, and has functions with parameters, there is more than a small learning curve.
If you don't program defensively, it tends to throw up errors all over the page, but on the bright side, you get a really nice helpful stack trace for each error. Every bit of HTML going to the page needs to be quoted, which can eat up some time, and getting complex client-side JavaScript quoted and escaped just right in server-side JavaScript can be a bit of a nightmare sometimes.
Some of us swear by SSJS, some of us swear at SSJS, and all of us use it at least part or most of the time. When you need the power and flexibility of a real programming language, want to create and prototype your own objects, have to do something with multiple locations or users at once, or need to do some serious number crunching, SSJS is for you.
We covered the Webcrossing Template Language (WCTL) in Part 1, and Part 3 will summarize what the two languages are each best and worst at.
JavaScript isn't usually thought of as a server-side language, but it's really quite rich and powerful on its own, and coupled with the OODB, you can do some really cool things. Since it's tightly tied to the object-oriented database (OODB), it's trivial to set properties on built-in forum objects.
Like WCTL, SSJS starts with the concept of "location" as the forum object at which the script is being run. From there, you can look up any other objects you want to work with. Any properties you set are automagically just saved in the database.
var someMessage = Stored.lookup( uniqueId );
someMessage.lastEditor = somebody;
But, unlike WCTL, you aren't stuck with a single current location. You can have as many Node objects available as you like:
var theFirstFolder = Node.lookup( uniqueId );
var theSecondFolder = Node.lookup( uniqueId );
var theThirdFolder = Node.lookup( uniqueId );
From there, you can do whatever you like with the first, second, and third folder objects you have open.
Same with users - no user/author dance like with WCTL.
var someUser = User.open( uniqueId );
var someOtherUser = User.lookup( name );
You can also create your own new object types based on the built-in primitive forum objects, prototype them as you see fit, and store them in the database.
As you can see, SSJS is really just plain old JavaScript, but there are some special methods added just for use within Webcrossing, like the $( someproperty ) hierarchical property-fetcher, date formatting methods, and more.
But SSJS might not be the best for beginners without any programming experience: as you would expect from a "real" programming language which is CaSe sensitive, has arrays and more than one variable type, and has functions with parameters, there is more than a small learning curve.
If you don't program defensively, it tends to throw up errors all over the page, but on the bright side, you get a really nice helpful stack trace for each error. Every bit of HTML going to the page needs to be quoted, which can eat up some time, and getting complex client-side JavaScript quoted and escaped just right in server-side JavaScript can be a bit of a nightmare sometimes.
Some of us swear by SSJS, some of us swear at SSJS, and all of us use it at least part or most of the time. When you need the power and flexibility of a real programming language, want to create and prototype your own objects, have to do something with multiple locations or users at once, or need to do some serious number crunching, SSJS is for you.
We covered the Webcrossing Template Language (WCTL) in Part 1, and Part 3 will summarize what the two languages are each best and worst at.
Subscribe to:
Posts (Atom)
