5 use parent qw(Rose::Object);
10 use Rose::Object::MakeMethods::Generic
12 'scalar --get_set_init' => [ qw(_actions) ],
15 my %supported_methods = (
23 # DOM insertion, around
29 # DOM insertion, inside
37 # DOM insertion, outside
65 # ## jstree plugin ## pattern: $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>)
67 # Operations on the whole tree
71 # Opening and closing nodes
72 'jstree:open_node' => 2,
73 'jstree:open_all' => 2,
74 'jstree:close_node' => 2,
75 'jstree:close_all' => 2,
76 'jstree:toggle_node' => 2,
77 'jstree:save_opened' => 1,
81 'jstree:rename_node' => 3,
82 'jstree:delete_node' => 2,
83 'jstree:move_node' => 5,
85 # Selecting nodes (from the 'ui' plugin to jstree)
86 'jstree:select_node' => 2, # $.jstree._reference($(<TARGET>)).<FUNCTION>(<ARGS>, true)
87 'jstree:deselect_node' => 2,
88 'jstree:deselect_all' => 1,
94 my ($self, @args) = @_;
96 my $method = $AUTOLOAD;
98 return if $method eq 'DESTROY';
99 return $self->action($method, @args);
103 my ($self, $method, @args) = @_;
105 $method = (delete($self->{_prefix}) || '') . $method;
106 my $num_args = $supported_methods{$method};
108 croak "Unsupported jQuery action: $method" unless defined $num_args;
109 croak "Parameter count mismatch for $method(actual: " . scalar(@args) . " wanted: $num_args)" if scalar(@args) != $num_args;
112 # Force flattening from SL::Presenter::EscapedText: "" . $...
113 $args[0] = "" . $args[0];
114 $args[0] =~ s/^\s+//;
117 push @{ $self->_actions }, [ $method, @args ];
128 return SL::JSON::to_json({ eval_actions => $self->_actions });
133 return $self->_actions;
137 my ($self, $controller) = @_;
138 return $controller->render(\$self->to_json, { type => 'json' });
143 $self->{_prefix} = 'jstree:';
156 SL::ClientJS - Easy programmatic client-side JavaScript generation
161 First some JavaScript code:
163 // In the client generate an AJAX request whose 'success' handler
164 // calls "eval_json_response(data)":
166 action: "SomeController/the_action",
167 id: $('#some_input_field').val()
169 $.post("controller.pl", data, eval_json_response);
173 # In the controller itself. First, make sure that the "client_js.js"
174 # is loaded. This must be done when the whole side is loaded, so
175 # it's not in the action called by the AJAX request shown above.
176 $::request->layout->use_javascript('client_js.js');
178 # Now in that action called via AJAX:
179 sub action_the_action {
182 # Create a new client-side JS object and do stuff with it!
183 my $js = SL::ClientJS->new;
185 # Show some element on the page:
186 $js->show('#usually_hidden');
188 # Set to hidden inputs. Yes, calls can be chained!
189 $js->val('#hidden_id', $self->new_id)
190 ->val('#other_type', 'Unicorn');
192 # Replace some HTML code:
193 my $html = $self->render('SomeController/the_action', { output => 0 });
194 $js->html('#id_with_new_content', $html);
196 # Operations on a jstree: rename a node and select it
197 my $text_block = SL::DB::RequirementSpecTextBlock->new(id => 4711)->load;
198 $js->jstree->rename_node('#tb-' . $text_block->id, $text_block->title)
199 ->jstree->select_node('#tb-' . $text_block->id);
201 # Finally render the JSON response:
204 # Rendering can also be chained, e.g.
205 $js->html('#selector', $html)
211 This module enables the generation of jQuery-using JavaScript code on
212 the server side. That code is then evaluated in a safe way on the
215 The workflow is usally that the client creates an AJAX request, the
216 server creates some actions and sends them back, and the client then
217 implements each of these actions.
219 There are three things that need to be done for this to work:
223 =item 1. The "client_js.js" has to be loaded before the AJAX request is started.
225 =item 2. The client code needs to call C<eval_json_response()> with the result returned from the server.
227 =item 3. The server must use this module.
231 The functions called on the client side are mostly jQuery
232 functions. Other functionality may be added later.
234 Note that L<SL::Controller/render> is aware of this module which saves
235 you some boilerplate. The following two calls are equivalent:
237 $controller->render($client_js);
238 $controller->render(\$client_js->to_json, { type => 'json' });
240 =head1 FUNCTIONS NOT PASSED TO THE CLIENT SIDE
246 Returns the actions gathered so far as an array reference. Each
247 element is an array reference containing at least two items: the
248 function's name and what it is called on. Additional array elements
249 are the function parameters.
253 Returns the actions gathered so far as a JSON string ready to be sent
256 =item C<render $controller>
258 Renders C<$self> via the controller. Useful for chaining. Equivalent
261 $controller->render(\$self->to_json, { type => 'json' });
265 Tells C<$self> that the next action is to be called on a jstree
266 instance. For example:
268 $js->jstree->rename_node('tb-' . $text_block->id, $text_block->title);
272 =head1 FUNCTIONS EVALUATED ON THE CLIENT SIDE
274 =head2 GENERIC FUNCTION
276 All of the following functions can be invoked in two ways: either by
277 calling the function name directly on C<$self> or by calling
278 L</action> with the function name as the first parameter. Therefore
279 the following two calls are identical:
281 $js->insertAfter($html, '#some-id');
282 $js->action('insertAfter', $html, '#some-id');
284 The second form, calling L</action>, is more to type but can be useful
285 in situations in which you have to call one of two functions depending
286 on context. For example, when you want to insert new code in a
287 list. If the list is empty you might have to use C<appendTo>, if it
288 isn't you might have to use C<insertAfter>. Example:
290 my $html = $self->render(...);
291 $js->action($list_is_empty ? 'appendTo' : 'insertAfter', $html, '#text-block-' . ($list_is_empty ? 'list' : $self->text_block->id));
295 my $html = $self->render(...);
296 if ($list_is_empty) {
297 $js->appendTo($html, '#text-block-list');
299 $js->insertAfter($html, '#text-block-' . $self->text_block->id);
302 The first variation is obviously better suited for chaining.
304 =head2 JQUERY FUNCTIONS
306 The following jQuery functions are supported:
312 C<hide>, C<show>, C<toggle>
314 =item DOM insertion, around
316 C<unwrap>, C<wrap>, C<wrapAll>, C<wrapInner>
318 =item DOM insertion, inside
320 C<append>, C<appendTo>, C<html>, C<prepend>, C<prependTo>, C<text>
322 =item DOM insertion, outside
324 C<after>, C<before>, C<insertAfter>, C<insertBefore>
330 =item DOM replacement
332 C<replaceAll>, C<replaceWith>
334 =item General attributes
336 C<attr>, C<prop>, C<removeAttr>, C<removeProp>, C<val>
340 C<data>, C<removeData>
348 =head2 JSTREE JQUERY PLUGIN
350 The following functions of the C<jstree> plugin to jQuery are
355 =item Operations on the whole tree
359 =item Opening and closing nodes
361 C<open_node>, C<close_node>, C<toggle_node>, C<open_all>,
362 C<close_all>, C<save_opened>, C<reopen>
364 =item Modifying nodes
366 C<rename_node>, C<delete_node>, C<move_node>
368 =item Selecting nodes (from the 'ui' jstree plugin)
370 C<select_node>, C<deselect_node>, C<deselect_all>
374 =head1 ADDING SUPPORT FOR ADDITIONAL FUNCTIONS
376 In order not having to maintain two files (this one and
377 C<js/client_js.js>) there's a script that can parse this file's
378 C<%supported_methods> definition and generate the file
379 C<js/client_js.js> accordingly. The steps are:
383 =item 1. Add lines in this file to the C<%supported_methods> hash. The
384 key is the function name and the value is the number of expected
387 =item 2. Run C<scripts/generate_client_js_actions.pl>. It will
388 generate C<js/client_js.js> automatically.
390 =item 3. Reload the files in your browser (cleaning its cache can also
395 The template file used for generated C<js/client_js.js> is
396 C<scripts/generate_client_js_actions.tpl>.
404 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>