Catalyst+Ajax: How to get a preview pane for a textarea which even supports live previewing of JavaScript

Published · Monday, 16 March 2009 (Updated · 7 March 2010)

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>
&lt;script type="text/javascript"
 src="http://elektrum.org/js/greeking.js?pc=1;delim=blockquote;wc=17"&gt;
&lt;/script&gt;
</pre>

<pre>
&lt;a href="http://somafm.com/"&gt;&lt;img src="http://somafm.com/linktous/728x90sfm.jpg"
style="border:0" alt="SomaFM independent internet radio"&gt;&lt;/a&gt;
</pre>

<pre>
&lt;script type="text/javascript" src="http://sedition.com/js/blog.js?t=random"&gt;
&lt;/script&gt;
</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.



digg stumbleupon del.icio.us reddit Fark Technorati Faves

« The shortest Ajax+Catalyst tutorial in the world
« Catalyst related articles »