ClientJS: neue Funktionen "run()", "run_once_for()"; Dokumentation
authorMoritz Bunkus <m.bunkus@linet-services.de>
Mon, 12 Aug 2013 09:23:06 +0000 (11:23 +0200)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Mon, 12 Aug 2013 10:10:54 +0000 (12:10 +0200)
SL/ClientJS.pm
js/client_js.js
js/kivi.js
scripts/generate_client_js_actions.pl

index 204e320..ff4aeb8 100644 (file)
@@ -13,9 +13,6 @@ use Rose::Object::MakeMethods::Generic
 );
 
 my %supported_methods = (
-  # ## Non-jQuery methods ##
-  flash        => 2,            # kivi.display_flash(<TARGET>, <ARGS>)
-
   # ## jQuery basics ##
 
   # Basic effects
@@ -112,7 +109,10 @@ my %supported_methods = (
   # ## other stuff ##
   redirect_to            => 1,  # window.location.href = <TARGET>
 
+  flash                  => 2,  # kivi.display_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>)
 );
 
 sub AUTOLOAD {
@@ -133,7 +133,14 @@ sub action {
   my $num_args =  $supported_methods{$method};
 
   croak "Unsupported jQuery action: $method"                                                    unless defined $num_args;
-  croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if     scalar(@args) != $num_args;
+
+  if ($num_args > 0) {
+    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;
+    $num_args  = scalar @args;
+  }
 
   foreach my $idx (0..$num_args - 1) {
     # Force flattening from SL::Presenter::EscapedText and trim leading whitespace for scalars
@@ -447,6 +454,26 @@ L<SL::Request/is_ajax>.
 
 =back
 
+=head2 KIVITENDO FUNCTIONS
+
+The following functions from the C<kivi> namespace are supported:
+
+=over 4
+
+=item Displaying stuff
+
+C<flash> (don't call directly, use L</flash> instead)
+
+=item Running functions
+
+C<run>, C<run_once_for>
+
+=item Widgets
+
+C<reinit_widgets>
+
+=back
+
 =head2 JQUERY FUNCTIONS
 
 The following jQuery functions are supported:
@@ -481,6 +508,10 @@ C<replaceAll>, C<replaceWith>
 
 C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>
 
+=item Class attributes
+
+C<addClass>, C<removeClass>, C<toggleClass>
+
 =item Data storage
 
 C<data>, C<removeData>
@@ -500,13 +531,45 @@ already exist when the handler is added.
 
 =back
 
-=head2 JSTREE JQUERY PLUGIN
+=head2 JQUERY POPUP DIALOG PLUGIN
 
-The following functions of the C<jstree> plugin to jQuery are
+Supported functions of the C<popup dialog> plugin to jQuery. They are
+invoked by first calling C<dialog> in the ClientJS instance and then
+the function itself:
+
+  $js->dialog->close(...);
+
+=over 4
+
+=item Closing and removing the popup
+
+C<close>
+
+=back
+
+=head2 AJAXFORM JQUERY PLUGIN
+
+The following functions of the C<ajaxForm> plugin to jQuery are
 supported:
 
 =over 4
 
+=item All functions by the generic accessor function:
+
+C<ajaxForm>
+
+=back
+
+=head2 JSTREE JQUERY PLUGIN
+
+Supported functions of the C<jstree> plugin to jQuery. They are
+invoked by first calling C<jstree> in the ClientJS instance and then
+the function itself:
+
+  $js->jstree->open_node(...);
+
+=over 4
+
 =item Operations on the whole tree
 
 C<lock>, C<unlock>
@@ -537,7 +600,10 @@ C<js/client_js.js> accordingly. The steps are:
 
 =item 1. Add lines in this file to the C<%supported_methods> hash. The
 key is the function name and the value is the number of expected
-parameters.
+parameters. The value can be negative to indicate that the function
+takes at least the absolute of this value as parameters and optionally
+more. In such a case the C<E<lt>ARGSE<gt>> format expands to an actual
+array (and the individual elements if the value is positive>.
 
 =item 2. Run C<scripts/generate_client_js_actions.pl>. It will
 generate C<js/client_js.js> automatically.
index 4697fa6..f39b192 100644 (file)
@@ -29,13 +29,9 @@ ns.eval_json_result = function(data) {
     $(data.eval_actions).each(function(idx, action) {
       // console.log("ACTION " + action[0] + " ON " + action[1]);
 
-      // ## Non-jQuery methods ##
-           if (action[0] == 'flash')                kivi.display_flash(action[1], action[2]);
-
       // ## jQuery basics ##
-
       // Basic effects
-      else if (action[0] == 'hide')                 $(action[1]).hide();
+           if (action[0] == 'hide')                 $(action[1]).hide();
       else if (action[0] == 'show')                 $(action[1]).show();
       else if (action[0] == 'toggle')               $(action[1]).toggle();
 
@@ -127,7 +123,10 @@ ns.eval_json_result = function(data) {
 
       // ## other stuff ##
       else if (action[0] == 'redirect_to')          window.location.href = action[1];
+      else if (action[0] == 'flash')                kivi.display_flash(action[1], action[2]);
       else if (action[0] == 'reinit_widgets')       kivi.reinit_widgets();
+      else if (action[0] == 'run')                  kivi.run(action[1], action.slice(2, action.length));
+      else if (action[0] == 'run_once_for')         kivi.run_once_for(action[1], action[2], action[3]);
 
       else                                          console.log('Unknown action: ' + action[0]);
 
index d84f28d..a39820a 100644 (file)
@@ -99,6 +99,52 @@ namespace("kivi", function(ns) {
 
     return true;
   };
+
+  // Run code only once for each matched element
+  //
+  // This allows running the function 'code' exactly once for each
+  // element that matches 'selector'. This is achieved by storing the
+  // state with jQuery's 'data' function. The 'identification' is
+  // required for differentiating unambiguously so that different code
+  // functions can still be run on the same elements.
+  //
+  // 'code' can be either a function or the name of one. It must
+  // resolve to a function that receives the jQueryfied element as its
+  // sole argument.
+  //
+  // Returns nothing.
+  ns.run_once_for = function(selector, identification, code) {
+    var attr_name = 'data-run-once-for-' + identification.toLowerCase().replace(/[^a-z]+/g, '-');
+    var fn        = typeof code === 'function' ? code : ns.get_function_by_name(code);
+    if (!fn) {
+      console.error('kivi.run_once_for(..., "' + code + '"): No function by that name found');
+      return;
+    }
+
+    $(selector).filter(function() { return $(this).data(attr_name) != true; }).each(function(idx, elt) {
+      var $elt = $(elt);
+      $elt.data(attr_name, true);
+      fn($elt);
+    });
+  };
+
+  // Run a function by its name passing it some arguments
+  //
+  // This is a function useful mainly for the ClientJS functionality.
+  // It finds a function by its name and then executes it on an empty
+  // object passing the elements in 'args' (an array) as the function
+  // parameters retuning its result.
+  //
+  // Logs an error to the console and returns 'undefined' if the
+  // function cannot be found.
+  ns.run = function(function_name, args) {
+    var fn = ns.get_function_by_name(function_name);
+    if (fn)
+      return fn.apply({}, args);
+
+    console.error('kivi.run("' + function_name + '"): No function by that name found');
+    return undefined;
+  };
 });
 
 kivi = namespace('kivi');
index 448a42c..a51c669 100755 (executable)
@@ -15,7 +15,7 @@ foreach (read_file("${rel_dir}/SL/ClientJS.pm")) {
 
   next unless (m/^my \%supported_methods/ .. m/^\);/);
 
-  push @actions, [ 'action',  $1, $2, $3 ] if m/^ \s+ '? ([a-zA-Z_:]+) '? \s*=>\s* (\d+) , (?: \s* \# \s+ (.+))? $/x;
+  push @actions, [ 'action',  $1, $2, $3 ] if m/^ \s+ '? ([a-zA-Z_:]+) '? \s*=>\s* (-? \d+) , (?: \s* \# \s+ (.+))? $/x;
   push @actions, [ 'comment', $1, $2     ] if m/^ \s+\# \s+ (.+?) (?: \s* pattern: \s+ (.+))? $/x;
 }
 
@@ -33,7 +33,9 @@ foreach my $action (@actions) {
     $pattern = $action->[2] eq '<DEFAULT>' ? $default_pattern : $action->[2] if $action->[2];
 
   } else {
-    my $args = $action->[2] == 1 ? '' : join(', ', map { "action[$_]" } (2..$action->[2]));
+    my $args = $action->[2] == 1 ? ''
+             : $action->[2] <  0 ? 'action.slice(2, action.length)'
+             :                     join(', ', map { "action[$_]" } (2..$action->[2]));
 
     $output .= sprintf('      %s if (action[0] == \'%s\')%s ',
                        $first ? '    ' : 'else',