5 use parent qw(Rose::Object);
10 use Rose::Object::MakeMethods::Generic
12 'scalar --get_set_init' => [ qw(_actions _flash _error) ],
15 my %supported_methods = (
16 # ## Non-jQuery methods ##
17 flash => 2, # display_flash(<TARGET>, <ARGS>)
26 # DOM insertion, around
32 # DOM insertion, inside
40 # DOM insertion, outside
73 # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
75 # Operations on the whole tree
79 # Opening and closing nodes
80 'jstree:open_node' => 2,
81 'jstree:open_all' => 2,
82 'jstree:close_node' => 2,
83 'jstree:close_all' => 2,
84 'jstree:toggle_node' => 2,
85 'jstree:save_opened' => 1,
89 'jstree:create_node' => 4,
90 'jstree:rename_node' => 3,
91 'jstree:delete_node' => 2,
92 'jstree:move_node' => 5,
94 # Selecting nodes (from the 'ui' plugin to jstree)
95 'jstree:select_node' => 2, # $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>, true)
96 'jstree:deselect_node' => 2,
97 'jstree:deselect_all' => 1,
100 redirect_to => 1, # window.location.href = <TARGET>
106 my ($self, @args) = @_;
108 my $method = $AUTOLOAD;
110 return if $method eq 'DESTROY';
111 return $self->action($method, @args);
115 my ($self, $method, @args) = @_;
117 $method = (delete($self->{_prefix}) || '') . $method;
118 my $num_args = $supported_methods{$method};
120 croak "Unsupported jQuery action: $method" unless defined $num_args;
121 croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if scalar(@args) != $num_args;
124 # Force flattening from SL::Presenter::EscapedText: "" . $...
125 $args[0] = "" . $args[0];
126 $args[0] =~ s/^\s+//;
129 push @{ $self->_actions }, [ $method, @args ];
135 my ($self, $condition, @args) = @_;
137 return $condition ? $self->action(@args) : $self;
155 return SL::JSON::to_json({ error => $self->_error }) if $self->_error;
156 return SL::JSON::to_json({ eval_actions => $self->_actions });
161 return $self->_actions;
165 my ($self, $controller) = @_;
166 return $controller->render(\$self->to_json, { type => 'json' });
171 $self->{_prefix} = 'jstree:';
176 my ($self, $type, @messages) = @_;
178 my $message = join ' ', grep { $_ } @messages;
180 if (!$self->_flash->{$type}) {
181 $self->_flash->{$type} = [ 'flash', $type, $message ];
182 push @{ $self->_actions }, $self->_flash->{$type};
184 $self->_flash->{$type}->[-1] .= ' ' . $message;
191 my ($self, @messages) = @_;
193 $self->_error(join ' ', grep { $_ } ($self->_error, @messages));
207 SL::ClientJS - Easy programmatic client-side JavaScript generation
212 First some JavaScript code:
214 // In the client generate an AJAX request whose 'success' handler
215 // calls "eval_json_response(data)":
217 action: "SomeController/the_action",
218 id: $('#some_input_field').val()
220 $.post("controller.pl", data, eval_json_response);
224 # In the controller itself. First, make sure that the "client_js.js"
225 # is loaded. This must be done when the whole side is loaded, so
226 # it's not in the action called by the AJAX request shown above.
227 $::request->layout->use_javascript('client_js.js');
229 # Now in that action called via AJAX:
230 sub action_the_action {
233 # Create a new client-side JS object and do stuff with it!
234 my $js = SL::ClientJS->new;
236 # Show some element on the page:
237 $js->show('#usually_hidden');
239 # Set to hidden inputs. Yes, calls can be chained!
240 $js->val('#hidden_id', $self->new_id)
241 ->val('#other_type', 'Unicorn');
243 # Replace some HTML code:
244 my $html = $self->render('SomeController/the_action', { output => 0 });
245 $js->html('#id_with_new_content', $html);
247 # Operations on a jstree: rename a node and select it
248 my $text_block = SL::DB::RequirementSpecTextBlock->new(id => 4711)->load;
249 $js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
250 ->jstree->select_node('#tb-' . $text_block->id);
252 # Finally render the JSON response:
255 # Rendering can also be chained, e.g.
256 $js->html('#selector', $html)
262 This module enables the generation of jQuery-using JavaScript code on
263 the server side. That code is then evaluated in a safe way on the
266 The workflow is usally that the client creates an AJAX request, the
267 server creates some actions and sends them back, and the client then
268 implements each of these actions.
270 There are three things that need to be done for this to work:
274 =item 1. The "client_js.js" has to be loaded before the AJAX request is started.
276 =item 2. The client code needs to call C<eval_json_response()> with the result returned from the server.
278 =item 3. The server must use this module.
282 The functions called on the client side are mostly jQuery
283 functions. Other functionality may be added later.
285 Note that L<SL::Controller/render> is aware of this module which saves
286 you some boilerplate. The following two calls are equivalent:
288 $controller->render($client_js);
289 $controller->render(\$client_js->to_json, { type => 'json' });
291 =head1 FUNCTIONS NOT PASSED TO THE CLIENT SIDE
297 Returns the actions gathered so far as an array reference. Each
298 element is an array reference containing at least two items: the
299 function's name and what it is called on. Additional array elements
300 are the function parameters.
304 Returns the actions gathered so far as a JSON string ready to be sent
307 =item C<render $controller>
309 Renders C<$self> via the controller. Useful for chaining. Equivalent
312 $controller->render(\$self->to_json, { type => 'json' });
316 Tells C<$self> that the next action is to be called on a jstree
317 instance. For example:
319 $js->jstree->rename_node('tb-' . $text_block->id, $text_block->title);
323 =head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE
325 =head2 GENERIC FUNCTION
327 All of the following functions can be invoked in two ways: either by
328 calling the function name directly on C<$self> or by calling
329 L</action> with the function name as the first parameter. Therefore
330 the following two calls are identical:
332 $js->insertAfter($html, '#some-id');
333 $js->action('insertAfter', $html, '#some-id');
335 The second form, calling L</action>, is more to type but can be useful
336 in situations in which you have to call one of two functions depending
337 on context. For example, when you want to insert new code in a
338 list. If the list is empty you might have to use C<appendTo>, if it
339 isn't you might have to use C<insertAfter>. Example:
341 my $html = $self->render(...);
342 $js->action($list_is_empty ? 'appendTo' : 'insertAfter', $html, '#text-block-' . ($list_is_empty ? 'list' : $self->text_block->id));
346 my $html = $self->render(...);
347 if ($list_is_empty) {
348 $js->appendTo($html, '#text-block-list');
350 $js->insertAfter($html, '#text-block-' . $self->text_block->id);
353 The first variation is obviously better suited for chaining.
357 =item C<action $method, @args>
359 Call the function with the name C<$method> on C<$self> with arguments
360 C<@args>. Returns the return value of the actual function
361 called. Useful for chaining (see above).
363 =item C<action_if $condition, $method, @args>
365 Call the function with the name C<$method> on C<$self> with arguments
366 C<@args> if C<$condition> is trueish. Does nothing otherwise.
368 Returns the return value of the actual function called if
369 C<$condition> is trueish and C<$self> otherwise. Useful for chaining
372 This function is equivalent to the following:
375 $obj->$method(@args);
378 But it is easier to integrate into a method call chain, e.g.:
380 $js->html('#content', $html)
381 ->action_if($item->is_flagged, 'toggleClass', '#marker', 'flagged')
386 =head2 ADDITIONAL FUNCTIONS
390 =item C<flash $type, $message>
392 Display a C<$message> in the flash of type C<$type>. Multiple calls of
393 C<flash> on the same C<$self> will be merged by type.
395 On the client side the flash of this type will be cleared before the
398 =item C<error $message>
400 Causes L<to_json> (and therefore L<render>) to output a JSON object
401 that only contains an C<error> field set to this C<$message>. The
402 client will then show the message in the 'error' flash.
404 The messages of multiple calls of C<error> on the same C<$self> will
407 =item C<redirect_to $url>
409 Redirects the browser window to the new URL by setting the JavaScript
410 property C<window.location.href>. Note that
411 L<SL::Controller::Base/redirect_to> is AJAX aware and uses this
412 function if the current request is an AJAX request as determined by
413 L<SL::Request/is_ajax>.
417 =head2 JQUERY FUNCTIONS
419 The following jQuery functions are supported:
425 C<hide>, C<show>, C<toggle>
427 =item DOM insertion, around
429 C<unwrap>, C<wrap>, C<wrapAll>, C<wrapInner>
431 =item DOM insertion, inside
433 C<append>, C<appendTo>, C<html>, C<prepend>, C<prependTo>, C<text>
435 =item DOM insertion, outside
437 C<after>, C<before>, C<insertAfter>, C<insertBefore>
443 =item DOM replacement
445 C<replaceAll>, C<replaceWith>
447 =item General attributes
449 C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>
453 C<data>, C<removeData>
461 =head2 JSTREE JQUERY PLUGIN
463 The following functions of the C<jstree> plugin to jQuery are
468 =item Operations on the whole tree
472 =item Opening and closing nodes
474 C<open_node>, C<close_node>, C<toggle_node>, C<open_all>,
475 C<close_all>, C<save_opened>, C<reopen>
477 =item Modifying nodes
479 C<rename_node>, C<delete_node>, C<move_node>
481 =item Selecting nodes (from the 'ui' jstree plugin)
483 C<select_node>, C<deselect_node>, C<deselect_all>
487 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
489 In order not having to maintain two files (this one and
490 C<js/client_js.js>) there's a script that can parse this file's
491 C<%supported_methods> definition and generate the file
492 C<js/client_js.js> accordingly. The steps are:
496 =item 1. Add lines in this file to the C<%supported_methods> hash. The
497 key is the function name and the value is the number of expected
500 =item 2. Run C<scripts/generate_client_js_actions.pl>. It will
501 generate C<js/client_js.js> automatically.
503 =item 3. Reload the files in your browser (cleaning its cache can also
508 The template file used for generated C<js/client_js.js> is
509 C<scripts/generate_client_js_actions.tpl>.
517 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>