From: Moritz Bunkus Date: Fri, 8 Mar 2013 14:31:36 +0000 (+0100) Subject: Pflichtenhefte: Erste Version Baumansicht Textblöcke/Abschnitte/Funktionsblöcke X-Git-Tag: release-3.2.0beta~467^2~247 X-Git-Url: http://wagnertech.de/gitweb/gitweb.cgi/kivitendo-erp.git/commitdiff_plain/54daa586799871ce5ede8f628a03ea55a9660cf5 Pflichtenhefte: Erste Version Baumansicht Textblöcke/Abschnitte/Funktionsblöcke --- diff --git a/SL/Controller/RequirementSpec.pm b/SL/Controller/RequirementSpec.pm index b865d2924..d7d897867 100644 --- a/SL/Controller/RequirementSpec.pm +++ b/SL/Controller/RequirementSpec.pm @@ -23,7 +23,7 @@ use Rose::Object::MakeMethods::Generic ); __PACKAGE__->run_before('setup'); -__PACKAGE__->run_before('load_requirement_spec', only => [ qw( edit update destroy) ]); +__PACKAGE__->run_before('load_requirement_spec', only => [ qw( edit update destroy tree) ]); __PACKAGE__->run_before('load_select_options', only => [ qw(new edit create update list) ]); __PACKAGE__->run_before('load_search_select_options', only => [ qw( list) ]); @@ -109,6 +109,11 @@ sub action_reorder { $self->render('1;', { type => 'js', inline => 1 }); } +sub action_tree { + my ($self) = @_; + my $r = $self->render('requirement_spec/tree', now => DateTime->now); +} + # # filters # @@ -117,12 +122,37 @@ sub setup { my ($self) = @_; $::auth->assert('config'); - $::request->{layout}->use_stylesheet("requirement_spec.css"); + $::request->{layout}->use_stylesheet("${_}.css") for qw(requirement_spec yaml/core/base.min); + $::request->{layout}->use_javascript("${_}.js") for qw(jquery.jstree requirement_spec); $self->is_template($::form->{is_template} ? 1 : 0); return 1; } +sub load_requirement_spec { + my ($self) = @_; + $self->{requirement_spec} = SL::DB::RequirementSpec->new(id => $::form->{id})->load || die "No such requirement spec"; +} + +sub load_select_options { + my ($self) = @_; + + my @filter = ('!obsolete' => 1); + if ($self->requirement_spec && $self->requirement_spec->customer_id) { + @filter = ( or => [ @filter, id => $self->requirement_spec->customer_id ] ); + } + + $self->customers(SL::DB::Manager::Customer->get_all_sorted(where => \@filter)); + $self->statuses( SL::DB::Manager::RequirementSpecStatus->get_all_sorted); + $self->types( SL::DB::Manager::RequirementSpecType->get_all_sorted); +} + +sub load_search_select_options { + my ($self) = @_; + + $self->projects(SL::DB::Manager::Project->get_all_sorted); +} + # # helpers # @@ -149,30 +179,6 @@ sub create_or_update { $self->redirect_to(action => 'list'); } -sub load_requirement_spec { - my ($self) = @_; - $self->{requirement_spec} = SL::DB::RequirementSpec->new(id => $::form->{id})->load; -} - -sub load_select_options { - my ($self) = @_; - - my @filter = ('!obsolete' => 1); - if ($self->requirement_spec && $self->requirement_spec->customer_id) { - @filter = ( or => [ @filter, id => $self->requirement_spec->customer_id ] ); - } - - $self->customers(SL::DB::Manager::Customer->get_all_sorted(where => \@filter)); - $self->statuses( SL::DB::Manager::RequirementSpecStatus->get_all_sorted); - $self->types( SL::DB::Manager::RequirementSpecType->get_all_sorted); -} - -sub load_search_select_options { - my ($self) = @_; - - $self->projects(SL::DB::Manager::Project->get_all_sorted); -} - sub setup_db_args_from_filter { my ($self) = @_; diff --git a/SL/Controller/RequirementSpecItem.pm b/SL/Controller/RequirementSpecItem.pm new file mode 100644 index 000000000..6ddf3a63a --- /dev/null +++ b/SL/Controller/RequirementSpecItem.pm @@ -0,0 +1,93 @@ +package SL::Controller::RequirementSpecItem; + +use strict; + +use parent qw(SL::Controller::Base); + +use Time::HiRes (); + +use SL::DB::RequirementSpec; +use SL::DB::RequirementSpecItem; +use SL::Helper::Flash; +use SL::JSON; +use SL::Locale::String; + +use Rose::Object::MakeMethods::Generic +( + scalar => [ qw(requirement_spec item) ], +); + +# __PACKAGE__->run_before('load_requirement_spec'); +__PACKAGE__->run_before('load_requirement_spec_item', only => [qw(dragged_and_dropped)]); + +# +# actions +# + +sub action_new { + my ($self) = @_; + + eval { + my $type = ($::form->{item_type} || '') =~ m/^ (?: section | (?: sub-)? function-block ) $/x ? $::form->{item_type} : die "Invalid item_type"; + $self->{item} = SL::DB::RequirementSpecItem->new(requirement_spec_id => $::form->{requirement_spec_id}); + my $section_form = $self->presenter->render("requirement_spec_item/_${type}_form", id => create_random_id(), title => t8('Create a new section')); + + $self->render(\to_json({ status => 'ok', html => $section_form }), { type => 'json' }); + 1; + } or do { + $self->render(\to_json({ status => 'failed', error => "Exception:\n" . format_exception() }), { type => 'json' }); + } +} + +sub action_create { + my ($self) = @_; + + my $type = ($::form->{item_type} || '') =~ m/^ (?: section | (?: sub-)? function-block ) $/x ? $::form->{item_type} : die "Invalid item_type"; + + $self->render(\to_json({ status => 'failed', error => 'not good, not good' }), { type => 'json' }); +} + +sub action_dragged_and_dropped { + my ($self) = @_; + + $::lxdebug->dump(0, "form", $::form); + + my $dropped_item = SL::DB::RequirementSpecItem->new(id => $::form->{dropped_id})->load || die "No such dropped item"; + my $position = $::form->{position} =~ m/^ (?: before | after | last ) $/x ? $::form->{position} : die "Unknown 'position' parameter"; + + $self->item->db->do_transaction(sub { + $self->item->remove_from_list; + $self->item->parent_id($position =~ m/before|after/ ? $dropped_item->parent_id : $dropped_item->id); + $self->item->add_to_list(position => $position, reference => $dropped_item->id); + }); + + $self->render(\'', { type => 'json' }); +} + +# +# filters +# + +sub load_requirement_spec { + my ($self) = @_; + $self->requirement_spec(SL::DB::RequirementSpec->new(id => $::form->{requirement_spec_id})->load || die "No such requirement spec"); +} + +sub load_requirement_spec_item { + my ($self) = @_; + $self->item(SL::DB::RequirementSpecItem->new(id => $::form->{id})->load || die "No such requirement spec item"); +} + +# +# helpers +# + +sub create_random_id { + return join '-', Time::HiRes::gettimeofday(); +} + +sub format_exception { + return join "\n", (split m/\n/, $@)[0..4]; +} + +1; diff --git a/SL/Controller/RequirementSpecTextBlock.pm b/SL/Controller/RequirementSpecTextBlock.pm new file mode 100644 index 000000000..4f5074098 --- /dev/null +++ b/SL/Controller/RequirementSpecTextBlock.pm @@ -0,0 +1,62 @@ +package SL::Controller::RequirementSpecTextBlock; + +use strict; + +use parent qw(SL::Controller::Base); + +use SL::DB::RequirementSpec; +use SL::DB::RequirementSpecTextBlock; +use SL::Helper::Flash; +use SL::Locale::String; + +use Rose::Object::MakeMethods::Generic +( + scalar => [ qw(requirement_spec text_block) ], +); + +# __PACKAGE__->run_before('load_requirement_spec'); +__PACKAGE__->run_before('load_requirement_spec_text_block', only => [qw(dragged_and_dropped)]); + +# +# actions +# + +sub action_dragged_and_dropped { + my ($self) = @_; + + $::lxdebug->dump(0, "form", $::form); + + my $position = $::form->{position} =~ m/^ (?: before | after | last ) $/x ? $::form->{position} : die "Unknown 'position' parameter"; + my $dropped_text_block = $position =~ m/^ (?: before | after ) $/x ? SL::DB::RequirementSpecTextBlock->new(id => $::form->{dropped_id})->load : undef; + + my $dropped_type = $position ne 'last' ? undef : $::form->{dropped_type} =~ m/^ textblocks- (?:front|back) $/x ? $::form->{dropped_type} : die "Unknown 'dropped_type' parameter"; + + $self->text_block->db->do_transaction(sub { + 1; + $self->text_block->remove_from_list; + $self->text_block->output_position($position =~ m/before|after/ ? $dropped_text_block->output_position : $::form->{dropped_type} eq 'textblocks-front' ? 0 : 1); + $self->text_block->add_to_list(position => $position, reference => $dropped_text_block ? $dropped_text_block->id : undef); + }); + + $self->render(\'', { type => 'json' }); +} + +# +# filters +# + +sub load_requirement_spec { + my ($self) = @_; + $self->requirement_spec(SL::DB::RequirementSpec->new(id => $::form->{requirement_spec_id})->load || die "No such requirement spec"); +} + +sub load_requirement_spec_text_block { + my ($self) = @_; + $self->text_block(SL::DB::RequirementSpecTextBlock->new(id => $::form->{id})->load || die "No such requirement spec text block"); +} + +# +# helpers +# + +1; diff --git a/SL/Presenter.pm b/SL/Presenter.pm index 23b8ad472..f0a0259a2 100644 --- a/SL/Presenter.pm +++ b/SL/Presenter.pm @@ -15,6 +15,7 @@ use SL::Presenter::Order; use SL::Presenter::Part; use SL::Presenter::Project; use SL::Presenter::Record; +use SL::Presenter::RequirementSpec; use SL::Presenter::SepaExport; use SL::Presenter::Text; use SL::Presenter::Tag; diff --git a/SL/Presenter/RequirementSpec.pm b/SL/Presenter/RequirementSpec.pm new file mode 100644 index 000000000..fe9df960c --- /dev/null +++ b/SL/Presenter/RequirementSpec.pm @@ -0,0 +1,39 @@ +package SL::Presenter::RequirementSpec; + +use strict; + +use parent qw(Exporter); + +use Exporter qw(import); +our @EXPORT = qw(requirement_spec_text_block_jstree_data + requirement_spec_item_jstree_data); + +use Carp; + +use SL::JSON; + +sub requirement_spec_text_block_jstree_data { + my ($self, $text_block, %params) = @_; + + return { + data => $text_block->title || '', + metadata => { id => $text_block->id, type => 'textblock' }, + attr => { id => "tb-" . $text_block->id, href => $params{href} || '#' }, + }; +} + +sub requirement_spec_item_jstree_data { + my ($self, $item, %params) = @_; + + my @children = map { $self->requirement_spec_item_jstree_data($_, %params) } @{ $item->sorted_children }; + my $type = !$item->parent_id ? 'section' : 'functionblock'; + + return { + data => join(' ', map { $_ || '' } ($item->fb_number, $item->title)), + metadata => { id => $item->id, type => $type }, + attr => { id => "fb-" . $item->id, href => $params{href} || '#' }, + children => \@children, + }; +} + +1; diff --git a/css/kivitendo/jquery-ui.custom.css b/css/kivitendo/jquery-ui.custom.css index be1686b2b..4f8a19d5c 100644 --- a/css/kivitendo/jquery-ui.custom.css +++ b/css/kivitendo/jquery-ui.custom.css @@ -118,3 +118,8 @@ text-align: left; font-size:125%; } + +/* jstree */ +.jstree a { + border-bottom: none; +} diff --git a/css/requirement_spec.css b/css/requirement_spec.css index 19faf7500..08b6d2be9 100644 --- a/css/requirement_spec.css +++ b/css/requirement_spec.css @@ -2,3 +2,7 @@ input.rs_input_field, select.rs_input_field, table.rs_input_field input, table.rs_input_field select { width: 300px; } + +#content-column { + margin-left: 10px; +} diff --git a/js/requirement_spec.js b/js/requirement_spec.js new file mode 100644 index 000000000..2b0d2007a --- /dev/null +++ b/js/requirement_spec.js @@ -0,0 +1,105 @@ +/* Functions used for the requirement specs tree view */ + +function check_move(data) { + var dragged_type = data.o.data('type'); + var dropped_type = data.r.data('type'); + + // console.debug("dragged " + dragged_type + " dropped " + dropped_type + " dir " + data.p); + + if ((dragged_type == "sections") || (dragged_type == "textblocks-front") || (dragged_type == "textblocks-back")) + return false; + + if (dragged_type == "textblock") { + if ((dropped_type == "textblocks-front") || (dropped_type == "textblocks-back")) + return (data.p == "inside") || (data.p == "last"); + if (dropped_type == "textblock") + return (data.p == "before") || (data.p == "after"); + + return false; + } + + if (dragged_type == "section") { + if (dropped_type == "sections") + return (data.p == "inside") || (data.p == "last"); + if (dropped_type == "section") + return (data.p == "before") || (data.p == "after"); + + return false; + } + + // dragged_type == (sub) function blocks + if ((dropped_type == "textblock") || (dropped_type == "textblocks-front") || (dropped_type == "textblocks-back")) + return false; + + var dropped_depth = dropped_type == "sections" ? 0 : dropped_type == "section" ? 1 : data.r.parent().parent().data('type') != "functionblock" ? 2 : 3; + if ((data.p == "inside") || (data.p == "last")) + dropped_depth++; + + var dragged_depth = 1 + data.o.children('ul').size(); + + // console.debug("dropped_depth " + dropped_depth + " dragged_depth " + dragged_depth); + + return (2 <= dropped_depth) && ((dragged_depth + dropped_depth) <= 4); +} + +function node_moved(event) { + console.debug("node moved"); + var move_obj = $.jstree._reference('#tree')._get_move(); + var dragged = move_obj.o; + var dropped = move_obj.r; + var controller = dragged.data("type") == "textblock" ? "RequirementSpecTextBlock" : "RequirementSpecItem"; + var data = { + action: controller + "/dragged_and_dropped", + requirement_spec_id: $('#requirement_spec_id').val(), + id: dragged.data("id"), + dropped_id: dropped.data("id"), + dropped_type: dropped.data("type"), + position: move_obj.p + }; + // console.debug("controller: " + controller); + // console.debug(data); + + $.ajax({ url: "controller.pl", data: data }); +} + +function section_form_requested(data) { + $('#new-section-button').removeAttr('disabled'); + if (data.status == "ok") + $('#content-column').html(data.html); + else + alert('oh yeah response: ' + data.status + "\n" + data.error); +} + +function section_form_submitted(data) { + alert('oh yeah response: ' + data.status); +} + +function server_side_error(things_to_enable) { + alert('Server-side error.'); + if (things_to_enable) + $(things_to_enable).removeAttr('disabled'); +} + +function new_section_form() { + $('#new-section-button').attr('disabled', 'disabled'); + $.ajax({ + type: 'POST', + url: 'controller.pl', + data: 'action=RequirementSpecItem/new.json&requirement_spec_id=' + $('#requirement_spec_id').val() + '&item_type=section', + success: section_form_requested, + error: function() { server_side_error('#new-section-button'); } + }); +} + +function submit_section_form(id) { + $.ajax({ + type: 'POST', + url: 'controller.pl', + data: 'action=RequirementSpecItem/create.json&' + $('section-form-' + id).serialize(), + success: section_form_submitted + }); +} + +function cancel_section_form(id) { + $('#content-column').html('intentionally empty'); +} diff --git a/js/themes/requirement-spec/d.gif b/js/themes/requirement-spec/d.gif new file mode 100644 index 000000000..0e958d387 Binary files /dev/null and b/js/themes/requirement-spec/d.gif differ diff --git a/js/themes/requirement-spec/d.png b/js/themes/requirement-spec/d.png new file mode 100644 index 000000000..8540175a0 Binary files /dev/null and b/js/themes/requirement-spec/d.png differ diff --git a/js/themes/requirement-spec/style.css b/js/themes/requirement-spec/style.css new file mode 100644 index 000000000..069609d41 --- /dev/null +++ b/js/themes/requirement-spec/style.css @@ -0,0 +1,74 @@ +/* + * jsTree default theme 1.0 + * Supported features: dots/no-dots, icons/no-icons, focused, loading + * Supported plugins: ui (hovered, clicked), checkbox, contextmenu, search + */ + +.jstree-requirement-spec li, +.jstree-requirement-spec ins { background-image:url("d.png"); background-repeat:no-repeat; background-color:transparent; } +.jstree-requirement-spec li { background-position:-90px 0; background-repeat:repeat-y; } +.jstree-requirement-spec li.jstree-last { background:transparent; } +.jstree-requirement-spec .jstree-open > ins { background-position:-72px 0; } +.jstree-requirement-spec .jstree-closed > ins { background-position:-54px 0; } +.jstree-requirement-spec .jstree-leaf > ins { background-position:-36px 0; } + +.jstree-requirement-spec .jstree-hovered { background:#e7f4f9; border:1px solid #d8f0fa; padding:0 2px 0 1px; } +.jstree-requirement-spec .jstree-clicked { background:#beebff; border:1px solid #99defd; padding:0 2px 0 1px; } +.jstree-requirement-spec a .jstree-icon { background-position:-56px -19px; } +.jstree-requirement-spec a.jstree-loading .jstree-icon { background:url("throbber.gif") center center no-repeat !important; } + +.jstree-requirement-spec.jstree-focused { } + +.jstree-requirement-spec .jstree-no-dots li, +.jstree-requirement-spec .jstree-no-dots .jstree-leaf > ins { background:transparent; } +.jstree-requirement-spec .jstree-no-dots .jstree-open > ins { background-position:-18px 0; } +.jstree-requirement-spec .jstree-no-dots .jstree-closed > ins { background-position:0 0; } + +.jstree-requirement-spec .jstree-no-icons a .jstree-icon { display:none; } + +.jstree-requirement-spec .jstree-search { font-style:italic; } + +.jstree-requirement-spec .jstree-no-icons .jstree-checkbox { display:inline-block; } +.jstree-requirement-spec .jstree-no-checkboxes .jstree-checkbox { display:none !important; } +.jstree-requirement-spec .jstree-checked > a > .jstree-checkbox { background-position:-38px -19px; } +.jstree-requirement-spec .jstree-unchecked > a > .jstree-checkbox { background-position:-2px -19px; } +.jstree-requirement-spec .jstree-undetermined > a > .jstree-checkbox { background-position:-20px -19px; } +.jstree-requirement-spec .jstree-checked > a > .jstree-checkbox:hover { background-position:-38px -37px; } +.jstree-requirement-spec .jstree-unchecked > a > .jstree-checkbox:hover { background-position:-2px -37px; } +.jstree-requirement-spec .jstree-undetermined > a > .jstree-checkbox:hover { background-position:-20px -37px; } + +#vakata-dragged.jstree-requirement-spec ins { background:transparent !important; } +#vakata-dragged.jstree-requirement-spec .jstree-ok { background:url("d.png") -2px -53px no-repeat !important; } +#vakata-dragged.jstree-requirement-spec .jstree-invalid { background:url("d.png") -18px -53px no-repeat !important; } +#jstree-marker.jstree-requirement-spec { background:url("d.png") -41px -57px no-repeat !important; text-indent:-100px; } + +.jstree-requirement-spec a.jstree-search { color:aqua; } +.jstree-requirement-spec .jstree-locked a { color:silver; cursor:default; } + +#vakata-contextmenu.jstree-requirement-spec-context, +#vakata-contextmenu.jstree-requirement-spec-context li ul { background:#f0f0f0; border:1px solid #979797; -moz-box-shadow: 1px 1px 2px #999; -webkit-box-shadow: 1px 1px 2px #999; box-shadow: 1px 1px 2px #999; } +#vakata-contextmenu.jstree-requirement-spec-context li { } +#vakata-contextmenu.jstree-requirement-spec-context a { color:black; } +#vakata-contextmenu.jstree-requirement-spec-context a:hover, +#vakata-contextmenu.jstree-requirement-spec-context .vakata-hover > a { padding:0 5px; background:#e8eff7; border:1px solid #aecff7; color:black; -moz-border-radius:2px; -webkit-border-radius:2px; border-radius:2px; } +#vakata-contextmenu.jstree-requirement-spec-context li.jstree-contextmenu-disabled a, +#vakata-contextmenu.jstree-requirement-spec-context li.jstree-contextmenu-disabled a:hover { color:silver; background:transparent; border:0; padding:1px 4px; } +#vakata-contextmenu.jstree-requirement-spec-context li.vakata-separator { background:white; border-top:1px solid #e0e0e0; margin:0; } +#vakata-contextmenu.jstree-requirement-spec-context li ul { margin-left:-4px; } + +/* IE6 BEGIN */ +.jstree-requirement-spec li, +.jstree-requirement-spec ins, +#vakata-dragged.jstree-requirement-spec .jstree-invalid, +#vakata-dragged.jstree-requirement-spec .jstree-ok, +#jstree-marker.jstree-requirement-spec { _background-image:url("d.gif"); } +.jstree-requirement-spec .jstree-open ins { _background-position:-72px 0; } +.jstree-requirement-spec .jstree-closed ins { _background-position:-54px 0; } +.jstree-requirement-spec .jstree-leaf ins { _background-position:-36px 0; } +.jstree-requirement-spec a ins.jstree-icon { _background-position:-56px -19px; } +#vakata-contextmenu.jstree-requirement-spec-context ins { _display:none; } +#vakata-contextmenu.jstree-requirement-spec-context li { _zoom:1; } +.jstree-requirement-spec .jstree-undetermined a .jstree-checkbox { _background-position:-20px -19px; } +.jstree-requirement-spec .jstree-checked a .jstree-checkbox { _background-position:-38px -19px; } +.jstree-requirement-spec .jstree-unchecked a .jstree-checkbox { _background-position:-2px -19px; } +/* IE6 END */ \ No newline at end of file diff --git a/js/themes/requirement-spec/throbber.gif b/js/themes/requirement-spec/throbber.gif new file mode 100644 index 000000000..5b33f7e54 Binary files /dev/null and b/js/themes/requirement-spec/throbber.gif differ diff --git a/locale/de/all b/locale/de/all index 26fdc38f7..5eb066d2c 100755 --- a/locale/de/all +++ b/locale/de/all @@ -1898,6 +1898,7 @@ $self->{texts} = { 'Searchable' => 'Durchsuchbar', 'Secondary sorting' => 'Untersortierung', 'Section "#1"' => 'Abschnitt "#1"', + 'Sections' => 'Abschnitte', 'Select' => 'auswählen', 'Select a Customer' => 'Endkunde auswählen', 'Select a customer' => 'Einen Kunden auswählen', @@ -2119,6 +2120,8 @@ $self->{texts} = { 'Terms missing in row ' => '+Tage fehlen in Zeile ', 'Test and preview' => 'Test und Vorschau', 'Test database connectivity' => 'Datenbankverbindung testen', + 'Text blocks back' => 'Textblöcke hinten', + 'Text blocks front' => 'Textblöcke vorne', 'Text field' => 'Textfeld', 'Text field variables: \'WIDTH=w HEIGHT=h\' sets the width and height of the text field. They default to 30 and 5 respectively.' => 'Textfelder: \'WIDTH=w HEIGHT=h\' setzen die Breite und die Höhe des Textfeldes. Wenn nicht anders angegeben, so werden sie 30 Zeichen breit und fünf Zeichen hoch dargestellt.', 'Text variables: \'MAXLENGTH=n\' sets the maximum entry length to \'n\'.' => 'Textzeilen: \'MAXLENGTH=n\' setzt eine Maximallänge von n Zeichen.', diff --git a/templates/webpages/requirement_spec/tree.html b/templates/webpages/requirement_spec/tree.html new file mode 100644 index 000000000..85ac0e35e --- /dev/null +++ b/templates/webpages/requirement_spec/tree.html @@ -0,0 +1,131 @@ +[%- USE HTML %][%- USE L %][%- USE LxERP %][% USE P %][% USE JSON %] + +[%- L.hidden_tag('requirement_spec_id', SELF.requirement_spec.id) -%] + +
+
+
+
+ [% L.button_tag("new_section_form()", LxERP.t8("New section"), id="new-section-button") %] +
+ +
+
+ +
+
+ +
+
+

There's beauty in the breakdown. 0

+

There's beauty in the breakdown. 1

+

There's beauty in the breakdown. 2

+

There's beauty in the breakdown. 3

+

There's beauty in the breakdown. 4

+

There's beauty in the breakdown. 5

+

There's beauty in the breakdown. 6

+

There's beauty in the breakdown. 7

+

There's beauty in the breakdown. 8

+

There's beauty in the breakdown. 9

+

There's beauty in the breakdown. 10

+

There's beauty in the breakdown. 11

+

There's beauty in the breakdown. 12

+

There's beauty in the breakdown. 13

+

There's beauty in the breakdown. 14

+

There's beauty in the breakdown. 15

+

There's beauty in the breakdown. 16

+

There's beauty in the breakdown. 17

+

There's beauty in the breakdown. 18

+

There's beauty in the breakdown. 19

+

There's beauty in the breakdown. 20

+

There's beauty in the breakdown. 21

+

There's beauty in the breakdown. 22

+ + + + + + + + + + + + + +
+
+
+ + diff --git a/templates/webpages/requirement_spec_item/_section_form.html b/templates/webpages/requirement_spec_item/_section_form.html new file mode 100644 index 000000000..12f8e6533 --- /dev/null +++ b/templates/webpages/requirement_spec_item/_section_form.html @@ -0,0 +1,24 @@ +[%- USE HTML %][%- USE L %][%- USE LxERP %] +[%- SET id_base="section-form-" _ HTML.escape(id) %] +[%- IF title %] +
[%- HTML.escape(title) %]
+[%- END -%] + +
+ [% L.hidden_tag("requirment_spec_id", SELF.item.requirement_spec_id, id=(id_base _ "-requirement-spec-id")) %] + +

+ [%- LxERP.t8("Title") %]:
+ [% L.input_tag("title", SELF.item.title, id=(id_base _ "-title")) %] +

+ +

+ [%- LxERP.t8("Description") %]:
+ [% L.textarea_tag("description", SELF.item.description, id=(id_base _ "-title"), rows=8, cols=80) %] +

+ +

+ [% L.button_tag("submit_section_form('" _ HTML.escape(id) _ "')", LxERP.t8("Save")) %] + [% L.button_tag("cancel_section_form('" _ HTML.escape(id) _ "')", LxERP.t8("Cancel")) %] +

+