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
68 # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
70 # Operations on the whole tree
74 # Opening and closing nodes
75 'jstree:open_node' => 2,
76 'jstree:open_all' => 2,
77 'jstree:close_node' => 2,
78 'jstree:close_all' => 2,
79 'jstree:toggle_node' => 2,
80 'jstree:save_opened' => 1,
84 'jstree:rename_node' => 3,
85 'jstree:delete_node' => 2,
86 'jstree:move_node' => 5,
88 # Selecting nodes (from the 'ui' plugin to jstree)
89 'jstree:select_node' => 2, # $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>, true)
90 'jstree:deselect_node' => 2,
91 'jstree:deselect_all' => 1,
97 my ($self, @args) = @_;
99 my $method = $AUTOLOAD;
101 return if $method eq 'DESTROY';
102 return $self->action($method, @args);
106 my ($self, $method, @args) = @_;
108 $method = (delete($self->{_prefix}) || '') . $method;
109 my $num_args = $supported_methods{$method};
111 croak "Unsupported jQuery action: $method" unless defined $num_args;
112 croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if scalar(@args) != $num_args;
115 # Force flattening from SL::Presenter::EscapedText: "" . $...
116 $args[0] = "" . $args[0];
117 $args[0] =~ s/^\s+//;
120 push @{ $self->_actions }, [ $method, @args ];
140 return SL::JSON::to_json({ error => $self->_error }) if $self->_error;
141 return SL::JSON::to_json({ eval_actions => $self->_actions });
146 return $self->_actions;
150 my ($self, $controller) = @_;
151 return $controller->render(\$self->to_json, { type => 'json' });
156 $self->{_prefix} = 'jstree:';
161 my ($self, $type, @messages) = @_;
163 my $message = join ' ', grep { $_ } @messages;
165 if (!$self->_flash->{$type}) {
166 $self->_flash->{$type} = [ 'flash', $type, $message ];
167 push @{ $self->_actions }, $self->_flash->{$type};
169 $self->_flash->{$type}->[-1] .= ' ' . $message;
176 my ($self, @messages) = @_;
178 $self->_error(join ' ', grep { $_ } ($self->_error, @messages));
192 SL::ClientJS - Easy programmatic client-side JavaScript generation
197 First some JavaScript code:
199 // In the client generate an AJAX request whose 'success' handler
200 // calls "eval_json_response(data)":
202 action: "SomeController/the_action",
203 id: $('#some_input_field').val()
205 $.post("controller.pl", data, eval_json_response);
209 # In the controller itself. First, make sure that the "client_js.js"
210 # is loaded. This must be done when the whole side is loaded, so
211 # it's not in the action called by the AJAX request shown above.
212 $::request->layout->use_javascript('client_js.js');
214 # Now in that action called via AJAX:
215 sub action_the_action {
218 # Create a new client-side JS object and do stuff with it!
219 my $js = SL::ClientJS->new;
221 # Show some element on the page:
222 $js->show('#usually_hidden');
224 # Set to hidden inputs. Yes, calls can be chained!
225 $js->val('#hidden_id', $self->new_id)
226 ->val('#other_type', 'Unicorn');
228 # Replace some HTML code:
229 my $html = $self->render('SomeController/the_action', { output => 0 });
230 $js->html('#id_with_new_content', $html);
232 # Operations on a jstree: rename a node and select it
233 my $text_block = SL::DB::RequirementSpecTextBlock->new(id => 4711)->load;
234 $js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
235 ->jstree->select_node('#tb-' . $text_block->id);
237 # Finally render the JSON response:
240 # Rendering can also be chained, e.g.
241 $js->html('#selector', $html)
247 This module enables the generation of jQuery-using JavaScript code on
248 the server side. That code is then evaluated in a safe way on the
251 The workflow is usally that the client creates an AJAX request, the
252 server creates some actions and sends them back, and the client then
253 implements each of these actions.
255 There are three things that need to be done for this to work:
259 =item 1. The "client_js.js" has to be loaded before the AJAX request is started.
261 =item 2. The client code needs to call C<eval_json_response()> with the result returned from the server.
263 =item 3. The server must use this module.
267 The functions called on the client side are mostly jQuery
268 functions. Other functionality may be added later.
270 Note that L<SL::Controller/render> is aware of this module which saves
271 you some boilerplate. The following two calls are equivalent:
273 $controller->render($client_js);
274 $controller->render(\$client_js->to_json, { type => 'json' });
276 =head1 FUNCTIONS NOT PASSED TO THE CLIENT SIDE
282 Returns the actions gathered so far as an array reference. Each
283 element is an array reference containing at least two items: the
284 function's name and what it is called on. Additional array elements
285 are the function parameters.
289 Returns the actions gathered so far as a JSON string ready to be sent
292 =item C<render $controller>
294 Renders C<$self> via the controller. Useful for chaining. Equivalent
297 $controller->render(\$self->to_json, { type => 'json' });
301 Tells C<$self> that the next action is to be called on a jstree
302 instance. For example:
304 $js->jstree->rename_node('tb-' . $text_block->id, $text_block->title);
308 =head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE
310 =head2 GENERIC FUNCTION
312 All of the following functions can be invoked in two ways: either by
313 calling the function name directly on C<$self> or by calling
314 L</action> with the function name as the first parameter. Therefore
315 the following two calls are identical:
317 $js->insertAfter($html, '#some-id');
318 $js->action('insertAfter', $html, '#some-id');
320 The second form, calling L</action>, is more to type but can be useful
321 in situations in which you have to call one of two functions depending
322 on context. For example, when you want to insert new code in a
323 list. If the list is empty you might have to use C<appendTo>, if it
324 isn't you might have to use C<insertAfter>. Example:
326 my $html = $self->render(...);
327 $js->action($list_is_empty ? 'appendTo' : 'insertAfter', $html, '#text-block-' . ($list_is_empty ? 'list' : $self->text_block->id));
331 my $html = $self->render(...);
332 if ($list_is_empty) {
333 $js->appendTo($html, '#text-block-list');
335 $js->insertAfter($html, '#text-block-' . $self->text_block->id);
338 The first variation is obviously better suited for chaining.
340 Additional functions:
344 =item C<flash $type, $message>
346 Display a C<$message> in the flash of type C<$type>. Multiple calls of
347 C<flash> on the same C<$self> will be merged by type.
349 On the client side the flash of this type will be cleared before the
352 =item C<error $message>
354 Causes L<to_json> (and therefore L<render>) to output a JSON object
355 that only contains an C<error> field set to this C<$message>. The
356 client will then show the message in the 'error' flash.
358 The messages of multiple calls of C<error> on the same C<$self> will
363 =head2 JQUERY FUNCTIONS
365 The following jQuery functions are supported:
371 C<hide>, C<show>, C<toggle>
373 =item DOM insertion, around
375 C<unwrap>, C<wrap>, C<wrapAll>, C<wrapInner>
377 =item DOM insertion, inside
379 C<append>, C<appendTo>, C<html>, C<prepend>, C<prependTo>, C<text>
381 =item DOM insertion, outside
383 C<after>, C<before>, C<insertAfter>, C<insertBefore>
389 =item DOM replacement
391 C<replaceAll>, C<replaceWith>
393 =item General attributes
395 C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>
399 C<data>, C<removeData>
407 =head2 JSTREE JQUERY PLUGIN
409 The following functions of the C<jstree> plugin to jQuery are
414 =item Operations on the whole tree
418 =item Opening and closing nodes
420 C<open_node>, C<close_node>, C<toggle_node>, C<open_all>,
421 C<close_all>, C<save_opened>, C<reopen>
423 =item Modifying nodes
425 C<rename_node>, C<delete_node>, C<move_node>
427 =item Selecting nodes (from the 'ui' jstree plugin)
429 C<select_node>, C<deselect_node>, C<deselect_all>
433 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
435 In order not having to maintain two files (this one and
436 C<js/client_js.js>) there's a script that can parse this file's
437 C<%supported_methods> definition and generate the file
438 C<js/client_js.js> accordingly. The steps are:
442 =item 1. Add lines in this file to the C<%supported_methods> hash. The
443 key is the function name and the value is the number of expected
446 =item 2. Run C<scripts/generate_client_js_actions.pl>. It will
447 generate C<js/client_js.js> automatically.
449 =item 3. Reload the files in your browser (cleaning its cache can also
454 The template file used for generated C<js/client_js.js> is
455 C<scripts/generate_client_js_actions.tpl>.
463 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>