Catalyst+Ajax: How to get a preview pane for a textarea which even supports live previewing of JavaScript
Published · Monday, 16 March 2009 (Updated · 20 June 2009)
Another shorty. Click on command lines to see results or further needed input/action.
This approach may seem a bit strange—and could certainly be refined to auto-size the iframe as well as smooth loading and apply an intent timer—but it’s the only way to get a preview pane to work with arbitrary rich content; i.e., JS and Flash. I originally wrote a more invovled version of this for a client to do live previews of rich media advertisements before saving.
Create your test application
jinx@jasper[467]~>catalyst.pl MyApp
created "MyApp" created "MyApp/script" created "MyApp/lib" created "MyApp/root" created "MyApp/root/static" created "MyApp/root/static/images" created "MyApp/t" created "MyApp/lib/MyApp" created "MyApp/lib/MyApp/Model" created "MyApp/lib/MyApp/View" created "MyApp/lib/MyApp/Controller" created "MyApp/myapp.conf" created "MyApp/lib/MyApp.pm" created "MyApp/lib/MyApp/Controller/Root.pm" created "MyApp/README" created "MyApp/Changes" created "MyApp/t/01app.t" created "MyApp/t/02pod.t" created "MyApp/t/03podcoverage.t" created "MyApp/root/static/images/catalyst_logo.png" created "MyApp/root/static/images/btn_120x50_built.png" created "MyApp/root/static/images/btn_120x50_built_shadow.png" created "MyApp/root/static/images/btn_120x50_powered.png" created "MyApp/root/static/images/btn_120x50_powered_shadow.png" created "MyApp/root/static/images/btn_88x31_built.png" created "MyApp/root/static/images/btn_88x31_built_shadow.png" created "MyApp/root/static/images/btn_88x31_powered.png" created "MyApp/root/static/images/btn_88x31_powered_shadow.png" created "MyApp/root/favicon.ico" created "MyApp/Makefile.PL" created "MyApp/script/myapp_cgi.pl" created "MyApp/script/myapp_fastcgi.pl" created "MyApp/script/myapp_server.pl" created "MyApp/script/myapp_test.pl" created "MyApp/script/myapp_create.pl"
jinx@jasper[468]~>cd MyApp/
Create a Template view
jinx@jasper[469]~/MyApp>./script/myapp_create.pl view TT TT
created "/Users/jinx/MyApp/script/../lib/MyApp/View" exists "/Users/jinx/MyApp/script/../t" created "/Users/jinx/MyApp/script/../lib/MyApp/View/TT.pm" exists "/Users/jinx/MyApp/script/../t/view_TT.t"
Make the index template
jinx@jasper[470]~/MyApp>emacs root/index.tt
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"
xml:lang="en-US">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>Ajax+Catalyst live preview</title>
<style type="text/css" media="screen">
/* style stub for you */
</style>
<script type="text/javascript"
src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.js">
</script>
</head>
<body>
<h1>Preview pane with Catalyst+Ajax</h1>
<div style="text-align:center;margin: 5px auto;">
<iframe style="border:0; width:98%; padding: 0; margin: 0; height:110px;"
class="preview"
src="[% c.uri_for("/preview") %]"
></iframe>
</div>
[% form %]
<h3>Here is some input to try-</h3>
<pre>
<script type="text/javascript"
src="http://elektrum.org/js/greeking.js?pc=1;delim=blockquote;wc=17">
</script>
</pre>
<pre>
<a href="http://somafm.com/"><img src="http://somafm.com/linktous/728x90sfm.jpg"
style="border:0" alt="SomaFM independent internet radio"></a>
</pre>
<pre>
<script type="text/javascript" src="http://sedition.com/js/blog.js?t=random">
</script>
</pre>
<script type="text/javascript"><!--//--><![CDATA[//><!--
jQuery(function($){
$("textarea[name='ad']")
.bind("keyup", // "change" would be much lower impact.
function(){
var xhtml = $(this).val();
$.ajax({
type: "POST"
,url: "[% c.uri_for("/preview") %]"
,data: { preview: xhtml }
,success: function(ok){
var fr = $("iframe.preview").clone();
// Amounts to a hard refresh by replacing node with its clone.
$("iframe.preview").after(fr).remove();
}
});
});
});
//--><!]]> </script>
</body>
</html>Make its FormFu configuration file
jinx@jasper[471]~/MyApp>emacs root/forms/index.yml
---
auto_fieldset:
legend: Preview your textarea
elements:
- type: Textarea
name: ad
label: Enter raw XHTML to preview above
add_attributes:
style: "width:100%; height: 8em; padding: 2px;"
Edit the application file proper to have cache available.
jinx@jasper[472]~/MyApp>emacs lib/MyApp.pm
package MyApp;
use strict;
use warnings;
use Catalyst::Runtime '5.70';
use parent qw/Catalyst/;
use Catalyst qw( -Debug
ConfigLoader
Cache::FastMmap
Static::Simple
);
__PACKAGE__->setup();
1;Edit the Root controller to display the index and deliver the preview to the iframe
jinx@jasper[473]~/MyApp>emacs lib/MyApp/Controller/Root.pm
package MyApp::Controller::Root;
use strict;
use warnings;
use parent 'Catalyst::Controller::HTML::FormFu';
use XML::LibXML;
__PACKAGE__->config->{namespace} = "";
sub index :Path Args(0) FormConfig {
my ( $self, $c ) = @_;
$c->forward($c->view("TT"));
}
sub preview : Local Args(0) {
my ( $self, $c ) = @_;
my $parser = XML::LibXML->new();
if ( $c->request->method eq 'POST' )
{
my $doc = eval {
$parser->parse_html_string("<body>"
. $c->request->param("preview")
. "</body>")
};
$c->cache->set("preview", $doc->serialize()) if $doc;
$c->response->content_type("text/plain");
$c->response->body("OK");
}
else
{
$c->response->content_type("text/html");
$c->response->body( $c->cache->get("preview") );
}
}
1;Start your app and start playing
jinx@jasper[474]~/MyApp>./script/myapp_server.pl -r
[debug] Debug messages enabled [debug] Statistics enabled [debug] Loaded plugins: .----------------------------------------------------------------------------. | Catalyst::Plugin::Cache::FastMmap 0.6 | | Catalyst::Plugin::ConfigLoader 0.22 | | Catalyst::Plugin::Static::Simple 0.20 | '----------------------------------------------------------------------------' [debug] Loaded dispatcher "Catalyst::Dispatcher" [debug] Loaded engine "Catalyst::Engine::HTTP::Restarter" [debug] Found home "/Users/jinx/MyApp" [debug] Loaded Config "/Users/jinx/MyApp/myapp.conf" [debug] Loaded components: .-----------------------------------------------------------------+----------. | Class | Type | +-----------------------------------------------------------------+----------+ | MyApp::Controller::Root | instance | | MyApp::View::TT | instance | '-----------------------------------------------------------------+----------' [debug] Loaded Private actions: .----------------------+--------------------------------------+--------------. | Private | Class | Method | +----------------------+--------------------------------------+--------------+ | /index | MyApp::Controller::Root | index | | /preview | MyApp::Controller::Root | preview | '----------------------+--------------------------------------+--------------' [debug] Loaded Path actions: .-------------------------------------+--------------------------------------. | Path | Private | +-------------------------------------+--------------------------------------+ | / | /index | | /preview | /preview | '-------------------------------------+--------------------------------------' [info] MyApp powered by Catalyst 5.71000 You can connect to your server at http://jasper.local:3000
See also: jQuery to make a preview pane of a textfield.
« The shortest Ajax+Catalyst tutorial in the world
« Catalyst related articles »
