ClientJS: um jstree-Funktionen erweitert; client_js.js komplett automatisch erzeugen
authorMoritz Bunkus <m.bunkus@linet-services.de>
Wed, 6 Mar 2013 15:31:28 +0000 (16:31 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Wed, 6 Mar 2013 16:06:33 +0000 (17:06 +0100)
SL/ClientJS.pm
js/client_js.js
scripts/generate_client_js_actions.pl
scripts/generate_client_js_actions.tpl [new file with mode: 0644]

index 4a0080f..6f9ce5a 100644 (file)
@@ -13,6 +13,8 @@ use Rose::Object::MakeMethods::Generic
 );
 
 my %supported_methods = (
+  # ## jQuery basics ##
+
   # Basic effects
   hide         => 1,
   show         => 1,
@@ -56,6 +58,31 @@ my %supported_methods = (
   # Data storage
   data         => 3,
   removeData   => 2,
+
+  # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
+
+  # Operations on the whole tree
+  'jstree:lock'          => 1,
+  'jstree:unlock'        => 1,
+
+  # Opening and closing nodes
+  'jstree:open_node'     => 2,
+  'jstree:open_all'      => 2,
+  'jstree:close_node'    => 2,
+  'jstree:close_all'     => 2,
+  'jstree:toggle_node'   => 2,
+  'jstree:save_opened'   => 1,
+  'jstree:reopen'        => 1,
+
+  # Modifying nodes
+  'jstree:rename_node'   => 3,
+  'jstree:delete_node'   => 2,
+  'jstree:move_node'     => 5,
+
+  # Selecting nodes (from the 'ui' plugin to jstree)
+  'jstree:select_node'   => 2,  # $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>, true)
+  'jstree:deselect_node' => 2,
+  'jstree:deselect_all'  => 1,
 );
 
 sub AUTOLOAD {
@@ -67,8 +94,8 @@ sub AUTOLOAD {
   $method           =~ s/.*:://;
   return if $method eq 'DESTROY';
 
+  $method      =  (delete($self->{_prefix}) || '') . $method;
   my $num_args =  $supported_methods{$method};
-  $::lxdebug->message(0, "autoload method $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;
@@ -103,6 +130,12 @@ sub render {
   return $controller->render(\$self->to_json, { type => 'json' });
 }
 
+sub jstree {
+  my ($self) = @_;
+  $self->{_prefix} = 'jstree:';
+  return $self;
+}
+
 1;
 __END__
 
@@ -152,8 +185,17 @@ Now some Perl code:
     my $html = $self->render('SomeController/the_action', { output => 0 });
     $js->html('#id_with_new_content', $html);
 
+    # Operations on a jstree: rename a node and select it
+    my $text_block = SL::DB::RequirementSpecTextBlock->new(id => 4711)->load;
+    $js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
+       ->jstree->select_node('#tb-' . $text_block->id);
+
     # Finally render the JSON response:
     $self->render($js);
+
+    # Rendering can also be chained, e.g.
+    $js->html('#selector', $html)
+       ->render($self);
   }
 
 =head1 OVERVIEW
@@ -210,6 +252,13 @@ to the following:
 
   $controller->render(\$self->to_json, { type => 'json' });
 
+=item C<jstree>
+
+Tells C<$self> that the next action is to be called on a jstree
+instance. For example:
+
+  $js->jstree->rename_node('tb-' . $text_block->id, $text_block->title);
+
 =back
 
 =head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE
@@ -254,13 +303,38 @@ C<data>, C<removeData>
 
 =back
 
+=head2 JSTREE JQUERY PLUGIN
+
+The following functions of the C<jstree> plugin to jQuery are
+supported:
+
+=over 4
+
+=item Operations on the whole tree
+
+C<lock>, C<unlock>
+
+=item Opening and closing nodes
+
+C<open_node>, C<close_node>, C<toggle_node>, C<open_all>,
+C<close_all>, C<save_opened>, C<reopen>
+
+=item Modifying nodes
+
+C<rename_node>, C<delete_node>, C<move_node>
+
+=item Selecting nodes (from the 'ui' jstree plugin)
+
+C<select_node>, C<deselect_node>, C<deselect_all>
+
+=back
+
 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
 
 In order not having 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 convert it into the appropriate
-code ready for manual insertion into C<js/client_js.js>. The steps
-are:
+C<%supported_methods> definition and generate the file
+C<js/client_js.js> accordingly. The steps are:
 
 =over 2
 
@@ -268,13 +342,17 @@ are:
 key is the function name and the value is the number of expected
 parameters.
 
-=item 2. Run C<scripts/generate_client_js_actions.pl>
+=item 2. Run C<scripts/generate_client_js_actions.pl>. It will
+generate C<js/client_js.js> automatically.
 
-=item 3. Edit C<js/client_js.js> and replace the type casing code with
-the output generated in step 2.
+=item 3. Reload the files in your browser (cleaning its cache can also
+help).
 
 =back
 
+The template file used for generated C<js/client_js.js> is
+C<scripts/generate_client_js_actions.tpl>.
+
 =head1 BUGS
 
 Nothing here yet.
index 3354fe7..34b254b 100644 (file)
@@ -1,6 +1,6 @@
 // NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE:
 
-// Generate the dispatching lines in this script by running
+// This file is generated automatically by the script
 // "scripts/generate_client_js_actions.pl". See the documentation for
 // SL/ClientJS.pm for instructions.
 
@@ -15,52 +15,79 @@ function eval_json_result(data) {
     $(data.eval_actions).each(function(idx, action) {
       // console.log("ACTION " + action[0] + " ON " + action[1]);
 
+      // ## jQuery basics ##
       // Basic effects
-           if (action[0] == 'hide')         $(action[1]).hide();
-      else if (action[0] == 'show')         $(action[1]).show();
-      else if (action[0] == 'toggle')       $(action[1]).toggle();
+           if (action[0] == 'hide')                 $(action[1]).hide();
+      else if (action[0] == 'show')                 $(action[1]).show();
+      else if (action[0] == 'toggle')               $(action[1]).toggle();
 
       // DOM insertion, around
-      else if (action[0] == 'unwrap')       $(action[1]).unwrap();
-      else if (action[0] == 'wrap')         $(action[1]).wrap(action[2]);
-      else if (action[0] == 'wrapAll')      $(action[1]).wrapAll(action[2]);
-      else if (action[0] == 'wrapInner')    $(action[1]).wrapInner(action[2]);
+      else if (action[0] == 'unwrap')               $(action[1]).unwrap();
+      else if (action[0] == 'wrap')                 $(action[1]).wrap(action[2]);
+      else if (action[0] == 'wrapAll')              $(action[1]).wrapAll(action[2]);
+      else if (action[0] == 'wrapInner')            $(action[1]).wrapInner(action[2]);
 
       // DOM insertion, inside
-      else if (action[0] == 'append')       $(action[1]).append(action[2]);
-      else if (action[0] == 'appendTo')     $(action[1]).appendTo(action[2]);
-      else if (action[0] == 'html')         $(action[1]).html(action[2]);
-      else if (action[0] == 'prepend')      $(action[1]).prepend(action[2]);
-      else if (action[0] == 'prependTo')    $(action[1]).prependTo(action[2]);
-      else if (action[0] == 'text')         $(action[1]).text(action[2]);
+      else if (action[0] == 'append')               $(action[1]).append(action[2]);
+      else if (action[0] == 'appendTo')             $(action[1]).appendTo(action[2]);
+      else if (action[0] == 'html')                 $(action[1]).html(action[2]);
+      else if (action[0] == 'prepend')              $(action[1]).prepend(action[2]);
+      else if (action[0] == 'prependTo')            $(action[1]).prependTo(action[2]);
+      else if (action[0] == 'text')                 $(action[1]).text(action[2]);
 
       // DOM insertion, outside
-      else if (action[0] == 'after')        $(action[1]).after(action[2]);
-      else if (action[0] == 'before')       $(action[1]).before(action[2]);
-      else if (action[0] == 'insertAfter')  $(action[1]).insertAfter(action[2]);
-      else if (action[0] == 'insertBefore') $(action[1]).insertBefore(action[2]);
+      else if (action[0] == 'after')                $(action[1]).after(action[2]);
+      else if (action[0] == 'before')               $(action[1]).before(action[2]);
+      else if (action[0] == 'insertAfter')          $(action[1]).insertAfter(action[2]);
+      else if (action[0] == 'insertBefore')         $(action[1]).insertBefore(action[2]);
 
       // DOM removal
-      else if (action[0] == 'empty')        $(action[1]).empty();
-      else if (action[0] == 'remove')       $(action[1]).remove();
+      else if (action[0] == 'empty')                $(action[1]).empty();
+      else if (action[0] == 'remove')               $(action[1]).remove();
 
       // DOM replacement
-      else if (action[0] == 'replaceAll')   $(action[1]).replaceAll(action[2]);
-      else if (action[0] == 'replaceWith')  $(action[1]).replaceWith(action[2]);
+      else if (action[0] == 'replaceAll')           $(action[1]).replaceAll(action[2]);
+      else if (action[0] == 'replaceWith')          $(action[1]).replaceWith(action[2]);
 
       // General attributes
-      else if (action[0] == 'attr')         $(action[1]).attr(action[2], action[3]);
-      else if (action[0] == 'prop')         $(action[1]).prop(action[2], action[3]);
-      else if (action[0] == 'removeAttr')   $(action[1]).removeAttr(action[2]);
-      else if (action[0] == 'removeProp')   $(action[1]).removeProp(action[2]);
-      else if (action[0] == 'val')          $(action[1]).val(action[2]);
+      else if (action[0] == 'attr')                 $(action[1]).attr(action[2], action[3]);
+      else if (action[0] == 'prop')                 $(action[1]).prop(action[2], action[3]);
+      else if (action[0] == 'removeAttr')           $(action[1]).removeAttr(action[2]);
+      else if (action[0] == 'removeProp')           $(action[1]).removeProp(action[2]);
+      else if (action[0] == 'val')                  $(action[1]).val(action[2]);
 
       // Data storage
-      else if (action[0] == 'data')         $(action[1]).data(action[2], action[3]);
-      else if (action[0] == 'removeData')   $(action[1]).removeData(action[2]);
+      else if (action[0] == 'data')                 $(action[1]).data(action[2], action[3]);
+      else if (action[0] == 'removeData')           $(action[1]).removeData(action[2]);
+
+      // ## jstree plugin ##
+
+      // Operations on the whole tree
+      else if (action[0] == 'jstree:lock')          $.jstree._reference($(action[1])).lock();
+      else if (action[0] == 'jstree:unlock')        $.jstree._reference($(action[1])).unlock();
+
+      // Opening and closing nodes
+      else if (action[0] == 'jstree:open_node')     $.jstree._reference($(action[1])).open_node(action[2]);
+      else if (action[0] == 'jstree:open_all')      $.jstree._reference($(action[1])).open_all(action[2]);
+      else if (action[0] == 'jstree:close_node')    $.jstree._reference($(action[1])).close_node(action[2]);
+      else if (action[0] == 'jstree:close_all')     $.jstree._reference($(action[1])).close_all(action[2]);
+      else if (action[0] == 'jstree:toggle_node')   $.jstree._reference($(action[1])).toggle_node(action[2]);
+      else if (action[0] == 'jstree:save_opened')   $.jstree._reference($(action[1])).save_opened();
+      else if (action[0] == 'jstree:reopen')        $.jstree._reference($(action[1])).reopen();
+
+      // Modifying nodes
+      else if (action[0] == 'jstree:rename_node')   $.jstree._reference($(action[1])).rename_node(action[2], action[3]);
+      else if (action[0] == 'jstree:delete_node')   $.jstree._reference($(action[1])).delete_node(action[2]);
+      else if (action[0] == 'jstree:move_node')     $.jstree._reference($(action[1])).move_node(action[2], action[3], action[4], action[5]);
+
+      // Selecting nodes (from the 'ui' plugin to jstree)
+      else if (action[0] == 'jstree:select_node')   $.jstree._reference($(action[1])).select_node(action[2], true);
+      else if (action[0] == 'jstree:deselect_node') $.jstree._reference($(action[1])).deselect_node(action[2]);
+      else if (action[0] == 'jstree:deselect_all')  $.jstree._reference($(action[1])).deselect_all();
+
+      else                                          console.log('Unknown action: ' + action[0]);
 
-      else                                  console.log("Unknown action: " + action[0]);
     });
 
-  console.log("current_content_type " + $('#current_content_type').val() + ' ID ' + $('#current_content_id').val());
+  // console.log("current_content_type " + $('#current_content_type').val() + ' ID ' + $('#current_content_id').val());
 }
index b2598b3..d2219a1 100755 (executable)
@@ -5,40 +5,56 @@ use warnings;
 
 use File::Slurp;
 use List::Util qw(first max);
+use Template;
 
-my $file_name = (first { -f } qw(SL/ClientJS.pm ../SL/ClientJS.pm)) || die "ClientJS.pm not found";
+my $rel_dir = (first { -f "${_}/SL/ClientJS.pm" } qw(. ..)) || die "ClientJS.pm not found";
 my @actions;
 
-foreach (read_file($file_name)) {
+foreach (read_file("${rel_dir}/SL/ClientJS.pm")) {
   chomp;
 
   next unless (m/^my \%supported_methods/ .. m/^\);/);
 
-  push @actions, [ 'action',  $1, $2 ] if m/^\s+([a-zA-Z]+)\s*=>\s*(\d+),$/;
-  push @actions, [ 'comment', $1     ] if m/^\s+#\s+(.+)/;
+  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;
 }
 
-my $longest   = max map { length($_->[1]) } grep { $_->[0] eq 'action' } @actions;
-my $first     = 1;
-my $output;
+my $longest         = max map { length($_->[1]) } grep { $_->[0] eq 'action' } @actions;
+my $first           = 1;
+my $default_pattern = '$(<TARGET>).<FUNCTION>(<ARGS>)';
+my $pattern         = $default_pattern;
+my $output          = '';
 
-#      else if (action[0] == 'hide')        $(action[1]).hide();
 foreach my $action (@actions) {
   if ($action->[0] eq 'comment') {
-    print "\n" unless $first;
-    print "      // ", $action->[1], "\n";
+    $output .= "\n" unless $first;
+    $output .= "      // " . $action->[1] . "\n";
+
+    $pattern = $action->[2] eq '<DEFAULT>' ? $default_pattern : $action->[2] if $action->[2];
 
   } else {
     my $args = $action->[2] == 1 ? '' : join(', ', map { "action[$_]" } (2..$action->[2]));
 
-    printf('      %s if (action[0] == \'%s\')%s $(action[1]).%s(%s);' . "\n",
-           $first ? '    ' : 'else',
-           $action->[1],
-           ' ' x ($longest - length($action->[1])),
-           $action->[1],
-           $args);
-    $first = 0;
+    $output .= sprintf('      %s if (action[0] == \'%s\')%s ',
+                       $first ? '    ' : 'else',
+                       $action->[1],
+                       ' ' x ($longest - length($action->[1])));
+
+    my $function =  $action->[1];
+    $function    =~ s/.*://;
+
+    my $call     =  $action->[3] || $pattern;
+    $call        =~ s/<TARGET>/'action[1]'/eg;
+    $call        =~ s/<FUNCTION>/$function/eg;
+    $call        =~ s/<ARGS>/$args/eg;
+
+    $output .= $call . ";\n";
+    $first   = 0;
   }
 }
 
-printf "\n      else\%sconsole.log('Unknown action: ' + action[0]);\n", ' ' x (4 + 2 + 6 + 3 + 4 + 2 + $longest + 1);
+$output .= sprintf "\n      else\%sconsole.log('Unknown action: ' + action[0]);\n", ' ' x (4 + 2 + 6 + 3 + 4 + 2 + $longest + 1);
+
+my $template = Template->new({ RELATIVE => 1 });
+$template->process($rel_dir . '/scripts/generate_client_js_actions.tpl', { actions => $output }, $rel_dir . '/js/client_js.js') || die $template->error(), "\n";
+print "js/client_js.js generated automatically.\n";
diff --git a/scripts/generate_client_js_actions.tpl b/scripts/generate_client_js_actions.tpl
new file mode 100644 (file)
index 0000000..6572e22
--- /dev/null
@@ -0,0 +1,22 @@
+// NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE:
+
+// This file is generated automatically by the script
+// "scripts/generate_client_js_actions.pl". See the documentation for
+// SL/ClientJS.pm for instructions.
+
+function eval_json_result(data) {
+  if (!data)
+    return;
+
+  if ((data.js || '') != '')
+    eval(data.js);
+
+  if (data.eval_actions)
+    $(data.eval_actions).each(function(idx, action) {
+      // console.log("ACTION " + action[0] + " ON " + action[1]);
+
+[% actions %]
+    });
+
+  // console.log("current_content_type " + $('#current_content_type').val() + ' ID ' + $('#current_content_id').val());
+}