X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FClientJS.pm;h=d58905e75986cb90178a56a7f3a6a30d3164dc0e;hb=feacad49259a8288edefa4c771fc028dd6380ca0;hp=4a0080fc5660907d64d0403bf46d39e1b7726525;hpb=4d9d7d51900ba9fc5690e4ac11c18f524eba6969;p=kivitendo-erp.git diff --git a/SL/ClientJS.pm b/SL/ClientJS.pm index 4a0080fc5..d58905e75 100644 --- a/SL/ClientJS.pm +++ b/SL/ClientJS.pm @@ -9,10 +9,15 @@ use SL::JSON (); use Rose::Object::MakeMethods::Generic ( - 'scalar --get_set_init' => [ qw(_actions) ], + 'scalar --get_set_init' => [ qw(_actions _flash _error) ], ); my %supported_methods = ( + # ## Non-jQuery methods ## + flash => 2, # display_flash(, ) + + # ## jQuery basics ## + # Basic effects hide => 1, show => 1, @@ -53,9 +58,46 @@ my %supported_methods = ( removeProp => 2, val => 2, + # Class attribute + addClass => 2, + removeClass => 2, + toggleClass => 2, + # Data storage data => 3, removeData => 2, + + # Form Events + focus => 1, + + # ## jstree plugin ## pattern: $.jstree._reference($()).() + + # 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:create_node' => 4, + '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($()).(, true) + 'jstree:deselect_node' => 2, + 'jstree:deselect_all' => 1, + + # ## other stuff ## + redirect_to => 1, # window.location.href = ); sub AUTOLOAD { @@ -66,9 +108,14 @@ sub AUTOLOAD { my $method = $AUTOLOAD; $method =~ s/.*:://; return if $method eq 'DESTROY'; + return $self->action($method, @args); +} + +sub action { + my ($self, $method, @args) = @_; + $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; @@ -84,12 +131,28 @@ sub AUTOLOAD { return $self; } +sub action_if { + my ($self, $condition, @args) = @_; + + return $condition ? $self->action(@args) : $self; +} + sub init__actions { return []; } +sub init__flash { + return {}; +} + +sub init__error { + return ''; +} + 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 }); } @@ -103,6 +166,35 @@ sub render { return $controller->render(\$self->to_json, { type => 'json' }); } +sub jstree { + my ($self) = @_; + $self->{_prefix} = 'jstree:'; + return $self; +} + +sub flash { + my ($self, $type, @messages) = @_; + + my $message = join ' ', grep { $_ } @messages; + + if (!$self->_flash->{$type}) { + $self->_flash->{$type} = [ 'flash', $type, $message ]; + push @{ $self->_actions }, $self->_flash->{$type}; + } else { + $self->_flash->{$type}->[-1] .= ' ' . $message; + } + + return $self; +} + +sub error { + my ($self, @messages) = @_; + + $self->_error(join ' ', grep { $_ } ($self->_error, @messages)); + + return $self; +} + 1; __END__ @@ -152,8 +244,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,10 +311,109 @@ to the following: $controller->render(\$self->to_json, { type => 'json' }); +=item C + +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 +=head2 GENERIC FUNCTION + +All of the following functions can be invoked in two ways: either by +calling the function name directly on C<$self> or by calling +L with the function name as the first parameter. Therefore +the following two calls are identical: + + $js->insertAfter($html, '#some-id'); + $js->action('insertAfter', $html, '#some-id'); + +The second form, calling L, is more to type but can be useful +in situations in which you have to call one of two functions depending +on context. For example, when you want to insert new code in a +list. If the list is empty you might have to use C, if it +isn't you might have to use C. Example: + + my $html = $self->render(...); + $js->action($list_is_empty ? 'appendTo' : 'insertAfter', $html, '#text-block-' . ($list_is_empty ? 'list' : $self->text_block->id)); + +Instead of: + + my $html = $self->render(...); + if ($list_is_empty) { + $js->appendTo($html, '#text-block-list'); + } else { + $js->insertAfter($html, '#text-block-' . $self->text_block->id); + } + +The first variation is obviously better suited for chaining. + +=over 4 + +=item C + +Call the function with the name C<$method> on C<$self> with arguments +C<@args>. Returns the return value of the actual function +called. Useful for chaining (see above). + +=item C + +Call the function with the name C<$method> on C<$self> with arguments +C<@args> if C<$condition> is trueish. Does nothing otherwise. + +Returns the return value of the actual function called if +C<$condition> is trueish and C<$self> otherwise. Useful for chaining +(see above). + +This function is equivalent to the following: + + if ($condition) { + $obj->$method(@args); + } + +But it is easier to integrate into a method call chain, e.g.: + + $js->html('#content', $html) + ->action_if($item->is_flagged, 'toggleClass', '#marker', 'flagged') + ->render($self); + +=back + +=head2 ADDITIONAL FUNCTIONS + +=over 4 + +=item C + +Display a C<$message> in the flash of type C<$type>. Multiple calls of +C on the same C<$self> will be merged by type. + +On the client side the flash of this type will be cleared before the +message is shown. + +=item C + +Causes L (and therefore L) to output a JSON object +that only contains an C field set to this C<$message>. The +client will then show the message in the 'error' flash. + +The messages of multiple calls of C on the same C<$self> will +be merged. + +=item C + +Redirects the browser window to the new URL by setting the JavaScript +property C. Note that +L is AJAX aware and uses this +function if the current request is an AJAX request as determined by +L. + +=back + =head2 JQUERY FUNCTIONS The following jQuery functions are supported: @@ -252,15 +452,44 @@ C, C, C, C, C C, C +=item Form Events + +C + +=back + +=head2 JSTREE JQUERY PLUGIN + +The following functions of the C plugin to jQuery are +supported: + +=over 4 + +=item Operations on the whole tree + +C, C + +=item Opening and closing nodes + +C, C, C, C, +C, C, C + +=item Modifying nodes + +C, C, C + +=item Selecting nodes (from the 'ui' jstree plugin) + +C, C, C + =back =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS In order not having to maintain two files (this one and C) 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. The steps -are: +C<%supported_methods> definition and generate the file +C accordingly. The steps are: =over 2 @@ -268,13 +497,17 @@ are: key is the function name and the value is the number of expected parameters. -=item 2. Run C +=item 2. Run C. It will +generate C automatically. -=item 3. Edit C 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 is +C. + =head1 BUGS Nothing here yet.