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, # kivi.display_flash(<TARGET>, <ARGS>)
26 # DOM insertion, around
32 # DOM insertion, inside
40 # DOM insertion, outside
73 # ## jqModal plugin ##
75 # Closing and removing the popup
78 # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
80 # Operations on the whole tree
84 # Opening and closing nodes
85 'jstree:open_node' => 2,
86 'jstree:open_all' => 2,
87 'jstree:close_node' => 2,
88 'jstree:close_all' => 2,
89 'jstree:toggle_node' => 2,
90 'jstree:save_opened' => 1,
94 'jstree:create_node' => 4,
95 'jstree:rename_node' => 3,
96 'jstree:delete_node' => 2,
97 'jstree:move_node' => 5,
99 # Selecting nodes (from the 'ui' plugin to jstree)
100 'jstree:select_node' => 2, # $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>, true)
101 'jstree:deselect_node' => 2,
102 'jstree:deselect_all' => 1,
105 redirect_to => 1, # window.location.href = <TARGET>
107 reinit_widgets => 0, # kivi.reinit_widgets()
113 my ($self, @args) = @_;
115 my $method = $AUTOLOAD;
117 return if $method eq 'DESTROY';
118 return $self->action($method, @args);
122 my ($self, $method, @args) = @_;
124 $method = (delete($self->{_prefix}) || '') . $method;
125 my $num_args = $supported_methods{$method};
127 croak "Unsupported jQuery action: $method" unless defined $num_args;
128 croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if scalar(@args) != $num_args;
131 # Force flattening from SL::Presenter::EscapedText: "" . $...
132 $args[0] = "" . $args[0];
133 $args[0] =~ s/^\s+//;
136 push @{ $self->_actions }, [ $method, @args ];
142 my ($self, $condition, @args) = @_;
144 return $condition ? $self->action(@args) : $self;
162 return SL::JSON::to_json({ error => $self->_error }) if $self->_error;
163 return SL::JSON::to_json({ eval_actions => $self->_actions });
168 return $self->_actions;
172 my ($self, $controller) = @_;
173 $self->reinit_widgets if $::request->presenter->need_reinit_widgets;
174 return $controller->render(\$self->to_json, { type => 'json' });
179 $self->{_prefix} = 'jstree:';
184 my ($self, $type, @messages) = @_;
186 my $message = join ' ', grep { $_ } @messages;
188 if (!$self->_flash->{$type}) {
189 $self->_flash->{$type} = [ 'flash', $type, $message ];
190 push @{ $self->_actions }, $self->_flash->{$type};
192 $self->_flash->{$type}->[-1] .= ' ' . $message;
199 my ($self, @messages) = @_;
201 $self->_error(join ' ', grep { $_ } ($self->_error, @messages));
215 SL::ClientJS - Easy programmatic client-side JavaScript generation
220 First some JavaScript code:
222 // In the client generate an AJAX request whose 'success' handler
223 // calls "eval_json_result(data)":
225 action: "SomeController/the_action",
226 id: $('#some_input_field').val()
228 $.post("controller.pl", data, eval_json_result);
232 # In the controller itself. First, make sure that the "client_js.js"
233 # is loaded. This must be done when the whole side is loaded, so
234 # it's not in the action called by the AJAX request shown above.
235 $::request->layout->use_javascript('client_js.js');
237 # Now in that action called via AJAX:
238 sub action_the_action {
241 # Create a new client-side JS object and do stuff with it!
242 my $js = SL::ClientJS->new;
244 # Show some element on the page:
245 $js->show('#usually_hidden');
247 # Set to hidden inputs. Yes, calls can be chained!
248 $js->val('#hidden_id', $self->new_id)
249 ->val('#other_type', 'Unicorn');
251 # Replace some HTML code:
252 my $html = $self->render('SomeController/the_action', { output => 0 });
253 $js->html('#id_with_new_content', $html);
255 # Operations on a jstree: rename a node and select it
256 my $text_block = SL::DB::RequirementSpecTextBlock->new(id => 4711)->load;
257 $js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
258 ->jstree->select_node('#tb-' . $text_block->id);
260 # Finally render the JSON response:
263 # Rendering can also be chained, e.g.
264 $js->html('#selector', $html)
270 This module enables the generation of jQuery-using JavaScript code on
271 the server side. That code is then evaluated in a safe way on the
274 The workflow is usally that the client creates an AJAX request, the
275 server creates some actions and sends them back, and the client then
276 implements each of these actions.
278 There are three things that need to be done for this to work:
282 =item 1. The "client_js.js" has to be loaded before the AJAX request is started.
284 =item 2. The client code needs to call C<kivi.eval_json_result()> with the result returned from the server.
286 =item 3. The server must use this module.
290 The functions called on the client side are mostly jQuery
291 functions. Other functionality may be added later.
293 Note that L<SL::Controller/render> is aware of this module which saves
294 you some boilerplate. The following two calls are equivalent:
296 $controller->render($client_js);
297 $controller->render(\$client_js->to_json, { type => 'json' });
299 =head1 FUNCTIONS NOT PASSED TO THE CLIENT SIDE
305 Returns the actions gathered so far as an array reference. Each
306 element is an array reference containing at least two items: the
307 function's name and what it is called on. Additional array elements
308 are the function parameters.
312 Returns the actions gathered so far as a JSON string ready to be sent
315 =item C<render $controller>
317 Renders C<$self> via the controller. Useful for chaining. Equivalent
320 $controller->render(\$self->to_json, { type => 'json' });
324 Tells C<$self> that the next action is to be called on a jstree
325 instance. For example:
327 $js->jstree->rename_node('tb-' . $text_block->id, $text_block->title);
331 =head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE
333 =head2 GENERIC FUNCTION
335 All of the following functions can be invoked in two ways: either by
336 calling the function name directly on C<$self> or by calling
337 L</action> with the function name as the first parameter. Therefore
338 the following two calls are identical:
340 $js->insertAfter($html, '#some-id');
341 $js->action('insertAfter', $html, '#some-id');
343 The second form, calling L</action>, is more to type but can be useful
344 in situations in which you have to call one of two functions depending
345 on context. For example, when you want to insert new code in a
346 list. If the list is empty you might have to use C<appendTo>, if it
347 isn't you might have to use C<insertAfter>. Example:
349 my $html = $self->render(...);
350 $js->action($list_is_empty ? 'appendTo' : 'insertAfter', $html, '#text-block-' . ($list_is_empty ? 'list' : $self->text_block->id));
354 my $html = $self->render(...);
355 if ($list_is_empty) {
356 $js->appendTo($html, '#text-block-list');
358 $js->insertAfter($html, '#text-block-' . $self->text_block->id);
361 The first variation is obviously better suited for chaining.
365 =item C<action $method, @args>
367 Call the function with the name C<$method> on C<$self> with arguments
368 C<@args>. Returns the return value of the actual function
369 called. Useful for chaining (see above).
371 =item C<action_if $condition, $method, @args>
373 Call the function with the name C<$method> on C<$self> with arguments
374 C<@args> if C<$condition> is trueish. Does nothing otherwise.
376 Returns the return value of the actual function called if
377 C<$condition> is trueish and C<$self> otherwise. Useful for chaining
380 This function is equivalent to the following:
383 $obj->$method(@args);
386 But it is easier to integrate into a method call chain, e.g.:
388 $js->html('#content', $html)
389 ->action_if($item->is_flagged, 'toggleClass', '#marker', 'flagged')
394 =head2 ADDITIONAL FUNCTIONS
398 =item C<flash $type, $message>
400 Display a C<$message> in the flash of type C<$type>. Multiple calls of
401 C<flash> on the same C<$self> will be merged by type.
403 On the client side the flashes of all types will be cleared after each
404 successful ClientJS call that did not end with C<$js-E<gt>error(...)>.
406 =item C<error $message>
408 Causes L<to_json> (and therefore L<render>) to output a JSON object
409 that only contains an C<error> field set to this C<$message>. The
410 client will then show the message in the 'error' flash.
412 The messages of multiple calls of C<error> on the same C<$self> will
415 =item C<redirect_to $url>
417 Redirects the browser window to the new URL by setting the JavaScript
418 property C<window.location.href>. Note that
419 L<SL::Controller::Base/redirect_to> is AJAX aware and uses this
420 function if the current request is an AJAX request as determined by
421 L<SL::Request/is_ajax>.
425 =head2 JQUERY FUNCTIONS
427 The following jQuery functions are supported:
433 C<hide>, C<show>, C<toggle>
435 =item DOM insertion, around
437 C<unwrap>, C<wrap>, C<wrapAll>, C<wrapInner>
439 =item DOM insertion, inside
441 C<append>, C<appendTo>, C<html>, C<prepend>, C<prependTo>, C<text>
443 =item DOM insertion, outside
445 C<after>, C<before>, C<insertAfter>, C<insertBefore>
451 =item DOM replacement
453 C<replaceAll>, C<replaceWith>
455 =item General attributes
457 C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>
461 C<data>, C<removeData>
469 =head2 JSTREE JQUERY PLUGIN
471 The following functions of the C<jstree> plugin to jQuery are
476 =item Operations on the whole tree
480 =item Opening and closing nodes
482 C<open_node>, C<close_node>, C<toggle_node>, C<open_all>,
483 C<close_all>, C<save_opened>, C<reopen>
485 =item Modifying nodes
487 C<rename_node>, C<delete_node>, C<move_node>
489 =item Selecting nodes (from the 'ui' jstree plugin)
491 C<select_node>, C<deselect_node>, C<deselect_all>
495 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
497 In order not having to maintain two files (this one and
498 C<js/client_js.js>) there's a script that can parse this file's
499 C<%supported_methods> definition and generate the file
500 C<js/client_js.js> accordingly. The steps are:
504 =item 1. Add lines in this file to the C<%supported_methods> hash. The
505 key is the function name and the value is the number of expected
508 =item 2. Run C<scripts/generate_client_js_actions.pl>. It will
509 generate C<js/client_js.js> automatically.
511 =item 3. Reload the files in your browser (cleaning its cache can also
516 The template file used for generated C<js/client_js.js> is
517 C<scripts/generate_client_js_actions.tpl>.
525 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>