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>
111 my ($self, @args) = @_;
113 my $method = $AUTOLOAD;
115 return if $method eq 'DESTROY';
116 return $self->action($method, @args);
120 my ($self, $method, @args) = @_;
122 $method = (delete($self->{_prefix}) || '') . $method;
123 my $num_args = $supported_methods{$method};
125 croak "Unsupported jQuery action: $method" unless defined $num_args;
126 croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if scalar(@args) != $num_args;
129 # Force flattening from SL::Presenter::EscapedText: "" . $...
130 $args[0] = "" . $args[0];
131 $args[0] =~ s/^\s+//;
134 push @{ $self->_actions }, [ $method, @args ];
140 my ($self, $condition, @args) = @_;
142 return $condition ? $self->action(@args) : $self;
160 return SL::JSON::to_json({ error => $self->_error }) if $self->_error;
161 return SL::JSON::to_json({ eval_actions => $self->_actions });
166 return $self->_actions;
170 my ($self, $controller) = @_;
171 return $controller->render(\$self->to_json, { type => 'json' });
176 $self->{_prefix} = 'jstree:';
181 my ($self, $type, @messages) = @_;
183 my $message = join ' ', grep { $_ } @messages;
185 if (!$self->_flash->{$type}) {
186 $self->_flash->{$type} = [ 'flash', $type, $message ];
187 push @{ $self->_actions }, $self->_flash->{$type};
189 $self->_flash->{$type}->[-1] .= ' ' . $message;
196 my ($self, @messages) = @_;
198 $self->_error(join ' ', grep { $_ } ($self->_error, @messages));
212 SL::ClientJS - Easy programmatic client-side JavaScript generation
217 First some JavaScript code:
219 // In the client generate an AJAX request whose 'success' handler
220 // calls "eval_json_result(data)":
222 action: "SomeController/the_action",
223 id: $('#some_input_field').val()
225 $.post("controller.pl", data, eval_json_result);
229 # In the controller itself. First, make sure that the "client_js.js"
230 # is loaded. This must be done when the whole side is loaded, so
231 # it's not in the action called by the AJAX request shown above.
232 $::request->layout->use_javascript('client_js.js');
234 # Now in that action called via AJAX:
235 sub action_the_action {
238 # Create a new client-side JS object and do stuff with it!
239 my $js = SL::ClientJS->new;
241 # Show some element on the page:
242 $js->show('#usually_hidden');
244 # Set to hidden inputs. Yes, calls can be chained!
245 $js->val('#hidden_id', $self->new_id)
246 ->val('#other_type', 'Unicorn');
248 # Replace some HTML code:
249 my $html = $self->render('SomeController/the_action', { output => 0 });
250 $js->html('#id_with_new_content', $html);
252 # Operations on a jstree: rename a node and select it
253 my $text_block = SL::DB::RequirementSpecTextBlock->new(id => 4711)->load;
254 $js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
255 ->jstree->select_node('#tb-' . $text_block->id);
257 # Finally render the JSON response:
260 # Rendering can also be chained, e.g.
261 $js->html('#selector', $html)
267 This module enables the generation of jQuery-using JavaScript code on
268 the server side. That code is then evaluated in a safe way on the
271 The workflow is usally that the client creates an AJAX request, the
272 server creates some actions and sends them back, and the client then
273 implements each of these actions.
275 There are three things that need to be done for this to work:
279 =item 1. The "client_js.js" has to be loaded before the AJAX request is started.
281 =item 2. The client code needs to call C<kivi.eval_json_result()> with the result returned from the server.
283 =item 3. The server must use this module.
287 The functions called on the client side are mostly jQuery
288 functions. Other functionality may be added later.
290 Note that L<SL::Controller/render> is aware of this module which saves
291 you some boilerplate. The following two calls are equivalent:
293 $controller->render($client_js);
294 $controller->render(\$client_js->to_json, { type => 'json' });
296 =head1 FUNCTIONS NOT PASSED TO THE CLIENT SIDE
302 Returns the actions gathered so far as an array reference. Each
303 element is an array reference containing at least two items: the
304 function's name and what it is called on. Additional array elements
305 are the function parameters.
309 Returns the actions gathered so far as a JSON string ready to be sent
312 =item C<render $controller>
314 Renders C<$self> via the controller. Useful for chaining. Equivalent
317 $controller->render(\$self->to_json, { type => 'json' });
321 Tells C<$self> that the next action is to be called on a jstree
322 instance. For example:
324 $js->jstree->rename_node('tb-' . $text_block->id, $text_block->title);
328 =head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE
330 =head2 GENERIC FUNCTION
332 All of the following functions can be invoked in two ways: either by
333 calling the function name directly on C<$self> or by calling
334 L</action> with the function name as the first parameter. Therefore
335 the following two calls are identical:
337 $js->insertAfter($html, '#some-id');
338 $js->action('insertAfter', $html, '#some-id');
340 The second form, calling L</action>, is more to type but can be useful
341 in situations in which you have to call one of two functions depending
342 on context. For example, when you want to insert new code in a
343 list. If the list is empty you might have to use C<appendTo>, if it
344 isn't you might have to use C<insertAfter>. Example:
346 my $html = $self->render(...);
347 $js->action($list_is_empty ? 'appendTo' : 'insertAfter', $html, '#text-block-' . ($list_is_empty ? 'list' : $self->text_block->id));
351 my $html = $self->render(...);
352 if ($list_is_empty) {
353 $js->appendTo($html, '#text-block-list');
355 $js->insertAfter($html, '#text-block-' . $self->text_block->id);
358 The first variation is obviously better suited for chaining.
362 =item C<action $method, @args>
364 Call the function with the name C<$method> on C<$self> with arguments
365 C<@args>. Returns the return value of the actual function
366 called. Useful for chaining (see above).
368 =item C<action_if $condition, $method, @args>
370 Call the function with the name C<$method> on C<$self> with arguments
371 C<@args> if C<$condition> is trueish. Does nothing otherwise.
373 Returns the return value of the actual function called if
374 C<$condition> is trueish and C<$self> otherwise. Useful for chaining
377 This function is equivalent to the following:
380 $obj->$method(@args);
383 But it is easier to integrate into a method call chain, e.g.:
385 $js->html('#content', $html)
386 ->action_if($item->is_flagged, 'toggleClass', '#marker', 'flagged')
391 =head2 ADDITIONAL FUNCTIONS
395 =item C<flash $type, $message>
397 Display a C<$message> in the flash of type C<$type>. Multiple calls of
398 C<flash> on the same C<$self> will be merged by type.
400 On the client side the flashes of all types will be cleared after each
401 successful ClientJS call that did not end with C<$js-E<gt>error(...)>.
403 =item C<error $message>
405 Causes L<to_json> (and therefore L<render>) to output a JSON object
406 that only contains an C<error> field set to this C<$message>. The
407 client will then show the message in the 'error' flash.
409 The messages of multiple calls of C<error> on the same C<$self> will
412 =item C<redirect_to $url>
414 Redirects the browser window to the new URL by setting the JavaScript
415 property C<window.location.href>. Note that
416 L<SL::Controller::Base/redirect_to> is AJAX aware and uses this
417 function if the current request is an AJAX request as determined by
418 L<SL::Request/is_ajax>.
422 =head2 JQUERY FUNCTIONS
424 The following jQuery functions are supported:
430 C<hide>, C<show>, C<toggle>
432 =item DOM insertion, around
434 C<unwrap>, C<wrap>, C<wrapAll>, C<wrapInner>
436 =item DOM insertion, inside
438 C<append>, C<appendTo>, C<html>, C<prepend>, C<prependTo>, C<text>
440 =item DOM insertion, outside
442 C<after>, C<before>, C<insertAfter>, C<insertBefore>
448 =item DOM replacement
450 C<replaceAll>, C<replaceWith>
452 =item General attributes
454 C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>
458 C<data>, C<removeData>
466 =head2 JSTREE JQUERY PLUGIN
468 The following functions of the C<jstree> plugin to jQuery are
473 =item Operations on the whole tree
477 =item Opening and closing nodes
479 C<open_node>, C<close_node>, C<toggle_node>, C<open_all>,
480 C<close_all>, C<save_opened>, C<reopen>
482 =item Modifying nodes
484 C<rename_node>, C<delete_node>, C<move_node>
486 =item Selecting nodes (from the 'ui' jstree plugin)
488 C<select_node>, C<deselect_node>, C<deselect_all>
492 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
494 In order not having to maintain two files (this one and
495 C<js/client_js.js>) there's a script that can parse this file's
496 C<%supported_methods> definition and generate the file
497 C<js/client_js.js> accordingly. The steps are:
501 =item 1. Add lines in this file to the C<%supported_methods> hash. The
502 key is the function name and the value is the number of expected
505 =item 2. Run C<scripts/generate_client_js_actions.pl>. It will
506 generate C<js/client_js.js> automatically.
508 =item 3. Reload the files in your browser (cleaning its cache can also
513 The template file used for generated C<js/client_js.js> is
514 C<scripts/generate_client_js_actions.tpl>.
522 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>