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 # Generic Event Handling ## pattern: $(<TARGET>).<FUNCTION>(<ARG1>, kivi.get_function_by_name(<ARG2>))
78 # ## jqModal plugin ##
80 # Closing and removing the popup
81 jqmClose => 1, # $(<TARGET>).jqmClose()
83 # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
85 # Operations on the whole tree
89 # Opening and closing nodes
90 'jstree:open_node' => 2,
91 'jstree:open_all' => 2,
92 'jstree:close_node' => 2,
93 'jstree:close_all' => 2,
94 'jstree:toggle_node' => 2,
95 'jstree:save_opened' => 1,
99 'jstree:create_node' => 4,
100 'jstree:rename_node' => 3,
101 'jstree:delete_node' => 2,
102 'jstree:move_node' => 5,
104 # Selecting nodes (from the 'ui' plugin to jstree)
105 'jstree:select_node' => 2, # $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>, true)
106 'jstree:deselect_node' => 2,
107 'jstree:deselect_all' => 1,
110 redirect_to => 1, # window.location.href = <TARGET>
112 reinit_widgets => 0, # kivi.reinit_widgets()
118 my ($self, @args) = @_;
120 my $method = $AUTOLOAD;
122 return if $method eq 'DESTROY';
123 return $self->action($method, @args);
127 my ($self, $method, @args) = @_;
129 $method = (delete($self->{_prefix}) || '') . $method;
130 my $num_args = $supported_methods{$method};
132 croak "Unsupported jQuery action: $method" unless defined $num_args;
133 croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if scalar(@args) != $num_args;
136 # Force flattening from SL::Presenter::EscapedText: "" . $...
137 $args[0] = "" . $args[0];
138 $args[0] =~ s/^\s+//;
141 push @{ $self->_actions }, [ $method, @args ];
147 my ($self, $condition, @args) = @_;
149 return $condition ? $self->action(@args) : $self;
167 return SL::JSON::to_json({ error => $self->_error }) if $self->_error;
168 return SL::JSON::to_json({ eval_actions => $self->_actions });
173 return $self->_actions;
177 my ($self, $controller) = @_;
178 $self->reinit_widgets if $::request->presenter->need_reinit_widgets;
179 return $controller->render(\$self->to_json, { type => 'json' });
184 $self->{_prefix} = 'jstree:';
189 my ($self, $type, @messages) = @_;
191 my $message = join ' ', grep { $_ } @messages;
193 if (!$self->_flash->{$type}) {
194 $self->_flash->{$type} = [ 'flash', $type, $message ];
195 push @{ $self->_actions }, $self->_flash->{$type};
197 $self->_flash->{$type}->[-1] .= ' ' . $message;
204 my ($self, @messages) = @_;
206 $self->_error(join ' ', grep { $_ } ($self->_error, @messages));
220 SL::ClientJS - Easy programmatic client-side JavaScript generation
225 First some JavaScript code:
227 // In the client generate an AJAX request whose 'success' handler
228 // calls "eval_json_result(data)":
230 action: "SomeController/the_action",
231 id: $('#some_input_field').val()
233 $.post("controller.pl", data, eval_json_result);
237 # In the controller itself. First, make sure that the "client_js.js"
238 # is loaded. This must be done when the whole side is loaded, so
239 # it's not in the action called by the AJAX request shown above.
240 $::request->layout->use_javascript('client_js.js');
242 # Now in that action called via AJAX:
243 sub action_the_action {
246 # Create a new client-side JS object and do stuff with it!
247 my $js = SL::ClientJS->new;
249 # Show some element on the page:
250 $js->show('#usually_hidden');
252 # Set to hidden inputs. Yes, calls can be chained!
253 $js->val('#hidden_id', $self->new_id)
254 ->val('#other_type', 'Unicorn');
256 # Replace some HTML code:
257 my $html = $self->render('SomeController/the_action', { output => 0 });
258 $js->html('#id_with_new_content', $html);
260 # Operations on a jstree: rename a node and select it
261 my $text_block = SL::DB::RequirementSpecTextBlock->new(id => 4711)->load;
262 $js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
263 ->jstree->select_node('#tb-' . $text_block->id);
265 # Finally render the JSON response:
268 # Rendering can also be chained, e.g.
269 $js->html('#selector', $html)
275 This module enables the generation of jQuery-using JavaScript code on
276 the server side. That code is then evaluated in a safe way on the
279 The workflow is usally that the client creates an AJAX request, the
280 server creates some actions and sends them back, and the client then
281 implements each of these actions.
283 There are three things that need to be done for this to work:
287 =item 1. The "client_js.js" has to be loaded before the AJAX request is started.
289 =item 2. The client code needs to call C<kivi.eval_json_result()> with the result returned from the server.
291 =item 3. The server must use this module.
295 The functions called on the client side are mostly jQuery
296 functions. Other functionality may be added later.
298 Note that L<SL::Controller/render> is aware of this module which saves
299 you some boilerplate. The following two calls are equivalent:
301 $controller->render($client_js);
302 $controller->render(\$client_js->to_json, { type => 'json' });
304 =head1 FUNCTIONS NOT PASSED TO THE CLIENT SIDE
310 Returns the actions gathered so far as an array reference. Each
311 element is an array reference containing at least two items: the
312 function's name and what it is called on. Additional array elements
313 are the function parameters.
317 Returns the actions gathered so far as a JSON string ready to be sent
320 =item C<render $controller>
322 Renders C<$self> via the controller. Useful for chaining. Equivalent
325 $controller->render(\$self->to_json, { type => 'json' });
329 Tells C<$self> that the next action is to be called on a jstree
330 instance. For example:
332 $js->jstree->rename_node('tb-' . $text_block->id, $text_block->title);
336 =head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE
338 =head2 GENERIC FUNCTION
340 All of the following functions can be invoked in two ways: either by
341 calling the function name directly on C<$self> or by calling
342 L</action> with the function name as the first parameter. Therefore
343 the following two calls are identical:
345 $js->insertAfter($html, '#some-id');
346 $js->action('insertAfter', $html, '#some-id');
348 The second form, calling L</action>, is more to type but can be useful
349 in situations in which you have to call one of two functions depending
350 on context. For example, when you want to insert new code in a
351 list. If the list is empty you might have to use C<appendTo>, if it
352 isn't you might have to use C<insertAfter>. Example:
354 my $html = $self->render(...);
355 $js->action($list_is_empty ? 'appendTo' : 'insertAfter', $html, '#text-block-' . ($list_is_empty ? 'list' : $self->text_block->id));
359 my $html = $self->render(...);
360 if ($list_is_empty) {
361 $js->appendTo($html, '#text-block-list');
363 $js->insertAfter($html, '#text-block-' . $self->text_block->id);
366 The first variation is obviously better suited for chaining.
370 =item C<action $method, @args>
372 Call the function with the name C<$method> on C<$self> with arguments
373 C<@args>. Returns the return value of the actual function
374 called. Useful for chaining (see above).
376 =item C<action_if $condition, $method, @args>
378 Call the function with the name C<$method> on C<$self> with arguments
379 C<@args> if C<$condition> is trueish. Does nothing otherwise.
381 Returns the return value of the actual function called if
382 C<$condition> is trueish and C<$self> otherwise. Useful for chaining
385 This function is equivalent to the following:
388 $obj->$method(@args);
391 But it is easier to integrate into a method call chain, e.g.:
393 $js->html('#content', $html)
394 ->action_if($item->is_flagged, 'toggleClass', '#marker', 'flagged')
399 =head2 ADDITIONAL FUNCTIONS
403 =item C<flash $type, $message>
405 Display a C<$message> in the flash of type C<$type>. Multiple calls of
406 C<flash> on the same C<$self> will be merged by type.
408 On the client side the flashes of all types will be cleared after each
409 successful ClientJS call that did not end with C<$js-E<gt>error(...)>.
411 =item C<error $message>
413 Causes L<to_json> (and therefore L<render>) to output a JSON object
414 that only contains an C<error> field set to this C<$message>. The
415 client will then show the message in the 'error' flash.
417 The messages of multiple calls of C<error> on the same C<$self> will
420 =item C<redirect_to $url>
422 Redirects the browser window to the new URL by setting the JavaScript
423 property C<window.location.href>. Note that
424 L<SL::Controller::Base/redirect_to> is AJAX aware and uses this
425 function if the current request is an AJAX request as determined by
426 L<SL::Request/is_ajax>.
430 =head2 JQUERY FUNCTIONS
432 The following jQuery functions are supported:
438 C<hide>, C<show>, C<toggle>
440 =item DOM insertion, around
442 C<unwrap>, C<wrap>, C<wrapAll>, C<wrapInner>
444 =item DOM insertion, inside
446 C<append>, C<appendTo>, C<html>, C<prepend>, C<prependTo>, C<text>
448 =item DOM insertion, outside
450 C<after>, C<before>, C<insertAfter>, C<insertBefore>
456 =item DOM replacement
458 C<replaceAll>, C<replaceWith>
460 =item General attributes
462 C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>
466 C<data>, C<removeData>
472 =item Generic Event Handlers
474 C<on>, C<off>, C<one>
476 These attach/detach event listeners to specific selectors. The first
477 argument is the selector, the second the name of the events and the
478 third argument is the name of the handler function. That function must
479 already exist when the handler is added.
483 =head2 JSTREE JQUERY PLUGIN
485 The following functions of the C<jstree> plugin to jQuery are
490 =item Operations on the whole tree
494 =item Opening and closing nodes
496 C<open_node>, C<close_node>, C<toggle_node>, C<open_all>,
497 C<close_all>, C<save_opened>, C<reopen>
499 =item Modifying nodes
501 C<rename_node>, C<delete_node>, C<move_node>
503 =item Selecting nodes (from the 'ui' jstree plugin)
505 C<select_node>, C<deselect_node>, C<deselect_all>
509 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
511 In order not having to maintain two files (this one and
512 C<js/client_js.js>) there's a script that can parse this file's
513 C<%supported_methods> definition and generate the file
514 C<js/client_js.js> accordingly. The steps are:
518 =item 1. Add lines in this file to the C<%supported_methods> hash. The
519 key is the function name and the value is the number of expected
522 =item 2. Run C<scripts/generate_client_js_actions.pl>. It will
523 generate C<js/client_js.js> automatically.
525 =item 3. Reload the files in your browser (cleaning its cache can also
530 The template file used for generated C<js/client_js.js> is
531 C<scripts/generate_client_js_actions.tpl>.
539 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>