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 = (
- # ## Non-jQuery methods ##
- flash => 2, # kivi.display_flash(<TARGET>, <ARGS>)
-
# ## jQuery basics ##
# Basic effects
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,
off => 3,
one => 3,
- # ## jqModal plugin ##
+ # ## jQuery UI dialog plugin ## pattern: $(<TARGET>).dialog('<FUNCTION>')
+
+ # Opening and closing a popup
+ 'dialog:open' => 1, # kivi.popup_dialog(<TARGET>)
+ 'dialog:close' => 1,
- # Closing and removing the popup
- jqmClose => 1, # $(<TARGET>).jqmClose()
+ # ## jQuery Form plugin ##
+ 'ajaxForm' => 1, # $(<TARGET>).ajaxForm({ success: eval_json_result })
# ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
'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>
+ 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>)
+
+ scroll_into_view => 1, # $(<TARGET>)[0].scrollIntoView()
);
+my %trim_target_for = map { ($_ => 1) } qw(insertAfter insertBefore appendTo prependTo);
+
sub AUTOLOAD {
our $AUTOLOAD;
$method = (delete($self->{_prefix}) || '') . $method;
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;
+ croak "Unsupported jQuery action: $method" unless defined $num_args;
- if ($num_args) {
- # Force flattening from SL::Presenter::EscapedText: "" . $...
- $args[0] = "" . $args[0];
- $args[0] =~ s/^\s+//;
+ 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.
+ $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;
return {};
}
+sub init__flash_detail {
+ return {};
+}
+
sub init__error {
return '';
}
+sub init__no_flash_clear {
+ 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 });
+ 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 render {
my ($self, $controller) = @_;
+ $controller ||= $self->controller;
$self->reinit_widgets if $::request->presenter->need_reinit_widgets;
return $controller->render(\$self->to_json, { type => 'json' });
}
return $self;
}
+sub dialog {
+ my ($self) = @_;
+ $self->{_prefix} = 'dialog:';
+ return $self;
+}
+
+sub ckeditor {
+ my ($self) = @_;
+ $self->{_prefix} = 'ckeditor:';
+ return $self;
+}
+
sub flash {
my ($self, $type, @messages) = @_;
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) = @_;
return $self;
}
+sub init_controller {
+ # fallback
+ require SL::Controller::Base;
+ SL::Controller::Base->new;
+}
+
1;
__END__
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');
$js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
->jstree->select_node('#tb-' . $text_block->id);
+ # Close a popup opened by kivi.popup_dialog():
+ $js->dialog->close('#jqueryui_popup_dialog');
+
# Finally render the JSON response:
$self->render($js);
# Rendering can also be chained, e.g.
$js->html('#selector', $html)
- ->render($self);
+ ->render;
}
=head1 OVERVIEW
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.
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' });
+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
+dialog instance, e.g. one opened by C<kivi.popup_dialog()>. For
+example:
+
+ $js->dialog->close('#jqueryui_popup_dialog');
+
=item C<jstree>
Tells C<$self> that the next action is to be called on a jstree
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>
=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:
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>
=back
-=head2 JSTREE JQUERY PLUGIN
+=head2 JQUERY POPUP DIALOG PLUGIN
+
+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<jstree> plugin to jQuery are
+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>
=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:
=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.