Revision 602e604a
Von Moritz Bunkus vor mehr als 10 Jahren hinzugefügt
SL/Controller/RequirementSpecTextBlock.pm | ||
---|---|---|
6 | 6 |
|
7 | 7 |
use SL::ClientJS; |
8 | 8 |
use SL::DB::RequirementSpec; |
9 |
use SL::DB::RequirementSpecPredefinedText; |
|
9 | 10 |
use SL::DB::RequirementSpecTextBlock; |
10 | 11 |
use SL::Helper::Flash; |
11 | 12 |
use SL::JSON; |
... | ... | |
16 | 17 |
scalar => [ qw(requirement_spec text_block) ], |
17 | 18 |
); |
18 | 19 |
|
19 |
__PACKAGE__->run_before('load_requirement_spec_text_block', only => [qw(dragged_and_dropped)]); |
|
20 |
__PACKAGE__->run_before('load_requirement_spec_text_block', only => [qw(ajax_edit update dragged_and_dropped)]);
|
|
20 | 21 |
|
21 | 22 |
# |
22 | 23 |
# actions |
... | ... | |
41 | 42 |
my $js = SL::ClientJS->new; |
42 | 43 |
|
43 | 44 |
if (!defined($current_where) || ($new_where != $current_where)) { |
44 |
my $text_blocks = SL::DB::Manager::RequirementSpecTextBlock->get_all_sorted(where => [ output_position => $new_where ]); |
|
45 |
my $html = $self->render('requirement_spec_text_block/ajax_list', { output => 0 }, TEXT_BLOCKS => $text_blocks, output_position => $new_where, nownow => DateTime->now_local);
|
|
45 |
my $text_blocks = SL::DB::Manager::RequirementSpecTextBlock->get_all_sorted(where => [ output_position => $new_where, requirement_spec_id => $::form->{requirement_spec_id} ]);
|
|
46 |
my $html = $self->render('requirement_spec_text_block/ajax_list', { output => 0 }, TEXT_BLOCKS => $text_blocks, output_position => $new_where); |
|
46 | 47 |
|
47 | 48 |
$js->html('#column-content', $html) |
48 | 49 |
} |
... | ... | |
50 | 51 |
$self->render($js); |
51 | 52 |
} |
52 | 53 |
|
54 |
sub action_ajax_edit { |
|
55 |
my ($self) = @_; |
|
56 |
|
|
57 |
my $js = SL::ClientJS->new; |
|
58 |
|
|
59 |
my $current_where = $self->output_position_from_id($::form->{current_content_id}, $::form->{current_content_type}) // -1; |
|
60 |
if ($self->text_block->output_position != $current_where) { |
|
61 |
my $text_blocks = $self->text_block->get_full_list; |
|
62 |
my $html = $self->render('requirement_spec_text_block/ajax_list', { output => 0 }, TEXT_BLOCKS => $text_blocks, output_position => $self->text_block->output_position); |
|
63 |
|
|
64 |
$js->html('#column-content', $html) |
|
65 |
->val('#current_content_type', 'text-block') |
|
66 |
->val('#current_content_id', $self->text_block->id); |
|
67 |
} |
|
68 |
|
|
69 |
my $predefined_texts = SL::DB::Manager::RequirementSpecPredefinedText->get_all_sorted; |
|
70 |
my $html = $self->render('requirement_spec_text_block/_form', { output => 0 }, PREDEFINED_TEXTS => $predefined_texts); |
|
71 |
|
|
72 |
$js->hide('#text-block-' . $self->text_block->id) |
|
73 |
->insertAfter($html, '#text-block-' . $self->text_block->id) |
|
74 |
->jstree->select_node('#tree', '#tb-' . $self->text_block->id) |
|
75 |
->render($self); |
|
76 |
} |
|
77 |
|
|
78 |
sub action_update { |
|
79 |
my ($self, %params) = @_; |
|
80 |
|
|
81 |
my $prefix = $::form->{form_prefix} || 'text_block'; |
|
82 |
my $attributes = $::form->{$prefix} || {}; |
|
83 |
|
|
84 |
foreach (qw(requirement_spec_id output_position)) { |
|
85 |
delete $attributes->{$_} if !defined $attributes->{$_}; |
|
86 |
} |
|
87 |
|
|
88 |
$self->text_block->update_attributes(%{ $attributes }); |
|
89 |
|
|
90 |
my $html = $self->render('requirement_spec_text_block/_text_block', { output => 0 }, text_block => $self->text_block); |
|
91 |
|
|
92 |
SL::ClientJS->new |
|
93 |
->remove('#' . $prefix . '_form') |
|
94 |
->replaceWith('#text-block-' . $self->text_block->id, $html) |
|
95 |
->jstree->rename_node('#tree', '#tb-' . $self->text_block->id, $self->text_block->title) |
|
96 |
->render($self); |
|
97 |
} |
|
98 |
|
|
99 |
|
|
53 | 100 |
sub action_dragged_and_dropped { |
54 | 101 |
my ($self) = @_; |
55 | 102 |
|
SL/Presenter.pm | ||
---|---|---|
15 | 15 |
use SL::Presenter::Part; |
16 | 16 |
use SL::Presenter::Project; |
17 | 17 |
use SL::Presenter::Record; |
18 |
use SL::Presenter::RequirementSpec; |
|
19 | 18 |
use SL::Presenter::RequirementSpecItem; |
19 |
use SL::Presenter::RequirementSpecTextBlock; |
|
20 | 20 |
use SL::Presenter::SepaExport; |
21 | 21 |
use SL::Presenter::Text; |
22 | 22 |
use SL::Presenter::Tag; |
js/requirement_spec.js | ||
---|---|---|
1 | 1 |
/* Functions used for the requirement specs tree view */ |
2 | 2 |
|
3 |
function check_move(data) { |
|
3 |
function requirement_spec_tree_check_move(data) {
|
|
4 | 4 |
var dragged_type = data.o.data('type'); |
5 | 5 |
var dropped_type = data.r.data('type'); |
6 | 6 |
|
... | ... | |
42 | 42 |
return (2 <= dropped_depth) && ((dragged_depth + dropped_depth) <= 4); |
43 | 43 |
} |
44 | 44 |
|
45 |
function node_moved(event) { |
|
45 |
function requirement_spec_tree_node_moved(event) {
|
|
46 | 46 |
console.debug("node moved"); |
47 | 47 |
var move_obj = $.jstree._reference('#tree')._get_move(); |
48 | 48 |
var dragged = move_obj.o; |
... | ... | |
66 | 66 |
return true; |
67 | 67 |
} |
68 | 68 |
|
69 |
function node_clicked(event) { |
|
69 |
function requirement_spec_tree_node_clicked(event) {
|
|
70 | 70 |
var node = $.jstree._reference('#tree')._get_node(event.target); |
71 | 71 |
var type = node ? node.data('type') : undefined; |
72 | 72 |
|
... | ... | |
82 | 82 |
var url = 'controller.pl?action=' |
83 | 83 |
$.get('controller.pl', { |
84 | 84 |
action: (/^textblock/ ? 'RequirementSpecTextBlock' : 'RequirementSpecItem') + '/ajax_list.js', |
85 |
requirement_spec_id: $('#requirement_spec_id').val(), |
|
85 | 86 |
current_content_type: $('#current_content_type').val(), |
86 | 87 |
current_content_id: $('#current_content_id').val(), |
87 | 88 |
clicked_type: type, |
... | ... | |
134 | 135 |
function cancel_section_form(id) { |
135 | 136 |
$('#content-column').html('intentionally empty'); |
136 | 137 |
} |
138 |
|
|
139 |
function find_text_block_id(clicked_elt) { |
|
140 |
console.log("id: " + $(clicked_elt).attr('id')); |
|
141 |
var id = $(clicked_elt).attr('id'); |
|
142 |
if (/^text-block-\d+$/.test(id)) { |
|
143 |
console.log("find_text_block_id: case 1: " + id.substr(11)); |
|
144 |
return id.substr(11) * 1; |
|
145 |
} |
|
146 |
|
|
147 |
id = $(clicked_elt).closest("[id*=text-block-]").attr('id') |
|
148 |
if (/^text-block-\d+$/.test(id)) { |
|
149 |
console.log("find_text_block_id: case 2: " + id.substr(11)); |
|
150 |
return id.substr(11) * 1; |
|
151 |
} |
|
152 |
|
|
153 |
id = $(clicked_elt).closest("[id*=tb-]").attr('id') |
|
154 |
if (/^tb-\d+$/.test(id)) { |
|
155 |
console.log("find_text_block_id: case 3: " + id.substr(3)); |
|
156 |
return id.substr(3) * 1; |
|
157 |
} |
|
158 |
|
|
159 |
console.log("find_text_block_id: case undef"); |
|
160 |
return undefined; |
|
161 |
} |
|
162 |
|
|
163 |
function find_text_block_output_position(clicked_elt) { |
|
164 |
var output_position = $(clicked_elt).closest('#text-block-list-container').find('#text_block_output_position').val(); |
|
165 |
if (output_position) |
|
166 |
return output_position; |
|
167 |
|
|
168 |
var type = $(clicked_elt).closest('#tb-back,#tb-front').data('type'); |
|
169 |
if (/^textblocks-(front|back)/.test(type)) |
|
170 |
return type == "textblocks-front" ? 0 : 1; |
|
171 |
|
|
172 |
return undefined; |
|
173 |
} |
|
174 |
|
|
175 |
function disable_edit_text_block_commands(key, opt) { |
|
176 |
return find_text_block_id(opt.$trigger) == undefined; |
|
177 |
} |
|
178 |
|
|
179 |
function edit_text_block(key, opt) { |
|
180 |
var data = { |
|
181 |
action: "RequirementSpecTextBlock/ajax_edit", |
|
182 |
id: find_text_block_id(opt.$trigger), |
|
183 |
current_content_type: $('#current_content_type').val(), |
|
184 |
current_content_id: $('#current_content_id').val() |
|
185 |
}; |
|
186 |
$.post("controller.pl", data, eval_json_result); |
|
187 |
return true; |
|
188 |
} |
|
189 |
|
|
190 |
function add_text_block(key, opt) { |
|
191 |
return true; |
|
192 |
} |
|
193 |
|
|
194 |
function delete_text_block(key, opt) { |
|
195 |
var data = { |
|
196 |
action: "RequirementSpecTextBlock/ajax_delete", |
|
197 |
id: find_text_block_id(opt.$trigger), |
|
198 |
current_content_type: $('#current_content_type').val(), |
|
199 |
current_content_id: $('#current_content_id').val() |
|
200 |
}; |
|
201 |
$.post("controller.pl", data, eval_json_result); |
|
202 |
return true; |
|
203 |
} |
|
204 |
|
|
205 |
function submit_edit_text_block_form(id_base) { |
|
206 |
var url = "controller.pl?" + $('#' + id_base + '_form').serialize(); |
|
207 |
var data = { |
|
208 |
action: 'RequirementSpecTextBlock/update.js', |
|
209 |
id: $('#' + id_base + '_id').val(), |
|
210 |
form_prefix: id_base |
|
211 |
}; |
|
212 |
console.log("posting edit text block: " + url); |
|
213 |
console.log(data); |
|
214 |
$.post(url, data, eval_json_result); |
|
215 |
} |
|
216 |
|
|
217 |
function cancel_edit_text_block_form(id_base) { |
|
218 |
var id = $('#' + id_base + '_id').val(); |
|
219 |
$('#' + id_base + '_form').remove(); |
|
220 |
if (id) |
|
221 |
$('#text-block-' + id).show(); |
|
222 |
} |
templates/webpages/requirement_spec/show.html | ||
---|---|---|
41 | 41 |
<script type="text/javascript"> |
42 | 42 |
<!-- |
43 | 43 |
var tree_data = [ |
44 |
{ "data": [% JSON.json(LxERP.t8("Text blocks front")) %],
|
|
45 |
"metadata": { "type": "textblocks-front" },
|
|
46 |
"attr": { "id": "tb-front" },
|
|
47 |
"children": [
|
|
44 |
{ data: [% JSON.json(LxERP.t8("Text blocks front")) %],
|
|
45 |
metadata: { type: "textblocks-front" },
|
|
46 |
attr: { id: "tb-front", class: "text-block-context-menu" },
|
|
47 |
children: [
|
|
48 | 48 |
[% FOREACH tb = SELF.requirement_spec.text_blocks_for_position(0) %] |
49 | 49 |
[% P.requirement_spec_text_block_jstree_data(tb).json %][% IF !loop.last %],[% END %] |
50 | 50 |
[% END %] |
51 | 51 |
] |
52 | 52 |
}, |
53 | 53 |
|
54 |
{ "data": [% JSON.json(LxERP.t8("Sections")) %],
|
|
55 |
"metadata": { "type": "sections" },
|
|
56 |
"attr": { "id": "sections" },
|
|
57 |
"children": [
|
|
54 |
{ data: [% JSON.json(LxERP.t8("Sections")) %],
|
|
55 |
metadata: { type: "sections" },
|
|
56 |
attr: { id: "sections" },
|
|
57 |
children: [
|
|
58 | 58 |
|
59 | 59 |
[% FOREACH section = SELF.requirement_spec.sections %] |
60 | 60 |
[% P.requirement_spec_item_jstree_data(section).json %][% IF !loop.last %],[% END %] |
... | ... | |
62 | 62 |
] |
63 | 63 |
}, |
64 | 64 |
|
65 |
{ "data": [% JSON.json(LxERP.t8("Text blocks back")) %],
|
|
66 |
"metadata": { "type": "textblocks-back" },
|
|
67 |
"attr": { "id": "tb-back" },
|
|
68 |
"children": [
|
|
65 |
{ data: [% JSON.json(LxERP.t8("Text blocks back")) %],
|
|
66 |
metadata: { type: "textblocks-back" },
|
|
67 |
attr: { id: "tb-back", class: "text-block-context-menu" },
|
|
68 |
children: [
|
|
69 | 69 |
[% FOREACH tb = SELF.requirement_spec.text_blocks_for_position(1) %] |
70 | 70 |
[% P.requirement_spec_text_block_jstree_data(tb).json %][% IF !loop.last %],[% END %] |
71 | 71 |
[% END %] |
... | ... | |
75 | 75 |
|
76 | 76 |
$(function() { |
77 | 77 |
$('#tree').jstree({ |
78 |
"core": {
|
|
79 |
"animation": 0,
|
|
80 |
"initially_open": [ "tb-front", "tb-back", "sections"
|
|
78 |
core: {
|
|
79 |
animation: 0,
|
|
80 |
initially_open: [ "tb-front", "tb-back", "sections"
|
|
81 | 81 |
[%- FOREACH section = SELF.requirement_spec.sections -%] |
82 | 82 |
, "fb-[% section.id %]" |
83 | 83 |
[%- FOREACH function_block = section.children -%] |
... | ... | |
86 | 86 |
[%- END -%] |
87 | 87 |
] |
88 | 88 |
}, |
89 |
"json_data": {
|
|
90 |
"data": tree_data
|
|
89 |
json_data: {
|
|
90 |
data: tree_data
|
|
91 | 91 |
}, |
92 |
"crrm": {
|
|
93 |
"move": {
|
|
94 |
"check_move": check_move,
|
|
95 |
"open_move": true
|
|
92 |
crrm: {
|
|
93 |
move: {
|
|
94 |
check_move: requirement_spec_tree_check_move,
|
|
95 |
open_move: true
|
|
96 | 96 |
} |
97 | 97 |
}, |
98 |
"themes": {
|
|
99 |
"theme": "requirement-spec"
|
|
98 |
themes: {
|
|
99 |
theme: "requirement-spec"
|
|
100 | 100 |
}, |
101 |
"plugins": [ "themes", "json_data", "ui", "crrm", "dnd" ]
|
|
101 |
plugins: [ "themes", "json_data", "ui", "crrm", "dnd" ]
|
|
102 | 102 |
}) |
103 |
.bind("move_node.jstree", node_moved) |
|
104 |
.bind("click.jstree", node_clicked) |
|
103 |
.bind("move_node.jstree", requirement_spec_tree_node_moved)
|
|
104 |
.bind("click.jstree", requirement_spec_tree_node_clicked)
|
|
105 | 105 |
}); |
106 |
|
|
107 |
function ask_delete_text_block(key, opt) { |
|
108 |
if (confirm("[% LxERP.t8("Are you sure?") %]")) |
|
109 |
delete_text_block(key, opt); |
|
110 |
return true; |
|
111 |
} |
|
112 |
|
|
113 |
$(function(){ |
|
114 |
$.contextMenu({ |
|
115 |
selector: '.text-block-context-menu', |
|
116 |
items: { |
|
117 |
add: { name: "[% LxERP.t8('Add text block') %]", icon: "add", callback: add_text_block }, |
|
118 |
edit: { name: "[% LxERP.t8('Edit text block') %]", icon: "edit", callback: edit_text_block, disabled: disable_edit_text_block_commands }, |
|
119 |
delete: { name: "[% LxERP.t8('Delete text block') %]", icon: "delete", callback: ask_delete_text_block, disabled: disable_edit_text_block_commands }, |
|
120 |
sep1: "---------", |
|
121 |
copy: { name: "[% LxERP.t8('Copy') %]", icon: "copy", disabled: disable_edit_text_block_commands }, |
|
122 |
paste: { name: "[% LxERP.t8('Paste') %]", icon: "paste", disabled: disable_edit_text_block_commands } |
|
123 |
} |
|
124 |
}); |
|
125 |
}); |
|
126 |
|
|
106 | 127 |
--> |
107 | 128 |
</script> |
templates/webpages/requirement_spec_text_block/_form.html | ||
---|---|---|
1 |
[%- USE LxERP -%][%- USE L -%][%- USE HTML -%][%- USE JavaScript -%][% SET style="width: 500px" %] |
|
2 |
[% DEFAULT id_base = 'edit_text_block_' _ SELF.text_block.id %] |
|
3 |
<form method="post" id="[% id_base %]_form"> |
|
4 |
[% L.hidden_tag(id_base _ '_id', SELF.text_block.id) %] |
|
5 |
[% L.hidden_tag(id_base _ '.requirement_spec_id', SELF.text_block.requirement_spec_id) %] |
|
6 |
[% L.hidden_tag(id_base _ '.output_position', SELF.text_block.output_position) %] |
|
7 |
|
|
8 |
<table> |
|
9 |
<tr> |
|
10 |
<th align="right">[%- LxERP.t8("Title") %]:</th> |
|
11 |
<td>[% L.input_tag(id_base _ '.title', SELF.text_block.title, style = style) %]</td> |
|
12 |
</tr> |
|
13 |
|
|
14 |
[%- IF PREDEFINED_TEXTS.size %] |
|
15 |
<tr> |
|
16 |
<th align="right">[%- LxERP.t8("Pre-defined text block") %]:</th> |
|
17 |
<td> |
|
18 |
[%- L.select_tag(id_base _ '_predefined_text_block', PREDEFINED_TEXTS, title_key='description', style=style) %] |
|
19 |
<a href="#" onclick="insert_selected_predefined_text()">[%- LxERP.t8("Insert") %]</a> |
|
20 |
</td> |
|
21 |
</tr> |
|
22 |
[%- END %] |
|
23 |
|
|
24 |
<tr> |
|
25 |
<th align="right" valign="top">[%- LxERP.t8("Description") %]:</th> |
|
26 |
<td valign="top">[% L.textarea_tag(id_base _ '.text', SELF.text_block.text, style = style, rows = 10) %]</td> |
|
27 |
</tr> |
|
28 |
</table> |
|
29 |
|
|
30 |
<p> |
|
31 |
[% L.button_tag('submit_edit_text_block_form("' _ id_base _ '")', LxERP.t8('Save')) %] |
|
32 |
<a href="#" onclick="cancel_edit_text_block_form('[% id_base %]')">[%- LxERP.t8("Cancel") %]</a> |
|
33 |
</p> |
|
34 |
|
|
35 |
[%- IF PREDEFINED_TEXTS.size %] |
|
36 |
<script type="text/javascript"> |
|
37 |
<!-- |
|
38 |
function insert_selected_predefined_text() { |
|
39 |
var data = { |
|
40 |
[%- FOREACH pt = PREDEFINED_TEXTS %] |
|
41 |
[% HTML.escape(pt.id) %]: { |
|
42 |
title: "[% JavaScript.escape(pt.title) %]", |
|
43 |
text: "[% JavaScript.escape(pt.text) %]" |
|
44 |
}[% UNLESS loop.last %],[% END %] |
|
45 |
[% END %] |
|
46 |
} |
|
47 |
|
|
48 |
var id = $('#[% id_base %]_predefined_text_block').val(); |
|
49 |
var pt = data[id]; |
|
50 |
if (!pt) { |
|
51 |
console.log("insert: case 1; id: " + id); |
|
52 |
return false; |
|
53 |
} |
|
54 |
|
|
55 |
var title_ctrl = $('#[% id_base %]_title'); |
|
56 |
|
|
57 |
if ( ((title_ctrl.val() || '') != '') |
|
58 |
&& ((pt.title || '') != '') |
|
59 |
&& confirm('[%- LxERP.t8("Do you want to overwrite your current title?") %]')) |
|
60 |
title_ctrl.val(pt.title); |
|
61 |
|
|
62 |
if ((pt.text || '') != '') { |
|
63 |
var text_ctrl = $('#[% id_base %]_text'); |
|
64 |
var text = text_ctrl.val() || ''; |
|
65 |
if (text != '') |
|
66 |
text += "\n\n"; |
|
67 |
|
|
68 |
text_ctrl.val(text + pt.text); |
|
69 |
} |
|
70 |
|
|
71 |
return false; |
|
72 |
} |
|
73 |
--> |
|
74 |
</script> |
|
75 |
[%- END %] |
|
76 |
</form> |
templates/webpages/requirement_spec_text_block/_text_block.html | ||
---|---|---|
1 | 1 |
[%- USE HTML -%][%- USE L -%][%- USE LxERP -%] |
2 |
<div id="text-block-[% HTML.escape(text_block.id) %]" class="requirement-spec-text-block"> |
|
3 |
<h2 class="requirement-spec-text-block-title">[%- HTML.escape(text_block.title) %]</h2> |
|
2 |
<div id="text-block-[% HTML.escape(text_block.id) %]" class="requirement-spec-text-block text-block-context-menu">
|
|
3 |
<h2 class="requirement-spec-text-block-title">[% IF !text_block.title %]<span class="dimmed-text">[%- LxERP.t8("No title yet") %]</span>[% END %][%- HTML.escape(text_block.title) %]</h2>
|
|
4 | 4 |
|
5 |
[% IF !text_block.description %]
|
|
6 |
<div class="requirement-spec-text-block-empty-description">
|
|
7 |
[%- LxERP.t8("No description has been entered yet.") %]
|
|
5 |
[% IF !text_block.text %]
|
|
6 |
<div class="dimmed-text">
|
|
7 |
[%- LxERP.t8("No text has been entered yet.") %]
|
|
8 | 8 |
</div> |
9 | 9 |
[% ELSE %] |
10 |
<div class="requirement-spec-text-block-description">
|
|
11 |
[% L.simple_format(text_block.description) %]
|
|
10 |
<div class="requirement-spec-text-block-text">
|
|
11 |
[% L.simple_format(text_block.text) %]
|
|
12 | 12 |
</div> |
13 | 13 |
[% END %] |
14 | 14 |
</div> |
templates/webpages/requirement_spec_text_block/ajax_list.html | ||
---|---|---|
1 |
[%- USE LxERP -%][% nownow %] |
|
2 |
<h1> |
|
3 |
[%- IF output_position == 0 %][%- LxERP.t8("Text blocks front") %][%- ELSE %][%- LxERP.t8("Text blocks back") %][%- END %] |
|
4 |
</h1> |
|
1 |
[%- USE LxERP -%][%- USE L -%] |
|
5 | 2 |
|
6 |
<div id="text-block-list-empty"[% IF TEXT_BLOCKS.size %] style="display: none"[% END %]> |
|
7 |
[%- LxERP.t8("No text blocks have been created for this position.") %] |
|
8 |
</div> |
|
3 |
<div id="text-block-list-container"> |
|
4 |
[% L.hidden_tag('text_block_output_position', output_position) %] |
|
5 |
<h1 class="text-block-context-menu"> |
|
6 |
[%- IF output_position == 0 %][%- LxERP.t8("Text blocks front") %][%- ELSE %][%- LxERP.t8("Text blocks back") %][%- END %] |
|
7 |
</h1> |
|
8 |
|
|
9 |
<div id="text-block-list-empty" class="text-block-context-menu"[% IF TEXT_BLOCKS.size %] style="display: none"[% END %]> |
|
10 |
[%- LxERP.t8("No text blocks have been created for this position.") %] |
|
11 |
</div> |
|
9 | 12 |
|
10 |
<div id="text-block-list"> |
|
11 |
[%- FOREACH text_block = TEXT_BLOCKS %] |
|
12 |
[%- INCLUDE 'requirement_spec_text_block/_text_block.html' %] |
|
13 |
[%- END %] |
|
13 |
<div id="text-block-list"> |
|
14 |
[%- FOREACH text_block = TEXT_BLOCKS %] |
|
15 |
[%- INCLUDE 'requirement_spec_text_block/_text_block.html' %] |
|
16 |
[%- END %] |
|
17 |
</div> |
|
14 | 18 |
</div> |
19 |
|
|
20 |
<script type="text/javascript"> |
|
21 |
<!-- |
|
22 |
--> |
|
23 |
</script> |
Auch abrufbar als: Unified diff
Textblöcke bearbeiten