test action
[kivitendo-erp.git] / SL / ClientJS.pm
index ff4aeb8..49bde80 100644 (file)
@@ -9,7 +9,8 @@ use SL::JSON ();
 
 use Rose::Object::MakeMethods::Generic
 (
 
 use Rose::Object::MakeMethods::Generic
 (
-  'scalar --get_set_init' => [ qw(_actions _flash _error) ],
+  scalar                  => [ qw() ],
+  'scalar --get_set_init' => [ qw(controller _actions _flash _flash_detail _no_flash_clear _error) ],
 );
 
 my %supported_methods = (
 );
 
 my %supported_methods = (
@@ -65,7 +66,7 @@ my %supported_methods = (
   removeData   => 2,
 
   # Form Events
   removeData   => 2,
 
   # Form Events
-  focus        => 1,
+  focus        => 1, # kivi.set_focus(<TARGET>)
 
   # Generic Event Handling ## pattern: $(<TARGET>).<FUNCTION>(<ARG1>, kivi.get_function_by_name(<ARG2>))
   on           => 3,
 
   # Generic Event Handling ## pattern: $(<TARGET>).<FUNCTION>(<ARG1>, kivi.get_function_by_name(<ARG2>))
   on           => 3,
@@ -74,11 +75,12 @@ my %supported_methods = (
 
   # ## jQuery UI dialog plugin ## pattern: $(<TARGET>).dialog('<FUNCTION>')
 
 
   # ## jQuery UI dialog plugin ## pattern: $(<TARGET>).dialog('<FUNCTION>')
 
-  # Closing and removing the popup
+  # Opening and closing a popup
+  'dialog:open'          => 1, # kivi.popup_dialog(<TARGET>)
   'dialog:close'         => 1,
 
   # ## jQuery Form plugin ##
   'dialog:close'         => 1,
 
   # ## jQuery Form plugin ##
-  'ajaxForm'             => 1, # pattern: $(<TARGET>).ajaxForm({ success: eval_json_result })
+  'ajaxForm'             => 1, # $(<TARGET>).ajaxForm({ success: eval_json_result })
 
   # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
 
 
   # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
 
@@ -106,15 +108,27 @@ my %supported_methods = (
   'jstree:deselect_node' => 2,
   'jstree:deselect_all'  => 1,
 
   'jstree:deselect_node' => 2,
   'jstree:deselect_all'  => 1,
 
+  # ## ckeditor stuff ##
+  'focus_ckeditor'       => 1,  # kivi.focus_ckeditor_when_ready(<TARGET>)
+
   # ## other stuff ##
   redirect_to            => 1,  # window.location.href = <TARGET>
   # ## other stuff ##
   redirect_to            => 1,  # window.location.href = <TARGET>
+  save_file              => 4,  # kivi.save_file(<TARGET>, <ARGS>)
 
   flash                  => 2,  # kivi.display_flash(<TARGET>, <ARGS>)
 
   flash                  => 2,  # kivi.display_flash(<TARGET>, <ARGS>)
+  flash_detail           => 2,  # kivi.display_flash_detail(<TARGET>, <ARGS>)
+  clear_flash            => 2,  # kivi.clear_flash(<TARGET>, <ARGS>)
   reinit_widgets         => 0,  # kivi.reinit_widgets()
   run                    => -1, # kivi.run(<TARGET>, <ARGS>)
   run_once_for           => 3,  # kivi.run_once_for(<TARGET>, <ARGS>)
   reinit_widgets         => 0,  # kivi.reinit_widgets()
   run                    => -1, # kivi.run(<TARGET>, <ARGS>)
   run_once_for           => 3,  # kivi.run_once_for(<TARGET>, <ARGS>)
+
+  scroll_into_view       => 1,  # $(<TARGET>)[0].scrollIntoView()
+
+  set_cursor_position    => 2,  # kivi.set_cursor_position(<TARGET>, <ARGS>)
 );
 
 );
 
+my %trim_target_for = map { ($_ => 1) } qw(insertAfter insertBefore appendTo prependTo);
+
 sub AUTOLOAD {
   our $AUTOLOAD;
 
 sub AUTOLOAD {
   our $AUTOLOAD;
 
@@ -132,10 +146,10 @@ sub action {
   $method      =  (delete($self->{_prefix}) || '') . $method;
   my $num_args =  $supported_methods{$method};
 
   $method      =  (delete($self->{_prefix}) || '') . $method;
   my $num_args =  $supported_methods{$method};
 
-  croak "Unsupported jQuery action: $method"                                                    unless defined $num_args;
+  croak "Unsupported jQuery action: $method" unless defined $num_args;
 
   if ($num_args > 0) {
 
   if ($num_args > 0) {
-    croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if scalar(@args) != $num_args;
+    croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)"          if scalar(@args) != $num_args;
   } else {
     $num_args *= -1;
     croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted at least: $num_args)" if scalar(@args) < $num_args;
   } else {
     $num_args *= -1;
     croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted at least: $num_args)" if scalar(@args) < $num_args;
@@ -143,11 +157,15 @@ sub action {
   }
 
   foreach my $idx (0..$num_args - 1) {
   }
 
   foreach my $idx (0..$num_args - 1) {
-    # Force flattening from SL::Presenter::EscapedText and trim leading whitespace for scalars
-    $args[$idx] =  "" . $args[$idx] if  ref($args[$idx]) eq 'SL::Presenter::EscapedText';
-    $args[$idx] =~ s/^\s+//         if !ref($args[$idx]);
+    # Force flattening from SL::Presenter::EscapedText.
+    $args[$idx] = "" . $args[$idx] if ref($args[$idx]) eq 'SL::Presenter::EscapedText';
   }
 
   }
 
+  # Trim leading whitespaces for certain jQuery functions that operate
+  # on HTML code: $("<p>test</p>").appendTo('#some-id'). jQuery croaks
+  # on leading whitespaces, e.g. on $(" <p>test</p>").
+  $args[0] =~ s{^\s+}{} if $trim_target_for{$method};
+
   push @{ $self->_actions }, [ $method, @args ];
 
   return $self;
   push @{ $self->_actions }, [ $method, @args ];
 
   return $self;
@@ -167,15 +185,23 @@ sub init__flash {
   return {};
 }
 
   return {};
 }
 
+sub init__flash_detail {
+  return {};
+}
+
 sub init__error {
   return '';
 }
 
 sub init__error {
   return '';
 }
 
+sub init__no_flash_clear {
+  return '';
+}
+
 sub to_json {
   my ($self) = @_;
 
 sub to_json {
   my ($self) = @_;
 
-  return SL::JSON::to_json({ error        => $self->_error   }) if $self->_error;
-  return SL::JSON::to_json({ eval_actions => $self->_actions });
+  return SL::JSON::to_json({ error          => $self->_error   }) if $self->_error;
+  return SL::JSON::to_json({ no_flash_clear => $self->_no_flash_clear, eval_actions => $self->_actions });
 }
 
 sub to_array {
 }
 
 sub to_array {
@@ -185,6 +211,7 @@ sub to_array {
 
 sub render {
   my ($self, $controller) = @_;
 
 sub render {
   my ($self, $controller) = @_;
+  $controller ||= $self->controller;
   $self->reinit_widgets if $::request->presenter->need_reinit_widgets;
   return $controller->render(\$self->to_json, { type => 'json' });
 }
   $self->reinit_widgets if $::request->presenter->need_reinit_widgets;
   return $controller->render(\$self->to_json, { type => 'json' });
 }
@@ -201,6 +228,12 @@ sub dialog {
   return $self;
 }
 
   return $self;
 }
 
+sub ckeditor {
+  my ($self) = @_;
+  $self->{_prefix} = 'ckeditor:';
+  return $self;
+}
+
 sub flash {
   my ($self, $type, @messages) = @_;
 
 sub flash {
   my ($self, $type, @messages) = @_;
 
@@ -216,6 +249,27 @@ sub flash {
   return $self;
 }
 
   return $self;
 }
 
+sub flash_detail {
+  my ($self, $type, @messages) = @_;
+
+  my $message = join '<br>', grep { $_ } @messages;
+
+  if (!$self->_flash_detail->{$type}) {
+    $self->_flash_detail->{$type} = [ 'flash_detail', $type, $message ];
+    push @{ $self->_actions }, $self->_flash_detail->{$type};
+  } else {
+    $self->_flash_detail->{$type}->[-1] .= ' ' . $message;
+  }
+
+  return $self;
+}
+
+sub no_flash_clear{
+  my ($self) = @_;
+  $self->_no_flash_clear('1');
+  return $self;
+}
+
 sub error {
   my ($self, @messages) = @_;
 
 sub error {
   my ($self, @messages) = @_;
 
@@ -224,6 +278,12 @@ sub error {
   return $self;
 }
 
   return $self;
 }
 
+sub init_controller {
+  # fallback
+  require SL::Controller::Base;
+  SL::Controller::Base->new;
+}
+
 1;
 __END__
 
 1;
 __END__
 
@@ -243,24 +303,19 @@ First some JavaScript code:
   // In the client generate an AJAX request whose 'success' handler
   // calls "eval_json_result(data)":
   var data = {
   // In the client generate an AJAX request whose 'success' handler
   // calls "eval_json_result(data)":
   var data = {
-    action: "SomeController/the_action",
+    action: "SomeController/my_personal_action",
     id:     $('#some_input_field').val()
   };
   $.post("controller.pl", data, eval_json_result);
 
     id:     $('#some_input_field').val()
   };
   $.post("controller.pl", data, eval_json_result);
 
-Now some Perl code:
+Now some Controller (perl) code for my personal action:
 
 
-  # In the controller itself. First, make sure that the "client_js.js"
-  # is loaded. This must be done when the whole side is loaded, so
-  # it's not in the action called by the AJAX request shown above.
-  $::request->layout->use_javascript('client_js.js');
-
-  # Now in that action called via AJAX:
-  sub action_the_action {
+  # my personal action
+  sub action_my_personal_action {
     my ($self) = @_;
 
     # Create a new client-side JS object and do stuff with it!
     my ($self) = @_;
 
     # Create a new client-side JS object and do stuff with it!
-    my $js = SL::ClientJS->new;
+    my $js = SL::ClientJS->new(controller => $self);
 
     # Show some element on the page:
     $js->show('#usually_hidden');
 
     # Show some element on the page:
     $js->show('#usually_hidden');
@@ -286,7 +341,7 @@ Now some Perl code:
 
     # Rendering can also be chained, e.g.
     $js->html('#selector', $html)
 
     # Rendering can also be chained, e.g.
     $js->html('#selector', $html)
-       ->render($self);
+       ->render;
   }
 
 =head1 OVERVIEW
   }
 
 =head1 OVERVIEW
@@ -295,7 +350,7 @@ This module enables the generation of jQuery-using JavaScript code on
 the server side. That code is then evaluated in a safe way on the
 client side.
 
 the server side. That code is then evaluated in a safe way on the
 client side.
 
-The workflow is usally that the client creates an AJAX request, the
+The workflow is usually that the client creates an AJAX request, the
 server creates some actions and sends them back, and the client then
 implements each of these actions.
 
 server creates some actions and sends them back, and the client then
 implements each of these actions.
 
@@ -336,13 +391,16 @@ are the function parameters.
 Returns the actions gathered so far as a JSON string ready to be sent
 to the client.
 
 Returns the actions gathered so far as a JSON string ready to be sent
 to the client.
 
-=item C<render $controller>
+=item C<render [$controller]>
 
 Renders C<$self> via the controller. Useful for chaining. Equivalent
 to the following:
 
   $controller->render(\$self->to_json, { type => 'json' });
 
 
 Renders C<$self> via the controller. Useful for chaining. Equivalent
 to the following:
 
   $controller->render(\$self->to_json, { type => 'json' });
 
+The controller instance to use can be set during object creation (see
+synopsis) or as an argument to C<render>.
+
 =item C<dialog>
 
 Tells C<$self> that the next action is to be called on a jQuery UI
 =item C<dialog>
 
 Tells C<$self> that the next action is to be called on a jQuery UI
@@ -434,6 +492,17 @@ C<flash> on the same C<$self> will be merged by type.
 
 On the client side the flashes of all types will be cleared after each
 successful ClientJS call that did not end with C<$js-E<gt>error(...)>.
 
 On the client side the flashes of all types will be cleared after each
 successful ClientJS call that did not end with C<$js-E<gt>error(...)>.
+This clearing can be switched of by the function C<no_flash_clear>
+
+=item C<flash_detail $type, $message>
+
+Display a detailed message C<$message> in the flash of type C<$type>. Multiple calls of
+C<flash_detail> on the same C<$self> will be merged by type.
+So the flash message can be hold short and the visibility of details can toggled by the user.
+
+=item C<no_flash_clear>
+
+No automatic clearing of flash after successful ClientJS call
 
 =item C<error $message>
 
 
 =item C<error $message>
 
@@ -591,7 +660,7 @@ C<select_node>, C<deselect_node>, C<deselect_all>
 
 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
 
 
 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
 
-In order not having to maintain two files (this one and
+In order to not have to maintain two files (this one and
 C<js/client_js.js>) there's a script that can parse this file's
 C<%supported_methods> definition and generate the file
 C<js/client_js.js> accordingly. The steps are:
 C<js/client_js.js>) there's a script that can parse this file's
 C<%supported_methods> definition and generate the file
 C<js/client_js.js> accordingly. The steps are: