9 |
9 |
use List::Util qw(first);
|
10 |
10 |
use Time::HiRes ();
|
11 |
11 |
|
|
12 |
use SL::Clipboard;
|
12 |
13 |
use SL::DB::RequirementSpec;
|
13 |
14 |
use SL::DB::RequirementSpecComplexity;
|
14 |
15 |
use SL::DB::RequirementSpecItem;
|
... | ... | |
19 |
20 |
|
20 |
21 |
use Rose::Object::MakeMethods::Generic
|
21 |
22 |
(
|
22 |
|
scalar => [ qw(item visible_item visible_section) ],
|
|
23 |
scalar => [ qw(item visible_item visible_section clicked_item sections) ],
|
23 |
24 |
'scalar --get_set_init' => [ qw(complexities risks) ],
|
24 |
25 |
);
|
25 |
26 |
|
26 |
|
__PACKAGE__->run_before('load_requirement_spec_item', only => [ qw(dragged_and_dropped ajax_update ajax_edit ajax_delete ajax_flag) ]);
|
|
27 |
__PACKAGE__->run_before('load_requirement_spec_item', only => [ qw(dragged_and_dropped ajax_update ajax_edit ajax_delete ajax_flag ajax_copy) ]);
|
27 |
28 |
__PACKAGE__->run_before('init_visible_section');
|
28 |
29 |
|
29 |
30 |
#
|
... | ... | |
52 |
53 |
$self->render($js);
|
53 |
54 |
}
|
54 |
55 |
|
|
56 |
sub insert_new_item_in_section_view {
|
|
57 |
my ($self, $js) = @_;
|
|
58 |
|
|
59 |
$js->hide('#section-list-empty');
|
|
60 |
|
|
61 |
my $new_type = $self->item->item_type;
|
|
62 |
my $id_prefix = $new_type eq 'sub-function-block' ? 'sub-' : '';
|
|
63 |
my $template = 'requirement_spec_item/_' . (apply { s/-/_/g; $_ } $new_type);
|
|
64 |
my $html = "" . $self->render($template, { output => 0 }, requirement_spec_item => $self->item);
|
|
65 |
my $next_item = $self->item->get_next_in_list;
|
|
66 |
|
|
67 |
if ($next_item) {
|
|
68 |
$js->insertBefore($html, '#' . $id_prefix . 'function-block-' . $next_item->id);
|
|
69 |
} else {
|
|
70 |
my $parent_is_section = $self->item->parent->item_type eq 'section';
|
|
71 |
$js->appendTo($html, $parent_is_section ? '#section-list' : '#sub-function-block-container-' . $self->item->parent_id);
|
|
72 |
$js->show('#sub-function-block-container-' . $self->item->parent_id) if !$parent_is_section;
|
|
73 |
}
|
|
74 |
|
|
75 |
$self->replace_bottom($js, $self->item->parent) if $new_type eq 'sub-function-block';
|
|
76 |
}
|
|
77 |
|
55 |
78 |
sub action_dragged_and_dropped {
|
56 |
79 |
my ($self) = @_;
|
57 |
80 |
|
... | ... | |
105 |
128 |
}
|
106 |
129 |
|
107 |
130 |
if ($old_visible_section->id == $new_section->id) {
|
108 |
|
$js->hide('#section-list-empty');
|
109 |
|
|
110 |
|
my $id_prefix = $new_type eq 'sub-function-block' ? 'sub-' : '';
|
111 |
|
my $template = 'requirement_spec_item/_' . (apply { s/-/_/g; $_ } $new_type);
|
112 |
|
my $html = "" . $self->render($template, { output => 0 }, requirement_spec_item => $self->item);
|
113 |
|
my $next_item = $self->item->get_next_in_list;
|
114 |
|
|
115 |
|
if ($next_item) {
|
116 |
|
$js->insertBefore($html, '#' . $id_prefix . 'function-block-' . $next_item->id);
|
117 |
|
} else {
|
118 |
|
my $parent_is_section = $self->item->parent->item_type eq 'section';
|
119 |
|
$js->appendTo($html, $parent_is_section ? '#section-list' : '#sub-function-block-container-' . $self->item->parent_id);
|
120 |
|
$js->show('#sub-function-block-container-' . $self->item->parent_id) if !$parent_is_section;
|
121 |
|
}
|
122 |
|
|
123 |
|
$self->replace_bottom($js, $self->item->parent) if $new_type eq 'sub-function-block';
|
|
131 |
$self->insert_new_item_in_section_view($js);
|
124 |
132 |
}
|
125 |
133 |
|
126 |
134 |
# $::lxdebug->dump(0, "js", $js->to_array);
|
... | ... | |
203 |
211 |
|
204 |
212 |
my $js = SL::ClientJS->new;
|
205 |
213 |
|
206 |
|
if (!$self->visible_section || ($self->visible_section->id != $self->item->section->id)) {
|
|
214 |
if (!$self->is_item_visible) {
|
207 |
215 |
# Show section/item to edit if it is not visible.
|
208 |
216 |
|
209 |
217 |
my $html = $self->render('requirement_spec_item/_section', { output => 0 }, requirement_spec_item => $self->item);
|
... | ... | |
315 |
323 |
->val('#current_content_id', '')
|
316 |
324 |
}
|
317 |
325 |
|
318 |
|
} elsif ($self->visible_section && ($self->visible_section->id == $self->item->section->id)) {
|
|
326 |
} elsif ($self->is_item_visible) {
|
319 |
327 |
# Item in currently visible section is deleted.
|
320 |
328 |
|
321 |
329 |
my $type = $self->item->item_type;
|
... | ... | |
342 |
350 |
|
343 |
351 |
$self->item->update_attributes(is_flagged => !$self->item->is_flagged);
|
344 |
352 |
|
345 |
|
my $is_visible = $self->visible_section && ($self->visible_section->id == $self->item->section->id);
|
346 |
|
|
347 |
353 |
SL::ClientJS->new
|
348 |
|
->action_if($is_visible, 'toggleClass', '#' . $self->item->item_type . '-' . $self->item->id, 'flagged')
|
|
354 |
->action_if($self->is_item_visible, 'toggleClass', '#' . $self->item->item_type . '-' . $self->item->id, 'flagged')
|
349 |
355 |
->toggleClass('#fb-' . $self->item->id, 'flagged')
|
350 |
356 |
->render($self);
|
351 |
357 |
}
|
352 |
358 |
|
|
359 |
sub action_ajax_copy {
|
|
360 |
my ($self, %params) = @_;
|
|
361 |
|
|
362 |
SL::Clipboard->new->copy($self->item);
|
|
363 |
SL::ClientJS->new->render($self);
|
|
364 |
}
|
|
365 |
|
|
366 |
sub determine_paste_position {
|
|
367 |
my ($self) = @_;
|
|
368 |
|
|
369 |
if ($self->item->item_type eq 'section') {
|
|
370 |
# Sections are always pasted either directly after the
|
|
371 |
# clicked-upon section or at the very end.
|
|
372 |
return $self->clicked_item ? (undef, $self->clicked_item->section->id) : ();
|
|
373 |
|
|
374 |
} elsif ($self->item->item_type eq 'function-block') {
|
|
375 |
# A function block:
|
|
376 |
# - paste on section list: insert into last section as last element
|
|
377 |
# - paste on section: insert into that section as last element
|
|
378 |
# - paste on function block: insert after clicked-upon element
|
|
379 |
# - paste on sub function block: insert after parent function block of clicked-upon element
|
|
380 |
return !$self->clicked_item ? ( $self->sections->[-1]->id, undef )
|
|
381 |
: $self->clicked_item->item_type eq 'section' ? ( $self->clicked_item->id, undef )
|
|
382 |
: $self->clicked_item->item_type eq 'function-block' ? ( $self->clicked_item->parent_id, $self->clicked_item->id )
|
|
383 |
: ( $self->clicked_item->parent->parent_id, $self->clicked_item->parent_id );
|
|
384 |
|
|
385 |
} else { # sub-function-block
|
|
386 |
# A sub function block:
|
|
387 |
# - paste on section list: promote to function block and insert into last section as last element
|
|
388 |
# - paste on section: promote to function block and insert into that section as last element
|
|
389 |
# - paste on function block: insert as last element in clicked-upon element
|
|
390 |
# - paste on sub function block: insert after clicked-upon element
|
|
391 |
|
|
392 |
# Promote sub function blocks to function blocks when pasting on a
|
|
393 |
# section or the section list.
|
|
394 |
$self->item->item_type('function-block') if !$self->clicked_item || ($self->clicked_item->item_type eq 'section');
|
|
395 |
|
|
396 |
return !$self->clicked_item ? ( $self->sections->[-1]->id, undef )
|
|
397 |
: $self->clicked_item->item_type eq 'section' ? ( $self->clicked_item->id, undef )
|
|
398 |
: $self->clicked_item->item_type eq 'function-block' ? ( $self->clicked_item->id, undef )
|
|
399 |
: ( $self->clicked_item->parent_id, $self->clicked_item->id );
|
|
400 |
}
|
|
401 |
}
|
|
402 |
|
|
403 |
sub assign_requirement_spec_id_rec {
|
|
404 |
my ($self, $item) = @_;
|
|
405 |
|
|
406 |
$item->requirement_spec_id($::form->{requirement_spec_id});
|
|
407 |
$self->assign_requirement_spec_id_rec($_) for @{ $item->children || [] };
|
|
408 |
|
|
409 |
return $item;
|
|
410 |
}
|
|
411 |
|
|
412 |
sub create_and_insert_node_rec {
|
|
413 |
my ($self, $js, $item, $new_parent_id, $insert_after) = @_;
|
|
414 |
|
|
415 |
my $node = $self->presenter->requirement_spec_item_jstree_data($item);
|
|
416 |
$js->jstree->create_node('#tree', $insert_after ? ('#fb-' . $insert_after, 'after') : $new_parent_id ? ('#fb-' . $new_parent_id, 'last') : ('#sections', 'last'), $node);
|
|
417 |
|
|
418 |
$self->create_and_insert_node_rec($js, $_, $item->id) for @{ $item->children || [] };
|
|
419 |
|
|
420 |
$js->jstree->open_node('#tree', '#fb-' . $item->id);
|
|
421 |
}
|
|
422 |
|
|
423 |
sub action_ajax_paste {
|
|
424 |
my ($self, %params) = @_;
|
|
425 |
|
|
426 |
my $js = SL::ClientJS->new;
|
|
427 |
my $copied = SL::Clipboard->new->get_entry(qr/^RequirementSpecItem$/);
|
|
428 |
|
|
429 |
if (!$copied) {
|
|
430 |
return $js->error(t8("The clipboard does not contain anything that can be pasted here."))
|
|
431 |
->render($self);
|
|
432 |
}
|
|
433 |
|
|
434 |
$self->item($self->assign_requirement_spec_id_rec($copied->to_object));
|
|
435 |
my $req_spec = SL::DB::RequirementSpec->new(id => $::form->{requirement_spec_id})->load;
|
|
436 |
$self->sections($req_spec->sections);
|
|
437 |
|
|
438 |
if (($self->item->item_type ne 'section') && !@{ $self->sections }) {
|
|
439 |
return $js->error(t8("You cannot paste function blocks or sub function blocks if there is no section."))
|
|
440 |
->render($self);
|
|
441 |
}
|
|
442 |
|
|
443 |
$self->clicked_item($::form->{id} ? SL::DB::RequirementSpecItem->new(id => $::form->{id})->load : undef);
|
|
444 |
|
|
445 |
my ($new_parent_id, $insert_after) = $self->determine_paste_position;
|
|
446 |
|
|
447 |
# Store result in database.
|
|
448 |
$self->item->update_attributes(requirement_spec_id => $::form->{requirement_spec_id}, parent_id => $new_parent_id);
|
|
449 |
$self->item->add_to_list(position => 'after', reference => $insert_after) if $insert_after;
|
|
450 |
|
|
451 |
# Update the tree: create the node for all pasted objects.
|
|
452 |
$self->create_and_insert_node_rec($js, $self->item, $new_parent_id, $insert_after);
|
|
453 |
|
|
454 |
# Pasting the very first section?
|
|
455 |
if (!@{ $self->sections }) {
|
|
456 |
my $html = $self->render('requirement_spec_item/_section', { output => 0 }, requirement_spec_item => $self->item);
|
|
457 |
$js->html('#column-content', $html)
|
|
458 |
->jstree->select_node('#tree', '#fb-' . $self->item->id)
|
|
459 |
}
|
|
460 |
|
|
461 |
# Update the current view if required.
|
|
462 |
$self->insert_new_item_in_section_view($js) if $self->is_item_visible;
|
|
463 |
|
|
464 |
$js->render($self);
|
|
465 |
}
|
|
466 |
|
353 |
467 |
#
|
354 |
468 |
# filters
|
355 |
469 |
#
|
... | ... | |
483 |
597 |
my $js = SL::ClientJS->new;
|
484 |
598 |
|
485 |
599 |
my $new_section = $self->item->section;
|
486 |
|
if (!$self->visible_section || ($self->visible_section->id != $new_section->id)) {
|
|
600 |
if (!$self->is_item_visible) {
|
487 |
601 |
# Show section/item to edit if it is not visible.
|
488 |
602 |
|
489 |
603 |
$html = $self->render('requirement_spec_item/_section', { output => 0 }, requirement_spec_item => $new_section);
|
... | ... | |
503 |
617 |
$js->render($self);
|
504 |
618 |
}
|
505 |
619 |
|
|
620 |
sub is_item_visible {
|
|
621 |
my ($self, $item) = @_;
|
|
622 |
|
|
623 |
$item ||= $self->item;
|
|
624 |
return $self->visible_section && ($self->visible_section->id == $item->section->id);
|
|
625 |
}
|
|
626 |
|
506 |
627 |
1;
|
Pflichtenhefte: Copy & Paste für Textblöcke & Items