Projekt

Allgemein

Profil

« Zurück | Weiter » 

Revision c1569bc1

Von Moritz Bunkus vor fast 11 Jahren hinzugefügt

  • ID c1569bc195865fa5c25c6945bbad78c872fa2046
  • Vorgänger 54daa586
  • Nachfolger ca7c2f91

Pflichtenhefte bearbeiten

Unterschiede anzeigen:

SL/Controller/RequirementSpec.pm
use Rose::Object::MakeMethods::Generic
(
scalar => [ qw(requirement_spec customers projects types statuses db_args flat_filter is_template) ],
scalar => [ qw(requirement_spec requirement_spec_item customers projects types statuses db_args flat_filter is_template) ],
);
__PACKAGE__->run_before('setup');
__PACKAGE__->run_before('load_requirement_spec', only => [ qw( edit update destroy tree) ]);
__PACKAGE__->run_before('load_requirement_spec', only => [ qw( edit update show 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) ]);
......
$self->render('requirement_spec/form', title => t8('Edit requirement spec'));
}
sub action_show {
my ($self) = @_;
my $item = $::form->{requirement_spec_item_id} ? SL::DB::RequirementSpecItem->new(id => $::form->{requirement_spec_item_id})->load : @{ $self->requirement_spec->sections }[0];
$self->requirement_spec_item($item);
$self->render('requirement_spec/show', title => t8('Show requirement spec'));
}
sub action_create {
my ($self) = @_;
......
my ($self) = @_;
$::auth->assert('config');
$::request->{layout}->use_stylesheet("${_}.css") for qw(requirement_spec yaml/core/base.min);
$::request->{layout}->use_javascript("${_}.js") for qw(jquery.jstree requirement_spec);
$::request->{layout}->use_stylesheet("${_}.css") for qw(jquery.contextMenu requirement_spec);
$::request->{layout}->use_javascript("${_}.js") for qw(jquery.jstree jquery/jquery.contextMenu requirement_spec);
$self->is_template($::form->{is_template} ? 1 : 0);
return 1;
SL/Controller/RequirementSpecItem.pm
);
# __PACKAGE__->run_before('load_requirement_spec');
__PACKAGE__->run_before('load_requirement_spec_item', only => [qw(dragged_and_dropped)]);
__PACKAGE__->run_before('load_requirement_spec_item', only => [qw(dragged_and_dropped edit_section update_section)]);
#
# actions
......
$self->render(\'', { type => 'json' });
}
sub action_edit_section {
my ($self, %params) = @_;
$self->render('requirement_spec_item/_section_form', { layout => 0 });
}
sub action_update_section {
my ($self, %params) = @_;
$self->item->update_attributes(title => $::form->{title}, description => $::form->{description});
my $result = {
id => $self->item->id,
header_html => $self->render('requirement_spec_item/_section_header', { layout => 0, output => 0 }, requirement_spec_item => $self->item),
node_name => join(' ', map { $_ || '' } ($self->item->fb_number, $self->item->title)),
};
$self->render(\to_json($result), { type => 'json' });
}
#
# filters
#
SL/DB/RequirementSpec.pm
return [ sort { $a->position <=> $b->position } grep { !$_->parent_id } @{ $self->items } ];
}
sub displayable_name {
my ($self) = @_;
return sprintf('%s: "%s"', $self->type->description, $self->title);
}
1;
SL/Presenter.pm
use SL::Presenter::Project;
use SL::Presenter::Record;
use SL::Presenter::RequirementSpec;
use SL::Presenter::RequirementSpecItem;
use SL::Presenter::SepaExport;
use SL::Presenter::Text;
use SL::Presenter::Tag;
SL/Presenter/RequirementSpec.pm
use parent qw(Exporter);
use Exporter qw(import);
our @EXPORT = qw(requirement_spec_text_block_jstree_data
requirement_spec_item_jstree_data);
our @EXPORT = qw(requirement_spec_text_block_jstree_data);
use Carp;
......
};
}
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;
SL/Presenter/RequirementSpecItem.pm
package SL::Presenter::RequirementSpecItem;
use strict;
use parent qw(Exporter);
use Exporter qw(import);
our @EXPORT = qw(requirement_spec_item_jstree_data requirement_spec_item_dependency_list);
use Carp;
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,
};
}
sub requirement_spec_item_dependency_list {
my ($self, $item) = @_;
$::locale->language_join([ map { $_->fb_number } @{ $item->dependencies } ]);
}
1;
css/requirement_spec.css
width: 300px;
}
#column-container {
width: 100%;
padding-left: 0;
padding-right: 0;
margin-left: 0;
margin-right: 0;
}
#tree-column {
float: left;
width: 25%;
border-right: 1px solid black;
}
#content-column {
margin-left: 10px;
float: left;
padding-left: 10px;
}
.section-empty-description {
color: #bbb;
}
templates/webpages/requirement_spec/_version.html
[%- USE L -%][%- USE LxERP -%][%- USE HTML -%]
[% L.stuff %]
[% LxERP.t8("Current version") %]:
[% IF !requirement_spec.version_id %]
[% LxERP.t8("Working copy without version") %]
[% ELSE %]
[% LxERP.t8("Version") %] [% HTML.escape(requirement_spec.version.version_number) %]
[% LxERP.t8("dated") %] [% HTML.escape(requirement_spec.version.itime.displayable_date) %]
[%- END -%]
templates/webpages/requirement_spec/show.html
[%- USE JSON -%][%- USE HTML %][%- USE L %][%- USE LxERP %][%- USE P -%]
<h1>[%- HTML.escape(SELF.requirement_spec.displayable_name('format', 'with_customer')) %]
[% LxERP.t8("for") %]
[% HTML.escape(SELF.requirement_spec.customer.displayable_name) -%]
</h1>
[%- L.hidden_tag('requirement_spec_id', SELF.requirement_spec.id) -%]
<div id="requirement_spec_version">
[%- INCLUDE 'requirement_spec/_version.html' requirement_spec=SELF.requirement_spec -%]
</div>
<div id="column-container">
<div id="tree-column" style="border-right: 1px solid black">
<div style="min-height: 32px; height: 32px;">
<div style="float: left">
[% L.button_tag("new_section_form()", LxERP.t8("New section"), id="new-section-button") %]
</div>
<div id="spinner" class="clearfix" style="float: right; display: none; background:url('js/themes/requirement-spec/throbber.gif') center center no-repeat !important; min-height: 32px; height: 32px; min-width: 32px; width: 32px;"></div>
</div>
<div id="tree"></div>
</div>
<div id="content-column" class="clearfix">
<div id="section-container" class="section-container">
<div id="section_content" class="section-content">
[%- IF SELF.requirement_spec_item && SELF.requirement_spec_item.id -%]
[%- INCLUDE 'requirement_spec_item/_single_section.html' requirement_spec_item=SELF.requirement_spec_item -%]
[%- ELSE -%]
no section
[%#- render :partial => 'requirement_spec_items/no_section' -%]
[%- END -%]
</div>
</div>
</div>
</div>
<script type="text/javascript">
<!--
var tree_data = [
{ "data": [% JSON.json(LxERP.t8("Text blocks front")) %],
"metadata": { "type": "textblocks-front" },
"attr": { "id": "tb-front" },
"children": [
[% FOREACH tb = SELF.requirement_spec.text_blocks_for_position(0) %]
[% P.requirement_spec_text_block_jstree_data(tb).json %][% IF !loop.last %],[% END %]
[% END %]
]
},
{ "data": [% JSON.json(LxERP.t8("Sections")) %],
"metadata": { "type": "sections" },
"attr": { "id": "sections" },
"children": [
[% FOREACH section = SELF.requirement_spec.sections %]
[% P.requirement_spec_item_jstree_data(section).json %][% IF !loop.last %],[% END %]
[% END %]
]
},
{ "data": [% JSON.json(LxERP.t8("Text blocks back")) %],
"metadata": { "type": "textblocks-back" },
"attr": { "id": "tb-back" },
"children": [
[% FOREACH tb = SELF.requirement_spec.text_blocks_for_position(1) %]
[% P.requirement_spec_text_block_jstree_data(tb).json %][% IF !loop.last %],[% END %]
[% END %]
]
}
];
$(function() {
$('#tree').jstree({
"core": {
"animation": 0,
"initially_open": [ "tb-front", "tb-back", "sections"
[%- FOREACH section = SELF.requirement_spec.sections -%]
, "fb-[% section.id %]"
[%- FOREACH function_block = section.children -%]
, "fb-[% function_block.id -%]"
[%- END -%]
[%- END -%]
]
},
"json_data": {
"data": tree_data
},
"crrm": {
"move": {
"check_move": check_move,
"open_move": true
}
},
"themes": {
"theme": "requirement-spec"
},
"plugins": [ "themes", "json_data", "ui", "crrm", "dnd" ]
})
.bind("move_node.jstree", node_moved);
$(document).ajaxSend(function() {
$('#spinner').show();
}).ajaxStop(function() {
$('#spinner').hide();
});
});
-->
</script>
templates/webpages/requirement_spec/tree.html
[%- L.hidden_tag('requirement_spec_id', SELF.requirement_spec.id) -%]
<div id="page" class="ym-grid ym-equalize">
<div class="ym-g25 ym-gl" style="border-right: 1px solid black">
<div id="column-container">
<div id="tree-column">
<div style="min-height: 32px; height: 32px;">
<div style="float: left">
[% L.button_tag("new_section_form()", LxERP.t8("New section"), id="new-section-button") %]
</div>
<div id="spinner" style="float: right; display: none; background:url('js/themes/requirement-spec/throbber.gif') center center no-repeat !important; min-height: 32px; height: 32px; min-width: 32px; width: 32px;"></div>
<div style="clear: both"></div>
</div>
<div id="tree"></div>
</div>
<div class="ym-gl">
<div id="content-column">
<p>There's beauty in the breakdown. 0</p>
<p>There's beauty in the breakdown. 1</p>
<p>There's beauty in the breakdown. 2</p>
<p>There's beauty in the breakdown. 3</p>
<p>There's beauty in the breakdown. 4</p>
<p>There's beauty in the breakdown. 5</p>
<p>There's beauty in the breakdown. 6</p>
<p>There's beauty in the breakdown. 7</p>
<p>There's beauty in the breakdown. 8</p>
<p>There's beauty in the breakdown. 9</p>
<p>There's beauty in the breakdown. 10</p>
<p>There's beauty in the breakdown. 11</p>
<p>There's beauty in the breakdown. 12</p>
<p>There's beauty in the breakdown. 13</p>
<p>There's beauty in the breakdown. 14</p>
<p>There's beauty in the breakdown. 15</p>
<p>There's beauty in the breakdown. 16</p>
<p>There's beauty in the breakdown. 17</p>
<p>There's beauty in the breakdown. 18</p>
<p>There's beauty in the breakdown. 19</p>
<p>There's beauty in the breakdown. 20</p>
<p>There's beauty in the breakdown. 21</p>
<p>There's beauty in the breakdown. 22</p>
<!-- <p>There's beauty in the breakdown. 23</p> -->
<!-- <p>There's beauty in the breakdown. 24</p> -->
<!-- <p>There's beauty in the breakdown. 25</p> -->
<!-- <p>There's beauty in the breakdown. 26</p> -->
<!-- <p>There's beauty in the breakdown. 27</p> -->
<!-- <p>There's beauty in the breakdown. 28</p> -->
<!-- <p>There's beauty in the breakdown. 29</p> -->
<!-- <p>There's beauty in the breakdown. 30</p> -->
<!-- <p>There's beauty in the breakdown. 31</p> -->
<!-- <p>There's beauty in the breakdown. 32</p> -->
<!-- <p>There's beauty in the breakdown. 33</p> -->
<!-- <p>There's beauty in the breakdown. 34</p> -->
<!-- <p>There's beauty in the breakdown. 35</p> -->
</div>
<div id="content-column" class="clearfix">
<p>There's beauty in the breakdown. 0</p>
<p>There's beauty in the breakdown. 1</p>
<p>There's beauty in the breakdown. 2</p>
<p>There's beauty in the breakdown. 3</p>
<p>There's beauty in the breakdown. 4</p>
<p>There's beauty in the breakdown. 5</p>
<p>There's beauty in the breakdown. 6</p>
<p>There's beauty in the breakdown. 7</p>
<p>There's beauty in the breakdown. 8</p>
<p>There's beauty in the breakdown. 9</p>
<p>There's beauty in the breakdown. 10</p>
<p>There's beauty in the breakdown. 11</p>
<p>There's beauty in the breakdown. 12</p>
<p>There's beauty in the breakdown. 13</p>
<p>There's beauty in the breakdown. 14</p>
<p>There's beauty in the breakdown. 15</p>
<p>There's beauty in the breakdown. 16</p>
<p>There's beauty in the breakdown. 17</p>
<p>There's beauty in the breakdown. 18</p>
<p>There's beauty in the breakdown. 19</p>
<p>There's beauty in the breakdown. 20</p>
<p>There's beauty in the breakdown. 21</p>
<p>There's beauty in the breakdown. 22</p>
<!-- <p>There's beauty in the breakdown. 23</p> -->
<!-- <p>There's beauty in the breakdown. 24</p> -->
<!-- <p>There's beauty in the breakdown. 25</p> -->
<!-- <p>There's beauty in the breakdown. 26</p> -->
<!-- <p>There's beauty in the breakdown. 27</p> -->
<!-- <p>There's beauty in the breakdown. 28</p> -->
<!-- <p>There's beauty in the breakdown. 29</p> -->
<!-- <p>There's beauty in the breakdown. 30</p> -->
<!-- <p>There's beauty in the breakdown. 31</p> -->
<!-- <p>There's beauty in the breakdown. 32</p> -->
<!-- <p>There's beauty in the breakdown. 33</p> -->
<!-- <p>There's beauty in the breakdown. 34</p> -->
<!-- <p>There's beauty in the breakdown. 35</p> -->
</div>
</div>
......
})
.bind("move_node.jstree", node_moved);
});
$(document).ajaxSend(function() {
$('#spinner').show();
}).ajaxStop(function() {
$('#spinner').hide();
});
-->
</script>
templates/webpages/requirement_spec_item/_section_form.html
[%- USE HTML %][%- USE L %][%- USE LxERP %]
[%- SET id_base="section-form-" _ HTML.escape(id) %]
[%- IF title %]
<div class="listtop">[%- HTML.escape(title) %]</div>
[%- END -%]
[%- SET id_base="section-form" %]
<div id="[% id_base %]">
<h1>[%- LxERP.t8("Edit section #1", SELF.item.fb_number) %]</h1>
<form id="[% id_base %]">
[% L.hidden_tag("requirment_spec_id", SELF.item.requirement_spec_id, id=(id_base _ "-requirement-spec-id")) %]
<form>
[% L.hidden_tag("requirement_spec_id", SELF.item.requirement_spec_id, id=(id_base _ "-requirement-spec-id")) %]
[% L.hidden_tag("id", SELF.item.id, id=(id_base _ "-requirement-spec-item-id")) %]
<p>
[%- LxERP.t8("Title") %]:<br>
[% L.input_tag("title", SELF.item.title, id=(id_base _ "-title")) %]
</p>
<p>
[%- LxERP.t8("Title") %]:<br>
[% L.input_tag("title", SELF.item.title, id=(id_base _ "-title")) %]
</p>
<p>
[%- LxERP.t8("Description") %]:<br>
[% L.textarea_tag("description", SELF.item.description, id=(id_base _ "-title"), rows=8, cols=80) %]
</p>
<p>
[%- LxERP.t8("Description") %]:<br>
[% L.textarea_tag("description", SELF.item.description, id=(id_base _ "-description"), rows=8, cols=80) %]
</p>
<p>
[% L.button_tag("submit_section_form('" _ HTML.escape(id) _ "')", LxERP.t8("Save")) %]
[% L.button_tag("cancel_section_form('" _ HTML.escape(id) _ "')", LxERP.t8("Cancel")) %]
</p>
</form>
<p>
[% L.button_tag("submit_section_form()", LxERP.t8("Save")) %]
<a href="#" onclick="cancel_section_form()">[% LxERP.t8("Cancel") %]</a>
</p>
</form>
</div>
templates/webpages/requirement_spec_item/_section_header.html
[%- USE HTML -%][%- USE L -%][%- USE LxERP -%]
<h1>
[%- HTML.escape(requirement_spec_item.fb_number) %]: [% HTML.escape(requirement_spec_item.title) -%]
</h1>
[% IF requirement_spec_item.description %]
<div class="section-description">
[%- L.simple_format(requirement_spec_item.description) -%]
</div>
[%- ELSE %]
<div class="section-empty-description">[%- LxERP.t8("No description has been entered yet.") %]</div>
[%- END %]
templates/webpages/requirement_spec_item/_single_section.html
[%- USE HTML -%][%- USE LxERP -%][%- USE L -%]
<div class="section-context-menu" id="section-header">
[%- INCLUDE 'requirement_spec_item/_section_header.html' %]
</div>
[%- L.hidden_tag('active_section_id', requirement_spec_item.id) -%]
<ul id="section" class="section function-block-context-menu">
[%- FOREACH subitem = requirement_spec_item.children -%]
[%- INCLUDE 'requirement_spec_item/_single_subitem.html' requirement_spec_item=subitem -%]
[%- END -%]
</ul>
<div id="new_subitem_form" class="subitem-form clearfix">
</div>
<div id="new_subitem_link" class="highlight-box" style="[%- 'display:none' IF requirement_spec_item.children.size -%]">
[%#- link_to_remote LxERP.t8("FIXME no_function_blocks_have_been_created_for_this_section_yet) + " " + t(:create_a_function_block_now"),
:url => new_requirement_spec_requirement_spec_item_url(requirement_spec_item.requirement_spec,
:requirement_spec_item_id => requirement_spec_item.id),
:method => :get,
:update => "new_subitem_form",
:loading => "$('new_subitem_link').hide();" -%]
</div>
<script type="text/javascript">
<!--
$(function(){
$.contextMenu({
selector: '.section-context-menu',
callback: function(key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
edit: { name: "[% LxERP.t8('Edit section') %]", icon: "edit", callback: edit_section_header },
delete: { name: "[% LxERP.t8('Delete section') %]", icon: "delete" },
sep1: "---------",
copy: { name: "[% LxERP.t8('Copy') %]", icon: "copy" },
paste: { name: "[% LxERP.t8('Paste') %]", icon: "paste" }
}
});
$.contextMenu({
selector: '.function-block-context-menu',
callback: function(key, options) {
var m = "clicked: " + key;
window.console && console.log(m) || alert(m);
},
items: {
new_item: { name: "[% LxERP.t8('New function block') %]", icon: "add"},
new_sub_item: { name: "[% LxERP.t8('New sub function block') %]", icon: "add"},
sep1: "---------",
edit: { name: "[% LxERP.t8('Edit') %]", icon: "edit"},
delete: { name: "[% LxERP.t8('Delete') %]", icon: "delete"},
sep2: "---------",
copy: { name: "[% LxERP.t8('Copy') %]", icon: "copy" },
paste: { name: "[% LxERP.t8('Paste') %]", icon: "paste" }
}
});
$('.section-context-menu').on('click', function(e){
console.log('clicked', this);
});
});
function edit_section_header() {
$.post("controller.pl?action=RequirementSpecItem/edit_section&id=" + encodeURIComponent($('#active_section_id').val()), function(data) {
var header = $('#section-header');
header.data('old-elements', header.children().detach());
header.html(data);
});
}
function submit_section_form() {
$.post("controller.pl?action=RequirementSpecItem/update_section&id=" + $('#section-form form').serialize(), function(data) {
var header = $('#section-header');
header.removeData('old-elements');
header.html(data['header_html']);
$('#tree').jstree('rename_node', '#fb-' + data['id'], data['node_name']);
});
}
function cancel_section_form() {
var header = $('#section-header');
header.empty();
header.append(header.data('old-elements'));
header.removeData('old-elements');
}
-->
</script>
templates/webpages/requirement_spec_item/_single_subitem.html
[%- USE HTML -%][%- USE LxERP -%][%- USE P -%]
<li id="subitem_[%- requirement_spec_item.id -%]" class="subitem">
<div id="subitem_content_[%- requirement_spec_item.id -%]" class="subitem-content[%- IF requirement_spec_item.flagged -%] flagged[%- END -%]">
[%#- IF !@requirement_spec.project.nil? -%]
[%#- link_to image_tag("chronometer.png"), account_time_requirement_spec_requirement_spec_item_path(requirement_spec_item.requirement_spec, requirement_spec_item) -%]
[%#- END -%]
[%#- link_to_new_sub(requirement_spec_item.parent) -%]
[%#- link_to_remote image_tag("new_subsub.png"),
:url => new_requirement_spec_requirement_spec_item_url(requirement_spec_item.requirement_spec, :requirement_spec_item_id => requirement_spec_item.id),
:method => :get,
:condition => "check_for_editbox()",
:update => "new_subsubitem_form_#{requirement_spec_item.id}" -%]
<b>[%- HTML.escape(requirement_spec_item.fb_number) -%]</b>
[%- HTML.escape(requirement_spec_item.description) -%]
<div class="subsubitem-container" id="subsubitem_container_[%- requirement_spec_item.id -%]">
<div class="subsubitem-header" id="subsubitem_header_[%- requirement_spec_item.id -%]"[%- IF !requirement_spec_item.children.size -%] style="display: none"[%- END -%]>
[%- LxERP.t8("Sub function blocks") -%]
</div>
[%- IF requirement_spec_item.children.size -%]
[%- FOREACH subsubitem = requirement_spec_item.children -%]
[%#- render :partial => "requirement_spec_items/single_subsubitem", :locals => {:requirement_spec_item => subsubitem} -%]
[%- END -%]
[%- END -%]
</div>
<div id="new_subsubitem_form_[%- requirement_spec_item.id -%]" class="subsubitem_form"></div>
<div class="smaller" style="text-align:right">
[%- IF requirement_spec_item.dependencies.size -%]
<span class="gray">
[%- LxERP.t8("Dependencies") -%]: [%- P.requirement_spec_item_dependency_list(requirement_spec_item.dependencies) -%]
</span><br>
[%- END -%]
<span class="gray">
[%- LxERP.t8("Complexity") -%]: [%- requirement_spec_item.requirement_spec_complexity.description IF requirement_spec_item.requirement_spec_complexity -%]
&nbsp; | &nbsp;
[%- LxERP.t8("Risk") -%]: [%- requirement_spec_item.requirement_spec_risk.description IF requirement_spec_item.requirement_spec_risk -%]
&nbsp; | &nbsp;
[%- LxERP.t8("Effort") -%]: [%#- render :partial => 'requirement_spec_items/time_estimation_item', :locals => { :item => requirement_spec_item } -%]
</span>
</div>
</div>
</li>

Auch abrufbar als: Unified diff