Abstract
This document describes how to develop web applications within the ExSite framework, how to port traditional web applications to the ExSite framework, and how to build drivers to interface to unsupported database engines.
Contents
An ExSite Web Application has the usual features associated with web applications, most importantly 2-way interactivity with the web site user, in which data is taken in, and dynamic web pages generated in response. ExSite Web Applications also have some additional features made possible by the ExSite Website Operating System framework:
There are four common types of web applications:
Client-side applications use special browser-based languages such as Javascript, Java, and Flash, to run directly on the user's computer. This is good when fast response is needed and little contact with the server is required. Since the program (or links to the program) must be embedded into the web page, client-side applications are also another form of web page content that is managed using ExSite's Content Management tools.
These applications embed programs into documents that are published and served like normal HTML files. Because PHP and ASP programs are published right into web page files, they are handled in ExSite as simply another form of web page content. ExSite supports .php and .asp extensions to web page files, indicating that they should be pre-processed before being served to the end user. Since the programming elements of your pages are just another form of content, you also gain the advantages of ExSite's Content Management features, such as versioning and access control.
CGI (Common Gateway Interface) applications use self-contained programs to dynamically generate web pages. These programs are spawned by the web server when a CGI request arrives, and they generate complete web pages, which are given back to the server (and then to the client). ExSite can operate in CGI mode (and does so by default in the regular distribution), but web applications are not treated as separate CGI programs. Rather, web applications are handled as "callbacks" that the standard ExSite CGI programs (in particular page.cgi, which generates web pages) use to obtain their content and insert them into templates. Existing CGI programs can be modified to conform to this framework using Dynamic Content Drivers (see below).
Embedded Server Applications are coded right into the web server engine itself. This is typically done for performance reasons, since it is faster if the web server doesn't have to farm out dynamic page generation to other processes. ExSite supports the mod_perl mode of the Apache web server, allowing it to run as an embedded server application. This gives your web application access to the internals of the webserver as it runs. To use this functionality, your application will have to be coded as a
Dynamic Content Driver (see below).
A Dynamic Content Driver (or DCD) is a way of coding a web application so that it can behave as a dynamic content module and take advantage of ExSite's website operating system framework. They are called dynamic content drivers because:
In end-user documentation, DCDs are often referred to as "plug-ins", "modules", or "web applications".
DCDs are written in Perl, the native language of ExSite. There are methods for interfacing DCDs to applications written in other languages, if your existing code is not written in Perl.
DCDs are invoked on the public side using content management tags that are inserted into web pages. A single web page can contain DCD tags for multiple web apps. Many DCDs are like "applets", and accomplish small but useful tasks. Many different DCDs may have to work together to build a full page, by dynamically creating page elements such as:
ExSite Dynamic Content Drivers follow the model of Unix device drivers, which is to say that they support three calls:
These three calls into the DCD can be invoked using tags embedded into the raw (pre-published) HTML of the page, as described later on in DCD Calling Conventions.
ExSite interacts with the web application only through these three entry points. In particular, information is returned to the viewer only by the write() function. Access control to the application can be concentrated at these points, simplifying your security.
The read() method is used to process all input to the DCD. ExSite provides an Input class library that will manage various forms of input in such a way that they can be shared by multiple DCDs simultaneously. This is strongly recommended over reading your own input directly, since if there are multiple DCDs at work within a single web page, one can potentially steal input that belongs to another. In particular, once stdin is read, it is gone, and the other DCDs won't be able to see the input that may have been intended for them. The Input class library handles the sharing of input between different DCDs, and is documented fully in the appendices of this document.
Among the various possible inputs that a DCD's read() method could be sensitive to are:
It is up to the application developer to determine which values are of interest to the application, and to acquire and store those values in the read() method.
The read() method will accept an optional argument string. Although not often used, this feature is available to give more control over how the read() method functions. The
read() method can be called from a web page, using special content management tags, and alternative read options can be passed to it from those same tags. See DCD Calling Conventions, below.
The write() method handles the actual generation of dynamic content. Like the read method, it will accept an optional argument string, which can contain special arguments or parameters controlling the details of the requested page. The write method does not generate any output directly; rather, its output is simply assembled into a string that is returned to its caller.
The argument string that write() receives, comes from the content management tag used to invoke the application's DCD. For example, this CMS tag:
<!--&MyDCD(options)-->
is translated into the following call:
&Modules::MyDCD::write("options");
A useful trick is to format the options string like URL-encoded form data or query string so that it can be treated like a "default" query string to initialize the DCD if no other input is provided. For example:
sub write() { my ($this,$options) = @_;# use the query string preferentially, but default to the options string my $input = $ENV{QUERY_STRING} ? $ENV{QUERY_STRING} : $options;
#... }
Or, if you are using the built-in input handling:
sub write() { my ($this,$options) = @_;my %hard_options = &DecodeString($options); # parse the options string my %soft_optoins = %{$this->{input}}; # pre-parsed input
# use the user-supplied soft options preferentially, # but fall back on the hard-coded options coded in the tag, # or to a default command if nothing at all has been provided
my $cmd = $soft_options{cmd} || $hard_options{cmd} || "default_command";
#... }
The ioctl() (I/O Control) method handles all non read/write operations. It behaves like a generic function wrapper, receiving a simple string argument (the "request"), and simply does its job, returning whatever value (or values) it deems appropriate. The return value is not treated as content, and will not appear in a web page if called directly from a CMS tag. Rather the caller must know what to do with the return value. If ioctl() is called from a CMS tag in a web page, the return value will be ignored.
ioctl() is used for all communications with the DCD that do not involve either reading web input, or generating web content. One of the major uses is responding to queries from the ExSite kernel to identify the attributes and capabilities of the DCD. For example, the following are standard ioctl() requests that will be used by the ExSite kernel and content management system:
Any particular DCD can respond to these requests or not. In general a DCD can function without any ioctl() functionality at all, but invoking some of these special DCD features will require some ioctl() configuration.
ExSite provides a BaseDCD class that can serve both as a template for writing DCDs, and also as a class that simple DCDs can inherit from to save on redundant code common to many DCDs. In particular, the BaseDCD class includes a generic constructor, new(), and a generic read() method.
The generic read() is particularly useful, because it automatically prefetches all QUERY_STRING and POST input, parses it using URL-encoding conventions, and stores the combined results in a hash reference under the {input} key of the DCD object. Furthermore it does this in a neighbourly way so that POST input can be shared with any other DCD that may need to use it. The generic new() method automatically calls read() so that this input is available to the write() method without any additional effort.
These generic methods are useful to many basic DCDs, meaning the developer only needs to code a write() method, telling the DCD how to generate content, and optionally an ioctl() method, if the DCD does anything extra or has an adminstrator interface.
The method for inheriting from the BaseDCD class is simply to include the following lines near the top of your DCD module:
# inherit from BaseDCD class use Modules::BaseDCD; use vars qw(@ISA); @ISA = qw(Modules::BaseDCD);
In addition to generic versions of these standard methods, the BaseDCD class also has a few useful utility functions that may come in handy when programming the DCD:
$this->link(param1=>"xxx",param2=>undef)
This is extremly useful when the DCD has to generate new links back to itself for recursive operation. Note that this works, even if the original page is published to HTML.
If the DCD is running as a service, then the new link that is returned will be to whichever page is running that service.
Dynamically-generated web pages can be visited recursively with different results; that is you can return to the same page using a different set of inputs, and end up with a different set of content being displayed.
This is no different in principle than a CGI program generating different output with different inputs. In fact, the page generator is a CGI program (page.cgi), so this is exactly what is happening. The important difference is that page.cgi is a clearing house for all of your web applications, so all input and output go through the same program, instead of having separate CGI programs for every function on your site.
The default version of a page is viewed using the URL to the published page, if it exists, or by using the page.cgi program to dynamically render it, ie:
http://mydomain.com/cgi-bin/page.cgi?_id=42
(This tells ExSite to display page number 42 in its database.) This page can call itself recursively, adding additional data to the URL as needed. For instance:
http://mydomain.com/cgi-bin/page.cgi?_id=42&action=view&product_id=295
Using these additional inputs, the DCD embedded in the page may make completely different decisions about what output to return to the page. This process can go on indefinitely, depending on how many variables and values your DCD can respond to.
The QUERY_STRING accepted by page.cgi is URL-encoded by convention, and the data parameters and values specified in it are visible to the internals of page.cgi and to all the DCDs that are embedded in that page.
For this reason, it helps to have some conventions to help sort out what data belongs to whom. By convention, data intended for page.cgi itself uses parameter names with a preceeding underscore (such as _id in the above URL examples). Data intended for DCDs is named with no preceeding underscore.
Inside a DCD it is not obvious which page you are generating content for. (Indeed it is not obvious that you are generating content for an actual page at all.) This can make it tricky to figure out what URL you need to point to when creating recursive links back to the DCD. The relink() and link() functions are invaluable for generating URLs to link back to yourself.
&relink(param=>"value");
The relink() function (in the ExSite::Misc library) returns a URL to the current page, but with the given arguments added or modified. For example, if the current URL is /cgi/page.cgi?_id=25&param=oldvalue, then the above call will return /cgi/page.cgi?_id=25&param=value. If you want to clear a parameter (ie. remove it from the QUERY_STRING entirely, set its value to undef. Note that the relink() function works for any URL-encoded QUERY_STRING on any CGI program, not just page.cgi.
$this->link(param=>"value");
The link() function is similar, but has additional features. For starters, it is a class method inherited from ExSite::BaseDCD. Rather than linking back to the same page, it generates a link back to the DCD. In simple cases, this links back to the same page (since the DCD is embedded in the page), which gives the same behaviour as relink(). However, if the DCD has been configured to be served from a specific page on the site (see inter-page services), then the link could jump to another page entirely. Furthermore, link() is sensitive to AJAX/DHTML methods, and will, in certain cases, generate Javascript to dynamically update certain elements of the page, instead of reloading the entire page.
Since any number of DCDs can be embedded into a single page, it is normal for DCDs to see input that was not intended for them. DCDs should be programmed to gracefully ignore input that makes no sense, since that input might have been targeted at another web application on the same page.
It is also conceivable that two DCDs will see input that makes sense to both of them. It may be the case that the user is only trying to communicate with one of the DCDs, but the consequence is that both will respond, possibly with unexpected results. This is called a DCD collision. To avoid this situation:
Try to use parameter names that are likely to be unique to your DCD. Parameters like id and number are so generic that they could easily be acted upon by multiple drivers. More unique names like event_id and numWidgets are less likely to be confused.
Encode all your DCD input into a single unique parameter, which your read() method can decode and parse out into its sub-parameters.
If neither of the above is feasible, be careful about sharing page space with other DCDs that accept inputs.
The control panel of an application is the administrator back-end, for configuring and managing the application. Not all applications have or need a control panel; they inform ExSite of the existence of a control panel by responding to the "ControlPanel" ioctl request. If the return value is a string scalar, that string is taken by ExSite to be a URL that will bring up the control panel. If the return value is a code reference, ExSite assumes that is a directly callable routine that will return the control panel HTML in a string. For example:
sub ioctl { my ($this,$request) = @_; if ($request eq "ControlPanel") { # case 1) return URL of the control panel return "/cgi-bin/myapp.cgi"; # case 2) return code reference to the control panel return \&my_control_panel; } }sub my_control_panel { #...
If your application's control panel is also a form of content (that is, it can be accessed from one of your site's web pages in addition to the ExSite administrator interface), then you can simply include a hook in the write() method to call the control panel method. (This only works if your control panel method is encoded in the DCD itself; if it is an external CGI program, it cannot be used as content.) Example:
# control panel accessible from both admin interface and the site itselfsub write { my ($this,$options) = @_; if ($options eq "ControlPanel") { # insert the control panel into a site page # (probably want some access control or security checks here) return &my_control_panel; } else { ... } }
sub ioctl { my ($this,$request) = @_; if ($request eq "ControlPanel") { # admin interface has built-in security checks return \&my_control_panel; } }
sub my_control_panel { #...
A quick-and-dirty method for bringing CGI programs into ExSite is to simply run the CGI program from within the DCD, capture the content, and pass it back to ExSite for further processing. Example:
# CGI wrapper DCDsub write { my ($this,$options) = @_ # the options string is the URL of a CGI program $options = /(\w*.cgi)(?(.*))$/; my $prog = $1; $ENV{QUERY_STRING} = $2; # careful - this changes our own query string, too! # execute the CGI program my $output = `$prog`; # the ouput will include headers and extraneous HTML, so strip that gak $output =~ s/<body>(.*)<\/body>/i; return $1; }
If your web application consists of Perl CGI programs, then it is relatively easy to completely absorb them into ExSite. In the simple case, you can take each Perl CGI program and convert it to its own DCD. Your original CGI program can be reformatted as a subroutine in this DCD, and the DCD write method set up to call this particular subroutine as required. Your code should be modified to only output the relevant content, since ExSite takes care of the templates and wrappers on its own.
If you have many CGI programs that act in concert to provide all of the functions of your web application, it may be more sensible in the long run to combine them into a single DCD. Here is one method to do this:
convert each CGI program to a subroutine in the DCD module. If the CGI program also contains subroutines, those are also brought into the module, of course. The main subroutine for each script must have a unique name (it's easy just to name it similarly to the original CGI program), and should contain the necessary logic to generate the DCD-specific content and layout, but not additional content (such as HTML wrappers).
give each program an action name by which it can be selected.
configure the write() method of your DCD to act as a switch statement to choose between these actions.
add an action parameter of some kind to your query strings, to select from the various options to the write() method.
If you formally invoked your web application using a series of URLs such as:
http://mydomain.com/cgi-bin/task1.cgi http://mydomain.com/cgi-bin/task2.cgi http://mydomain.com/cgi-bin/task3.cgi
Those tasks/URLs are now invoked as:
http://mydomain.com/cgi-bin/page.cgi?_id=XXX&action=task1 http://mydomain.com/cgi-bin/page.cgi?_id=XXX&action=task2 http://mydomain.com/cgi-bin/page.cgi?_id=XXX&action=task3
where the web application has been embedded into page XXX. Additional parameters to the original CGI programs can still be used with the new URLs.
Conventionally in CGI programming you would hard-code your links to related CGI functions into your output. For example:
<form method="post" action="/cgi-bin/addpurchase.cgi"> ...
If your CGI functions have all been rolled into a single package (see above), then these links can be replaced with self-referential links, eg.
<form method="post">...
In this case you might need to provide some extra data in the form to indicate the function that is needed in this case (ie. "addpurchase"). Alternatively, you can explicitly provide self-referential links using the built-in self-link() mechanism.
Well-behaved DCDs share their input with their fellow DCDs, by using the ExSite::Input class. This is especially important when dealing with POST data, since once it is read from stdin, it is no longer available for other DCDs to read. Normally a form's input will be directed at a single DCD, so it may not seem like a big deal to share it. However, if you have several DCDs in a page that can potentially respond to form input, then the first one will grab the input to see if it makes sense to it. Without shared input, subsequent DCDs will not see the input stream, even if the first DCD did nothing with it.
See the example DCD in the appendices for an example of this.
DCDs are "called" using tags embedded in the raw HTML of the ExSite Content Management System. The HTML is only "raw" before the page is fully constructed. The content management tags are replaced during the page construction process. Once a page is written to disk as a .html or .php file, it has been fully processed. No more content management tags exist in it, and no more replacements will occur. (PHP and other server-side substitutions will still occur, however, allowing a whole second level of content management to take over...)
If you have a web applet or application called "App", then it is called from your raw HTML using a tag like this:
<!--&App.method(options)-->
where "method" is one of read, write, or ioctl. For instance:
<!--&Debug.write(POST)-->
This would call our Debug applet (from the appendices, below), invoking the write() method, with argument "POST". More formally, it results in the equivalent of these Perl statements being executed:
require Modules::Debug; my $d = new Modules::Debug; print $d->write("POST");
Note that the only "methods" you can use in these tags are the three standard methods. All others (eg. showhash(), from the Debug example) will be ignored.
It is useful to note that if no method is given in the tag, then the write() method is assumed, so the above can be written more concisely as:
<!--&Debug(POST)-->
The default output of the DCD can be invoked with no arguments, eg:
<!--&Debug()-->
AJAX/DHTML content-handling methods can be used automatically with minor changes to this notation, as described here.
Alternative read logic can be invoked in some pages but not others by calling read() as needed, for instance (using an imaginary online shopping application):
<!--&Shop.read(currency-cookie)--> <!--display customer invoice--><!--&Shop(action=invoice)-->
(Note that the middle line here is a normal HTML comment, which will pass through ExSite unchanged.)
In principle, you can call ioctl() using CMS tags as well. Since ioctls generate no visible output, this is only useful for executing certain tasks behind the scenes. For example, to receive an email whenever a certain page is viewed, you might use an ioctl like this:
<!--&Shop.ioctl(send_to=morgan@foo.com)-->
It is easy for one web application to call another. It simply has to include the tags to make the call as part of the output generated from its own write() methods. For example, if package A wants to invoke package B, it can do something like this:
# in package A...sub write { #... return "<!--&B(options)-->"; }
This feature also makes it simple to maintain dynamic content libraries, consisting of re-useable utility functions, such as clocks, calendars, and so on, which can be re-used by any other applications. For example:
<!-- display current date and time, using the ExLib DCD --> <p>Page generated on <!--&ExLib(datetime)-->.</p>
A more direct form of calling can be performed by simply using or requiring the DCD module, and then invoking its methods directly. To ensure portability and compatibility, it is best to restrict your calls to the "public" driver methods read(),
write(), and ioctl(), but in fact you are not prevented from making a call to any "private" DCD method using this trick.
# direct call to another DCD if (eval 'require Modules::OtherDCD') { my $dcd = new Modules::OtherDCD; $output = $dcd->write("option-string"); } else { $output = "OtherDCD: $!"; }
A similar effect can be achieved by inheriting another DCD, and simply overloading its methods in the cases where the new DCD differs.
Most DCDs will generate new content that is substituted for the same tag on the calling page. This effectively keeps the DCD confined to the space allocated for that tag. If the DCD is placed in a page body, that's no big deal, since the body is usually designed to expand or contract as much as necessary to hold its content. In other cases, however, the DCD may be embedded in a sidebar, header, footer, or other confined space. Rather than expanding the DCD there, you would prefer that the DCD request be served by a different DCD tag that has more room. In some cases, you want the request to be served by an entirely different page. Such DCDs are called "services" because there is a particular tag or page that services the requests that come from other tags.
If you want the DCD request to be served by a different tag on the same page, that can be accomplished by selective processing of the DCD input. For example, say the following tag generates a form to search:
<!--&MyDCD(searchform)-->
And the next tag displays search results:
<!--&MyDCD(dosearch)-->
Then, the form can be embedded in some part of the page such as a header, sidebar, or right-align <DIV>, while the results of the search are displayed elsewhere (such as the body). Only one instance of the MyDCD object is created on each page, so both of these tags will reference the same code object, and will be privy to each other's private data. Thus, the search form could be primed with the same search term that was used to produce the search results.
If you want the DCD request to be served by a different page, that is a little more involved. The DCD must be configured to run as an inter-page service, as follows:
* To bind a service to a page, you create a record in the Service table in the ExSite database (this can be done using the WebDB tool, or the Security plug-in). There are two fields that must be set:
To restrict a DCD so that it can only be used by selected websites, it must be configured similarly to an inter-page service:
* To bind a service to a site, you create a record in the Service table in the ExSite database (this can be done using the WebDB tool, or the Security plug-in). There are two fields that must be set:
Most DCDs will have many additional methods and routines; the read(), write(), and ioctl() methods are simply the ones that ExSite uses to interface to the DCD. In some cases, you may need to split your DCD into multiple Perl packages. The additional packages can also be set up as DCDs, with their own entry points (if it makes sense to do so), or they can be stand-alone packages only invoked by the single DCD.
Example: a registration or purchasing system may have a complicated series of tools for the end-user's front-end/e-commerce system and the administrator's back-end/reporting system. Although these can be placed all together into a single monolithic package, it may make sense to keep them separate, especially if the only interaction between the sub-systems is through the database. If these functions are placed together in a single package, then you will have a single write() method that can generate both end-user and administrator content, which means you will have to be more careful about security, and make more effort to keep the code easy to read. If you broke the end-user and administrator toolkits into separate DCDs, security and readability are both enhanced. In summary, a single web application does not imply a single DCD module.
In some cases it may be necessary or desirable to create libraries or modules that are not DCDs themselves. For instance, in the above example, if there is a set of functions required by both the front and back ends, it would be best to place them in a package that is loaded by both DCDs. But if this package is placed in the DCD directory, ExSite will attempt to load it up as a driver. Although ExSite tries to handle non-conforming packages gracefully, it still results in an unnecessary performance hit. In this case, supporting packages can be placed in a subdirectory of the DCD directory. ExSite will ignore them when scanning its drivers, but the drivers themselves can load the packages as needed:
cgi-bin/Modules/Reg.pm <-- user front-end DCD to registration system cgi-bin/Modules/RegAdmin.pm <-- administrator back-end DCD cgi-bin/Modules/Reg/RegLib.pm <-- shared registration system toolkit (non-DCD)
To load the RegLib.pm toolkit, the DCDs would simply include the line use Modules::Reg::RegLib.pm; at the top of the DCD modules.
Database-driven web applications can be encoded in a DCD, using all the same programming methods described in standard CGI and web database programming guides. The most important difference is that the DCD output is returned to the caller in a string, rather than printed to stdout.
ExSite has its own database interfacing tools, which have many features that may be useful to web database programmers. See the kernel programming docs for more information on these tools. The ExSite database API is only required when interacting with ExSite's default database, which is used for basic content management. This database can be extended to incorporate the tables/functions required by the local web applications, or a second ExSite-compatible database can be created and connected to separately from the content database.
If using the default database, there is no need to establish a database connection, since ExSite will already have one open. You can find this default database handle in $share{DB}.
If the developer does not want to work with the ExSite database tools at all, the DCD can perform its own database connections and interactions in any way that it chooses.
Web applications written in other languages will have relatively simple Perl DCDs that simply act as a call translation layer to interface to the rest of the application.
In the simplest case, if your web applet is packaged in an executable format, you can simply spawn it as a separate process. For example:
sub write { my ($this,$options) = @_; # myapplet can get the HTTP request data from the environment return `myapplet.exe`; }
Although nice and simple, this solution is not especially high-performance, since you pay for the overhead of launching a new process and loading and executing another program. (This is no big deal in a basic CGI setup, since you've already paid this price once to start up Perl, which may be substantially more costly than your binary.) This general technique is extensible through inter-process communication methods, outlined below.
For better performance, Perl has methods for interfacing directly to C-compatible binary libraries, so that it runs entirely within the same process. For more information, see the perlxs tools (do "man perlxs" or "man perlxstut" for more info).
Perl also has a variety of language interfacing tools for various lanaguages, including C++, Java, Python, and more. The Inline tools (see Cpan.org) allow you to inline the other languages directly into your ExSite drivers, which can greatly ease the job of "gluing" your driver to a backend that is native to another language.
If your applets run externally and command line switches are insufficient to completely determine the applet's output, then some more advanced IPC tricks will have to be used.
For example, some external applications expect input (eg. commands) to come through stdin. In this case, you will have to open a bidirectional pipe to the application so that you can send the input and capture its stdout. See the perlipc manpage for more on this. Example:
use FileHandle; use IPC::Open2; $pid = open2(*myapp_read, *myapp_write, "myapp.exe"); print myapp_write "commands\n"; $output = <myapp_read>;
Examples of this type of IPC can be found in the ExSite::Image module, which uses the external convert program to modify images.
The unidirectional pipe (eg. open MYAPP, "|myapp.exe";) will not generally work for inlining HTML content into a page, because the output will go to stdout, and will not be captured for proper insertion into the web page templates. (It may also mess up the HTTP headers of the reply, if those haven't been output yet.) However, note that secondary content such as images are fetched in separate requests that require no templating. In these cases it is safer to use unidirectional pipes that simply dump their results to stdout.
There are other tricks that can be used for passing input and output between two separated processes, such as signals, fifos (named pipes), shared memory, and plain ol' disk files.
For high performance external applets that avoid process creation overhead (which can be costly for large programs), you can consider client-server models of construction. In essence, your applet runs continuously, and ExSite simply connects to it, sends a reqeust, and receives a reply. This can be done using sockets if you wish to follow conventional client-server methods, but if full networking functionality is not necessary, you can also use shared memory or disk files for data communication, and signals to alert your server applet that a request is being made.
AJAX (asynchronous javascript and XML) and DHTML (dynamic HTML) are techniques for altering/updating page content without going through a full page reload. Essentially, content is fetched in the background and injected into the current page, possible replacing a block of content that was there previously. These techniques tend to improve the responsiveness of the page from the user's point of view, and also reduce server load, since only a fraction of the page needs to be generated at a time.
ExSite supports using AJAX/DHTML methods for dynamically fetching and refreshing DCD content that is embedded into a page. If you use the link() method from ExSite::BaseDCD to generate all internal links back to your DCD, then you do not need to know anything about AJAX, DHTML, CSS, or Javascript to implement these features. AJAX functionality is invoked automatically by adding additional ampersand characters to the DCD content tags, as follows:
<!--&Module()-->
This is the conventional syntax for invoking a DCD module with no AJAX/DHTML. The content is fetched from the DCD, and inserted directly into the page at the time the page is being built. Recursive links to the DCD are normal URLs that regenerate the entire page. This method has the advantage of accessibility (it works on all browsers, and without Javascript), and is search-engine friendly.
<!--&&Module()-->
Instead of inserting the DCD content directly into the page, this method inserts some Javascript that will fetch the DCD content in a separate HTTP request. Recursive links to the DCD are still normal URLs that will regenerate the entire page. This method is best suited for instances where you have a page that is published to a static file (especially index.html, which must be static), but certain module(s) need to remain fully dynamic (such as breaking news and announcements). Note that search engines will not be able to see the DCD content, as they generally ignore javascript.
<!--&&&Module()-->
The initial substutition is made using AJAX/DHTML, as in the previous case. However, all recursive links to the DCD are also handled using AJAX, so that the DCD is refreshed "in place" without a full page reload. This has the benefit of faster, smoother content updates, giving a more responsive user experience. However, search engines cannot see the DCD content, and furthermore, the non-default state of the page cannot be bookmarked.
Normally, you do not have to do anything to make use of the above AJAX methods. If you follow standard ExSite programming conventions, ExSite can manage all of the AJAX protocol automatically.
The most important convention to follow is in the generation of recursive links. Use the link() method provided in the BaseDCD.pm module that you should be inheriting from. Example:
# generate a reply link my $reply_url = $this->link(action=>"reply"); return "<a href=\"$reply_url\">Reply</a>\n";
The link() method will automatically build a conventional URL or a Javascript call depending on what AJAX mode the plug-in is running in.
The AJAX calls invoke a server-side program called dcd.cgi which executes the plug-in directly, and returns the bare content with no template wrapper. Inspect that code for details on this procedure.
Under the AJAX modes described above, the entire plug-in block can be refreshed using AJAX. If you want to refresh only a sub-block using AJAX methods, you can follow this template:
# ensure we are running in AJAX mode if ($share{ajax_target}) { # set your AJAX target element # (this is the DOM ID of the element you intend to modify via AJAX): my $target_id = "DOM_ID";# set the plain URL to obtain the new content from the plug-in. # If we are running in AJAX mode, this should give us a dcd.cgi URL. my $plain_url = ExSite::Misc::relink(%options);
# the Javascript to make the AJAX substitution # (ensure that you are also loading /_ExSite/js/httprequest.js) my $ajax_js = "subRequestData('$target_id','$url')";
# you can use the following value in an HTML anchor's HREF attribute my $ajax_url = "javascript:$ajax_js";
# alternatively, you can build a complete anchor tag: my $ajax_anchor = "<a href=\"#\" onclick=\"$ajax_js\">Click here</a>"; }
DCDs may define their own publish methods. This is particularly useful if the web application uploads or creates content in the form of auxiliary files such as images or documents. Publishing these files to disk will usually result in higher webserving performance than retrieving them from a database. If there is a significant performance hit in generating the DCD's normal text data, there may also be an advantage to pre-publishing some text elements so they do not have to be completely regenerated on each page view. It is entirely up to the individual web application to determine whether any of its content may benefit from being prepublished in this way. It is also up to the DCD to determine how to find its published files and whether or not they have been published or must be generated from scratch.
To configure your DCD for publishing, create an ioctl request,
Publish, which returns a code reference to your internal publish method. For example:
sub ioctl { my $this = shift; # DCD object shift; # ioctl request in $_ if (/Publish/) return \&my_publisher; } }sub my_publisher { # code to publish this DCD's files # ... }
Although you could invoke your publishing method directly from your control panel or write methods, in practice this will fail under most webserving configurations. That's because publishing requires special priveleges to write files, which normal webserver processes do not have for security reasons. ExSite deals with this by restricting all file writing operations to a special publishing program (publish.cgi), and gives this one program sufficient priveleges to write to files. Only publish.cgi is able to invoke your DCD's publish method with sufficient privelges to write to files, when invoked from over the web.
By convention, DCD content should be published to
_Modules/ModuleName/.... As of version 3.2, ExSite does not enforce this, so in principle you can publish files anywhere on the system that you have write access to. Obviously you should be extremely careful about publishing elsewhere, to avoid overwriting files that do not originate from your DCD.
To invoke your DCD's publisher over the web, use the following URL:
/cgi-bin/publish.cgi?module=MyDCD
If you require finer-grained control over your publishing than a simple catch-all publish URL that publishes the entire DCD, then your publish method must be configured to look for additional inputs in the query string of publish.cgi, and interpret/validate them appropriately. For instance, if you want to publish only the photos for a certain article in your News module, then you could use a URL like this:
/cgi-bin/publish.cgi?module=News&article=1234
Your News DCD's publish method is responsible for finding and reading the article parameter, and knowing what to do with it (including authorizing the user to perform this action).
ExSite's generic publish tools will only include links to publish the whole DCD. If you need more specialized publishing links, you must include them in your control panel output.
Unpublishing consists of removing files that you had previously published. It normally occurs when records are deleted from your DCD's data. If any of those deletions involve files that were published to disk, then it is a good idea to remove those published files to keep things tidy.
Unpublishing works in much the same manner as publishing. Write your unpublish method, which performs whatever file deletions are appropriate (be careful!), and create an ioctl("Unpublish") request that returns a reference to this method. Unpublishing occurs via a url such as this:
/cgi-bin/publish.cgi?-module=MyDCD
(Note the - symbol in front of "module", indicating that publish.cgi should be removing files instead of creating them.) If you have not defined an Unpublish ioctl request, this URL will fail with an error message.
Just as with normal publishing, you can include additional parameters if your DCD's unpublish method knows how to interpret them.
Plug-ins are free to implement their own private search tools by defining an internal search method and form. The plug-in can use its own conventions for the search form and reporting style. From the point of view of ExSite, a private search tool is just like any other DCD output. This may be easier in some cases from a programming perspective, but it also means that you could have numerous different search tools on your site, depending on the specific type of content being searched form.
Alternatively, you may wish to make your plug-in's content available to generic system searches, ie. a simple keyword search on a site-wide search form. Then you could search for your plug-in's special content using the same search tool that is used to searching the rest of the website.
ExSite's search system indexes text on the default view of each page. It does not follow links deeper into a particular plug-in to find new content. You only need to concern yourself with indexing "deep" plug-in content if the default front-page view of the plug-in content is insufficient.
Instructions for tying in to the Search system are provided in the ExSite Search Guide.
The following suggestions will produce ExSite DCDs that are interoperable, extensible, and which make fullest use of the ExSite framework.
For example, use a uniquely-named command parameter, such as mycmd
and set it to the various module functions, eg.
page.cgi?_id=123&mycmd=reportOther parameters needed by each specific functions can also be included, of course.
<!--&Module(x=1&y=6&fcmd=start)-->
<div class="MyModule"> ... </div>This makes it easier to apply module-specific CSS style controls, if the site wants them, eg.
/* MyModule text should be small and green */ div.MyModule p { font-size:9pt; color:#009900; }
This first example is ExSite's equivalent of "Hello World". It inherits from BaseDCD, which allows us to skip over many of the basic DCD methods in our class.
package Modules::HelloWorld; use strict; use Modules::BaseDCD; use vars (@ISA); @ISA = qw(Modules::BaseDCD);sub write { my ($this,$options) = @_; # we accept the $options string, but ignore it return "Hello World!"; }
# we also say hi to admins!
sub ioctl { my ($this,$request) = @_; if ($request eq "ControlPanel") { return \&ctrl_panel; } }
sub ctrl_panel { my $this = shift; return "Hello Underworld!"; }
1;
This example is slightly more complicated. It can be used to inspect the server and application environment for debugging purposes. Note that it is written in Object-Oriented Perl, and uses the ExSite::Input class library to share input with other DCDs. In addition to the three standard DCD methods, there is also a class constructor (new()), and a private method (showhash()). It is not using BaseDCD and so has to define all module methods.
package Modules::Debug;use strict; use ExSite::Input;
# new : called implicitly once per page to instantiate the applet, # when the DCD is first invoked
sub new { my $this = shift; my $obj = {}; my $class = ref($this) || $this; bless $obj, $class;
# call the read method (no special args) when we create the DCD # so that we don't have to invoke read() explicitly. $obj->read;
return $obj; }
sub read { my ($this,$opt) = @_; # note: $opt not used # use Input class to grab any form input. The Input class saves # all input so that it can be re-used by other DCDs. my $in = new ExSite::Input;
# retrieve and parse any data passed by POST method, # save the data in this object $this->{post_data} = $in->post;
# retrieve and parse any data passed by GET method # save the data in this object $this->{get_data} = $in->get; }
sub write { my ($this,$options) = @_; # allow user to specify what info they want to see in the option string if ($options =~ /POST/) { return $this->showhash($this->{post_data}); } if ($options =~ /GET/) { return $this->showhash($this->{get_data}); } else { # by default, show the server environment return $this->showhash(\%ENV); } }
sub ioctl { my ($this,$request) = @_;
# The module name response is not necessary, since it is the same # as the DCD name, which is used by default.
if ($request eq "ModuleName") { return "Debug"; }
# There is no real control panel for this module, but for the sake # of illustration, we will display the server environment as a # "control panel". We do this simply by passing a reference to # the write() method, since that's exactly what it does with no # special args.
if ($request eq "ControlPanel") { return \&write; } }
# a private DCD routine that cannot be called externally from the # administrator WebTop or from content management tags.
sub showhash { my ($this,$href) = @_; my $out; # displays the keys and values of a hash while (my ($key,$val) = each %$href) { $out .= "$key = $val<br>\n"; } return $out; }
1;