5 use parent qw(Rose::Object);
 
  10 use Rose::Object::MakeMethods::Generic
 
  13   'scalar --get_set_init' => [ qw(controller _actions _flash _flash_detail _no_flash_clear _error) ],
 
  16 my %supported_methods = (
 
  24   # DOM insertion, around
 
  30   # DOM insertion, inside
 
  38   # DOM insertion, outside
 
  69   focus        => 1, # kivi.set_focus(<TARGET>)
 
  71   # Generic Event Handling ## pattern: $(<TARGET>).<FUNCTION>(<ARG1>, kivi.get_function_by_name(<ARG2>))
 
  76   # ## jQuery UI dialog plugin ## pattern: $(<TARGET>).dialog('<FUNCTION>')
 
  78   # Opening and closing a popup
 
  79   'dialog:open'          => 1, # kivi.popup_dialog(<TARGET>)
 
  82   # ## jQuery Form plugin ##
 
  83   'ajaxForm'             => 1, # $(<TARGET>).ajaxForm({ success: eval_json_result })
 
  85   # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
 
  87   # Operations on the whole tree
 
  91   # Opening and closing nodes
 
  92   'jstree:open_node'     => 2,
 
  93   'jstree:open_all'      => 2,
 
  94   'jstree:close_node'    => 2,
 
  95   'jstree:close_all'     => 2,
 
  96   'jstree:toggle_node'   => 2,
 
  97   'jstree:save_opened'   => 1,
 
 101   'jstree:create_node'   => 4,
 
 102   'jstree:rename_node'   => 3,
 
 103   'jstree:delete_node'   => 2,
 
 104   'jstree:move_node'     => 5,
 
 106   # Selecting nodes (from the 'ui' plugin to jstree)
 
 107   'jstree:select_node'   => 2,  # $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>, true)
 
 108   'jstree:deselect_node' => 2,
 
 109   'jstree:deselect_all'  => 1,
 
 111   # ## ckeditor stuff ##
 
 112   'focus_ckeditor'       => 1,  # kivi.focus_ckeditor_when_ready(<TARGET>)
 
 115   redirect_to            => 1,  # window.location.href = <TARGET>
 
 116   save_file              => 4,  # kivi.save_file(<TARGET>, <ARGS>)
 
 118   flash                  => 2,  # kivi.display_flash(<TARGET>, <ARGS>)
 
 119   flash_detail           => 2,  # kivi.display_flash_detail(<TARGET>, <ARGS>)
 
 120   clear_flash            => 2,  # kivi.clear_flash(<TARGET>, <ARGS>)
 
 121   reinit_widgets         => 0,  # kivi.reinit_widgets()
 
 122   run                    => -1, # kivi.run(<TARGET>, <ARGS>)
 
 123   run_once_for           => 3,  # kivi.run_once_for(<TARGET>, <ARGS>)
 
 125   scroll_into_view       => 1,  # $(<TARGET>)[0].scrollIntoView()
 
 127   set_cursor_position    => 2,  # kivi.set_cursor_position(<TARGET>, <ARGS>)
 
 130 my %trim_target_for = map { ($_ => 1) } qw(insertAfter insertBefore appendTo prependTo);
 
 135   my ($self, @args) = @_;
 
 137   my $method        =  $AUTOLOAD;
 
 139   return if $method eq 'DESTROY';
 
 140   return $self->action($method, @args);
 
 144   my ($self, $method, @args) = @_;
 
 146   $method      =  (delete($self->{_prefix}) || '') . $method;
 
 147   my $num_args =  $supported_methods{$method};
 
 149   croak "Unsupported jQuery action: $method" unless defined $num_args;
 
 152     croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)"          if scalar(@args) != $num_args;
 
 155     croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted at least: $num_args)" if scalar(@args) < $num_args;
 
 156     $num_args  = scalar @args;
 
 159   foreach my $idx (0..$num_args - 1) {
 
 160     # Force flattening from SL::Presenter::EscapedText.
 
 161     $args[$idx] = "" . $args[$idx] if ref($args[$idx]) eq 'SL::Presenter::EscapedText';
 
 164   # Trim leading whitespaces for certain jQuery functions that operate
 
 165   # on HTML code: $("<p>test</p>").appendTo('#some-id'). jQuery croaks
 
 166   # on leading whitespaces, e.g. on $(" <p>test</p>").
 
 167   $args[0] =~ s{^\s+}{} if $trim_target_for{$method};
 
 169   push @{ $self->_actions }, [ $method, @args ];
 
 175   my ($self, $condition, @args) = @_;
 
 177   return $condition ? $self->action(@args) : $self;
 
 188 sub init__flash_detail {
 
 196 sub init__no_flash_clear {
 
 203   return SL::JSON::to_json({ error          => $self->_error   }) if $self->_error;
 
 204   return SL::JSON::to_json({ no_flash_clear => $self->_no_flash_clear, eval_actions => $self->_actions });
 
 209   return $self->_actions;
 
 213   my ($self, $controller) = @_;
 
 214   $controller ||= $self->controller;
 
 215   $self->reinit_widgets if $::request->presenter->need_reinit_widgets;
 
 216   return $controller->render(\$self->to_json, { type => 'json' });
 
 221   $self->{_prefix} = 'jstree:';
 
 227   $self->{_prefix} = 'dialog:';
 
 233   $self->{_prefix} = 'ckeditor:';
 
 238   my ($self, $type, @messages) = @_;
 
 240   my $message = join ' ', grep { $_ } @messages;
 
 242   if (!$self->_flash->{$type}) {
 
 243     $self->_flash->{$type} = [ 'flash', $type, $message ];
 
 244     push @{ $self->_actions }, $self->_flash->{$type};
 
 246     $self->_flash->{$type}->[-1] .= ' ' . $message;
 
 253   my ($self, $type, @messages) = @_;
 
 255   my $message = join '<br>', grep { $_ } @messages;
 
 257   if (!$self->_flash_detail->{$type}) {
 
 258     $self->_flash_detail->{$type} = [ 'flash_detail', $type, $message ];
 
 259     push @{ $self->_actions }, $self->_flash_detail->{$type};
 
 261     $self->_flash_detail->{$type}->[-1] .= ' ' . $message;
 
 269   $self->_no_flash_clear('1');
 
 274   my ($self, @messages) = @_;
 
 276   $self->_error(join ' ', grep { $_ } ($self->_error, @messages));
 
 281 sub init_controller {
 
 283   require SL::Controller::Base;
 
 284   SL::Controller::Base->new;
 
 296 SL::ClientJS - Easy programmatic client-side JavaScript generation
 
 301 First some JavaScript code:
 
 303   // In the client generate an AJAX request whose 'success' handler
 
 304   // calls "eval_json_result(data)":
 
 306     action: "SomeController/the_action",
 
 307     id:     $('#some_input_field').val()
 
 309   $.post("controller.pl", data, eval_json_result);
 
 313   # In the controller itself. First, make sure that the "client_js.js"
 
 314   # is loaded. This must be done when the whole side is loaded, so
 
 315   # it's not in the action called by the AJAX request shown above.
 
 316   $::request->layout->use_javascript('client_js.js');
 
 318   # Now in that action called via AJAX:
 
 319   sub action_the_action {
 
 322     # Create a new client-side JS object and do stuff with it!
 
 323     my $js = SL::ClientJS->new(controller => $self);
 
 325     # Show some element on the page:
 
 326     $js->show('#usually_hidden');
 
 328     # Set to hidden inputs. Yes, calls can be chained!
 
 329     $js->val('#hidden_id', $self->new_id)
 
 330        ->val('#other_type', 'Unicorn');
 
 332     # Replace some HTML code:
 
 333     my $html = $self->render('SomeController/the_action', { output => 0 });
 
 334     $js->html('#id_with_new_content', $html);
 
 336     # Operations on a jstree: rename a node and select it
 
 337     my $text_block = SL::DB::RequirementSpecTextBlock->new(id => 4711)->load;
 
 338     $js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
 
 339        ->jstree->select_node('#tb-' . $text_block->id);
 
 341     # Close a popup opened by kivi.popup_dialog():
 
 342     $js->dialog->close('#jqueryui_popup_dialog');
 
 344     # Finally render the JSON response:
 
 347     # Rendering can also be chained, e.g.
 
 348     $js->html('#selector', $html)
 
 354 This module enables the generation of jQuery-using JavaScript code on
 
 355 the server side. That code is then evaluated in a safe way on the
 
 358 The workflow is usually that the client creates an AJAX request, the
 
 359 server creates some actions and sends them back, and the client then
 
 360 implements each of these actions.
 
 362 There are three things that need to be done for this to work:
 
 366 =item 1. The "client_js.js" has to be loaded before the AJAX request is started.
 
 368 =item 2. The client code needs to call C<kivi.eval_json_result()> with the result returned from the server.
 
 370 =item 3. The server must use this module.
 
 374 The functions called on the client side are mostly jQuery
 
 375 functions. Other functionality may be added later.
 
 377 Note that L<SL::Controller/render> is aware of this module which saves
 
 378 you some boilerplate. The following two calls are equivalent:
 
 380   $controller->render($client_js);
 
 381   $controller->render(\$client_js->to_json, { type => 'json' });
 
 383 =head1 FUNCTIONS NOT PASSED TO THE CLIENT SIDE
 
 389 Returns the actions gathered so far as an array reference. Each
 
 390 element is an array reference containing at least two items: the
 
 391 function's name and what it is called on. Additional array elements
 
 392 are the function parameters.
 
 396 Returns the actions gathered so far as a JSON string ready to be sent
 
 399 =item C<render [$controller]>
 
 401 Renders C<$self> via the controller. Useful for chaining. Equivalent
 
 404   $controller->render(\$self->to_json, { type => 'json' });
 
 406 The controller instance to use can be set during object creation (see
 
 407 synopsis) or as an argument to C<render>.
 
 411 Tells C<$self> that the next action is to be called on a jQuery UI
 
 412 dialog instance, e.g. one opened by C<kivi.popup_dialog()>. For
 
 415   $js->dialog->close('#jqueryui_popup_dialog');
 
 419 Tells C<$self> that the next action is to be called on a jstree
 
 420 instance. For example:
 
 422   $js->jstree->rename_node('tb-' . $text_block->id, $text_block->title);
 
 426 =head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE
 
 428 =head2 GENERIC FUNCTION
 
 430 All of the following functions can be invoked in two ways: either by
 
 431 calling the function name directly on C<$self> or by calling
 
 432 L</action> with the function name as the first parameter. Therefore
 
 433 the following two calls are identical:
 
 435   $js->insertAfter($html, '#some-id');
 
 436   $js->action('insertAfter', $html, '#some-id');
 
 438 The second form, calling L</action>, is more to type but can be useful
 
 439 in situations in which you have to call one of two functions depending
 
 440 on context. For example, when you want to insert new code in a
 
 441 list. If the list is empty you might have to use C<appendTo>, if it
 
 442 isn't you might have to use C<insertAfter>. Example:
 
 444   my $html = $self->render(...);
 
 445   $js->action($list_is_empty ? 'appendTo' : 'insertAfter', $html, '#text-block-' . ($list_is_empty ? 'list' : $self->text_block->id));
 
 449   my $html = $self->render(...);
 
 450   if ($list_is_empty) {
 
 451     $js->appendTo($html, '#text-block-list');
 
 453     $js->insertAfter($html, '#text-block-' . $self->text_block->id);
 
 456 The first variation is obviously better suited for chaining.
 
 460 =item C<action $method, @args>
 
 462 Call the function with the name C<$method> on C<$self> with arguments
 
 463 C<@args>. Returns the return value of the actual function
 
 464 called. Useful for chaining (see above).
 
 466 =item C<action_if $condition, $method, @args>
 
 468 Call the function with the name C<$method> on C<$self> with arguments
 
 469 C<@args> if C<$condition> is trueish. Does nothing otherwise.
 
 471 Returns the return value of the actual function called if
 
 472 C<$condition> is trueish and C<$self> otherwise. Useful for chaining
 
 475 This function is equivalent to the following:
 
 478     $obj->$method(@args);
 
 481 But it is easier to integrate into a method call chain, e.g.:
 
 483   $js->html('#content', $html)
 
 484      ->action_if($item->is_flagged, 'toggleClass', '#marker', 'flagged')
 
 489 =head2 ADDITIONAL FUNCTIONS
 
 493 =item C<flash $type, $message>
 
 495 Display a C<$message> in the flash of type C<$type>. Multiple calls of
 
 496 C<flash> on the same C<$self> will be merged by type.
 
 498 On the client side the flashes of all types will be cleared after each
 
 499 successful ClientJS call that did not end with C<$js-E<gt>error(...)>.
 
 500 This clearing can be switched of by the function C<no_flash_clear>
 
 502 =item C<flash_detail $type, $message>
 
 504 Display a detailed message C<$message> in the flash of type C<$type>. Multiple calls of
 
 505 C<flash_detail> on the same C<$self> will be merged by type.
 
 506 So the flash message can be hold short and the visibility of details can toggled by the user.
 
 508 =item C<no_flash_clear>
 
 510 No automatic clearing of flash after successful ClientJS call
 
 512 =item C<error $message>
 
 514 Causes L<to_json> (and therefore L<render>) to output a JSON object
 
 515 that only contains an C<error> field set to this C<$message>. The
 
 516 client will then show the message in the 'error' flash.
 
 518 The messages of multiple calls of C<error> on the same C<$self> will
 
 521 =item C<redirect_to $url>
 
 523 Redirects the browser window to the new URL by setting the JavaScript
 
 524 property C<window.location.href>. Note that
 
 525 L<SL::Controller::Base/redirect_to> is AJAX aware and uses this
 
 526 function if the current request is an AJAX request as determined by
 
 527 L<SL::Request/is_ajax>.
 
 531 =head2 KIVITENDO FUNCTIONS
 
 533 The following functions from the C<kivi> namespace are supported:
 
 537 =item Displaying stuff
 
 539 C<flash> (don't call directly, use L</flash> instead)
 
 541 =item Running functions
 
 543 C<run>, C<run_once_for>
 
 551 =head2 JQUERY FUNCTIONS
 
 553 The following jQuery functions are supported:
 
 559 C<hide>, C<show>, C<toggle>
 
 561 =item DOM insertion, around
 
 563 C<unwrap>, C<wrap>, C<wrapAll>, C<wrapInner>
 
 565 =item DOM insertion, inside
 
 567 C<append>, C<appendTo>, C<html>, C<prepend>, C<prependTo>, C<text>
 
 569 =item DOM insertion, outside
 
 571 C<after>, C<before>, C<insertAfter>, C<insertBefore>
 
 577 =item DOM replacement
 
 579 C<replaceAll>, C<replaceWith>
 
 581 =item General attributes
 
 583 C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>
 
 585 =item Class attributes
 
 587 C<addClass>, C<removeClass>, C<toggleClass>
 
 591 C<data>, C<removeData>
 
 597 =item Generic Event Handlers
 
 599 C<on>, C<off>, C<one>
 
 601 These attach/detach event listeners to specific selectors. The first
 
 602 argument is the selector, the second the name of the events and the
 
 603 third argument is the name of the handler function. That function must
 
 604 already exist when the handler is added.
 
 608 =head2 JQUERY POPUP DIALOG PLUGIN
 
 610 Supported functions of the C<popup dialog> plugin to jQuery. They are
 
 611 invoked by first calling C<dialog> in the ClientJS instance and then
 
 614   $js->dialog->close(...);
 
 618 =item Closing and removing the popup
 
 624 =head2 AJAXFORM JQUERY PLUGIN
 
 626 The following functions of the C<ajaxForm> plugin to jQuery are
 
 631 =item All functions by the generic accessor function:
 
 637 =head2 JSTREE JQUERY PLUGIN
 
 639 Supported functions of the C<jstree> plugin to jQuery. They are
 
 640 invoked by first calling C<jstree> in the ClientJS instance and then
 
 643   $js->jstree->open_node(...);
 
 647 =item Operations on the whole tree
 
 651 =item Opening and closing nodes
 
 653 C<open_node>, C<close_node>, C<toggle_node>, C<open_all>,
 
 654 C<close_all>, C<save_opened>, C<reopen>
 
 656 =item Modifying nodes
 
 658 C<rename_node>, C<delete_node>, C<move_node>
 
 660 =item Selecting nodes (from the 'ui' jstree plugin)
 
 662 C<select_node>, C<deselect_node>, C<deselect_all>
 
 666 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
 
 668 In order to not have to maintain two files (this one and
 
 669 C<js/client_js.js>) there's a script that can parse this file's
 
 670 C<%supported_methods> definition and generate the file
 
 671 C<js/client_js.js> accordingly. The steps are:
 
 675 =item 1. Add lines in this file to the C<%supported_methods> hash. The
 
 676 key is the function name and the value is the number of expected
 
 677 parameters. The value can be negative to indicate that the function
 
 678 takes at least the absolute of this value as parameters and optionally
 
 679 more. In such a case the C<E<lt>ARGSE<gt>> format expands to an actual
 
 680 array (and the individual elements if the value is positive>.
 
 682 =item 2. Run C<scripts/generate_client_js_actions.pl>. It will
 
 683 generate C<js/client_js.js> automatically.
 
 685 =item 3. Reload the files in your browser (cleaning its cache can also
 
 690 The template file used for generated C<js/client_js.js> is
 
 691 C<scripts/generate_client_js_actions.tpl>.
 
 699 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>