Projekt

Allgemein

Profil

Herunterladen (6,2 KB) Statistiken
| Zweig: | Markierung: | Revision:
7af2b128 Moritz Bunkus
package SL::ClientJS;

use strict;

use parent qw(Rose::Object);

use Carp;
use SL::JSON ();

use Rose::Object::MakeMethods::Generic
(
'scalar --get_set_init' => [ qw(_actions) ],
);

my %supported_methods = (
# Basic effects
hide => 1,
show => 1,
toggle => 1,

# DOM insertion, around
unwrap => 1,
wrap => 2,
wrapAll => 2,
wrapInner => 2,

# DOM insertion, inside
append => 2,
appendTo => 2,
html => 2,
prepend => 2,
prependTo => 2,
text => 2,

# DOM insertion, outside
after => 2,
before => 2,
insertAfter => 2,
insertBefore => 2,

# DOM removal
empty => 1,
remove => 1,

# DOM replacement
replaceAll => 2,
replaceWith => 2,

# General attributes
attr => 3,
prop => 3,
removeAttr => 2,
removeProp => 2,
val => 2,

# Data storage
data => 3,
removeData => 2,
);

sub AUTOLOAD {
our $AUTOLOAD;

my ($self, @args) = @_;

my $method = $AUTOLOAD;
$method =~ s/.*:://;
return if $method eq 'DESTROY';

my $num_args = $supported_methods{$method};
$::lxdebug->message(0, "autoload method $method");

croak "Unsupported jQuery action: $method" unless defined $num_args;
croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if scalar(@args) != $num_args;

if ($num_args) {
# Force flattening from SL::Presenter::EscapedText: "" . $...
$args[0] = "" . $args[0];
$args[0] =~ s/^\s+//;
}

push @{ $self->_actions }, [ $method, @args ];

return $self;
}

sub init__actions {
return [];
}

sub to_json {
my ($self) = @_;
return SL::JSON::to_json({ eval_actions => $self->_actions });
}

sub to_array {
my ($self) = @_;
return $self->_actions;
}

4d9d7d51 Moritz Bunkus
sub render {
my ($self, $controller) = @_;
return $controller->render(\$self->to_json, { type => 'json' });
}

7af2b128 Moritz Bunkus
1;
__END__

=pod

=encoding utf8

=head1 NAME

SL::ClientJS - Easy programmatic client-side JavaScript generation
with jQuery

=head1 SYNOPSIS

First some JavaScript code:

// In the client generate an AJAX request whose 'success' handler
// calls "eval_json_response(data)":
var data = {
action: "SomeController/the_action",
id: $('#some_input_field').val()
};
$.post("controller.pl", data, eval_json_response);

Now some Perl code:

# In the controller itself. First, make sure that the "client_js.js"
# is loaded. This must be done when the whole side is loaded, so
# it's not in the action called by the AJAX request shown above.
$::request->layout->use_javascript('client_js.js');

# Now in that action called via AJAX:
sub action_the_action {
my ($self) = @_;

# Create a new client-side JS object and do stuff with it!
my $js = SL::ClientJS->new;

# Show some element on the page:
$js->show('#usually_hidden');

# Set to hidden inputs. Yes, calls can be chained!
$js->val('#hidden_id', $self->new_id)
->val('#other_type', 'Unicorn');

# Replace some HTML code:
my $html = $self->render('SomeController/the_action', { output => 0 });
$js->html('#id_with_new_content', $html);

# Finally render the JSON response:
$self->render($js);
}

=head1 OVERVIEW

This module enables the generation of jQuery-using JavaScript code on
the server side. That code is then evaluated in a safe way on the
client side.

The workflow is usally that the client creates an AJAX request, the
server creates some actions and sends them back, and the client then
implements each of these actions.

There are three things that need to be done for this to work:

=over 2

=item 1. The "client_js.js" has to be loaded before the AJAX request is started.

=item 2. The client code needs to call C<eval_json_response()> with the result returned from the server.

=item 3. The server must use this module.

=back

The functions called on the client side are mostly jQuery
functions. Other functionality may be added later.

Note that L<SL::Controller/render> is aware of this module which saves
you some boilerplate. The following two calls are equivalent:

$controller->render($client_js);
$controller->render(\$client_js->to_json, { type => 'json' });

=head1 FUNCTIONS NOT PASSED TO THE CLIENT SIDE

=over 4

=item C<to_array>

Returns the actions gathered so far as an array reference. Each
element is an array reference containing at least two items: the
function's name and what it is called on. Additional array elements
are the function parameters.

=item C<to_json>

Returns the actions gathered so far as a JSON string ready to be sent
to the client.

4d9d7d51 Moritz Bunkus
=item C<render $controller>

Renders C<$self> via the controller. Useful for chaining. Equivalent
to the following:

$controller->render(\$self->to_json, { type => 'json' });

7af2b128 Moritz Bunkus
=back

=head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE

=head2 JQUERY FUNCTIONS

The following jQuery functions are supported:

=over 4

=item Basic effects

C<hide>, C<show>, C<toggle>

=item DOM insertion, around

C<unwrap>, C<wrap>, C<wrapAll>, C<wrapInner>

=item DOM insertion, inside

C<append>, C<appendTo>, C<html>, C<prepend>, C<prependTo>, C<text>

=item DOM insertion, outside

C<after>, C<before>, C<insertAfter>, C<insertBefore>

=item DOM removal

C<empty>, C<remove>

=item DOM replacement

C<replaceAll>, C<replaceWith>

=item General attributes

C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>

=item Data storage

C<data>, C<removeData>

=back

=head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS

In order not having to maintain two files (this one and
C<js/client_js.js>) there's a script that can parse this file's
C<%supported_methods> definition and convert it into the appropriate
code ready for manual insertion into C<js/client_js.js>. The steps
are:

=over 2

=item 1. Add lines in this file to the C<%supported_methods> hash. The
key is the function name and the value is the number of expected
parameters.

=item 2. Run C<scripts/generate_client_js_actions.pl>

=item 3. Edit C<js/client_js.js> and replace the type casing code with
the output generated in step 2.

=back

=head1 BUGS

Nothing here yet.

=head1 AUTHOR

Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>

=cut