Textblöcke bearbeiten
authorMoritz Bunkus <m.bunkus@linet-services.de>
Fri, 8 Mar 2013 14:33:19 +0000 (15:33 +0100)
committerMoritz Bunkus <m.bunkus@linet-services.de>
Tue, 1 Apr 2014 11:02:25 +0000 (13:02 +0200)
SL/Controller/RequirementSpecTextBlock.pm
SL/Presenter.pm
js/requirement_spec.js
templates/webpages/requirement_spec/show.html
templates/webpages/requirement_spec_text_block/_form.html [new file with mode: 0644]
templates/webpages/requirement_spec_text_block/_text_block.html
templates/webpages/requirement_spec_text_block/ajax_list.html

index 6027cc5..21cf29e 100644 (file)
@@ -6,6 +6,7 @@ use parent qw(SL::Controller::Base);
 
 use SL::ClientJS;
 use SL::DB::RequirementSpec;
+use SL::DB::RequirementSpecPredefinedText;
 use SL::DB::RequirementSpecTextBlock;
 use SL::Helper::Flash;
 use SL::JSON;
@@ -16,7 +17,7 @@ use Rose::Object::MakeMethods::Generic
  scalar => [ qw(requirement_spec text_block) ],
 );
 
-__PACKAGE__->run_before('load_requirement_spec_text_block', only => [qw(dragged_and_dropped)]);
+__PACKAGE__->run_before('load_requirement_spec_text_block', only => [qw(ajax_edit update dragged_and_dropped)]);
 
 #
 # actions
@@ -41,8 +42,8 @@ sub action_ajax_list {
   my $js = SL::ClientJS->new;
 
   if (!defined($current_where) || ($new_where != $current_where)) {
-    my $text_blocks = SL::DB::Manager::RequirementSpecTextBlock->get_all_sorted(where => [ output_position => $new_where ]);
-    my $html        = $self->render('requirement_spec_text_block/ajax_list', { output => 0 }, TEXT_BLOCKS => $text_blocks, output_position => $new_where, nownow => DateTime->now_local);
+    my $text_blocks = SL::DB::Manager::RequirementSpecTextBlock->get_all_sorted(where => [ output_position => $new_where, requirement_spec_id => $::form->{requirement_spec_id} ]);
+    my $html        = $self->render('requirement_spec_text_block/ajax_list', { output => 0 }, TEXT_BLOCKS => $text_blocks, output_position => $new_where);
 
     $js->html('#column-content', $html)
   }
@@ -50,6 +51,52 @@ sub action_ajax_list {
   $self->render($js);
 }
 
+sub action_ajax_edit {
+  my ($self) = @_;
+
+  my $js = SL::ClientJS->new;
+
+  my $current_where = $self->output_position_from_id($::form->{current_content_id}, $::form->{current_content_type}) // -1;
+  if ($self->text_block->output_position != $current_where) {
+    my $text_blocks = $self->text_block->get_full_list;
+    my $html        = $self->render('requirement_spec_text_block/ajax_list', { output => 0 }, TEXT_BLOCKS => $text_blocks, output_position => $self->text_block->output_position);
+
+    $js->html('#column-content', $html)
+       ->val('#current_content_type', 'text-block')
+       ->val('#current_content_id',   $self->text_block->id);
+  }
+
+  my $predefined_texts = SL::DB::Manager::RequirementSpecPredefinedText->get_all_sorted;
+  my $html             = $self->render('requirement_spec_text_block/_form', { output => 0 }, PREDEFINED_TEXTS => $predefined_texts);
+
+  $js->hide('#text-block-' . $self->text_block->id)
+     ->insertAfter($html, '#text-block-' . $self->text_block->id)
+     ->jstree->select_node('#tree', '#tb-' . $self->text_block->id)
+     ->render($self);
+}
+
+sub action_update {
+  my ($self, %params) = @_;
+
+  my $prefix     = $::form->{form_prefix} || 'text_block';
+  my $attributes = $::form->{$prefix}     || {};
+
+  foreach (qw(requirement_spec_id output_position)) {
+    delete $attributes->{$_} if !defined $attributes->{$_};
+  }
+
+  $self->text_block->update_attributes(%{ $attributes });
+
+  my $html = $self->render('requirement_spec_text_block/_text_block', { output => 0 }, text_block => $self->text_block);
+
+  SL::ClientJS->new
+    ->remove('#' . $prefix . '_form')
+    ->replaceWith('#text-block-' . $self->text_block->id, $html)
+    ->jstree->rename_node('#tree', '#tb-' . $self->text_block->id, $self->text_block->title)
+    ->render($self);
+}
+
+
 sub action_dragged_and_dropped {
   my ($self)       = @_;
 
index 67b37df..ba24b82 100644 (file)
@@ -15,8 +15,8 @@ use SL::Presenter::Order;
 use SL::Presenter::Part;
 use SL::Presenter::Project;
 use SL::Presenter::Record;
-use SL::Presenter::RequirementSpec;
 use SL::Presenter::RequirementSpecItem;
+use SL::Presenter::RequirementSpecTextBlock;
 use SL::Presenter::SepaExport;
 use SL::Presenter::Text;
 use SL::Presenter::Tag;
index fdd949e..a9e19e2 100644 (file)
@@ -1,6 +1,6 @@
 /* Functions used for the requirement specs tree view */
 
-function check_move(data) {
+function requirement_spec_tree_check_move(data) {
   var dragged_type = data.o.data('type');
   var dropped_type = data.r.data('type');
 
@@ -42,7 +42,7 @@ function check_move(data) {
   return (2 <= dropped_depth) && ((dragged_depth + dropped_depth) <= 4);
 }
 
-function node_moved(event) {
+function requirement_spec_tree_node_moved(event) {
   console.debug("node moved");
   var move_obj   = $.jstree._reference('#tree')._get_move();
   var dragged    = move_obj.o;
@@ -66,7 +66,7 @@ function node_moved(event) {
   return true;
 }
 
-function node_clicked(event) {
+function requirement_spec_tree_node_clicked(event) {
   var node = $.jstree._reference('#tree')._get_node(event.target);
   var type = node ? node.data('type') : undefined;
 
@@ -82,6 +82,7 @@ function node_clicked(event) {
   var url = 'controller.pl?action='
   $.get('controller.pl', {
     action:               (/^textblock/ ? 'RequirementSpecTextBlock' : 'RequirementSpecItem') + '/ajax_list.js',
+    requirement_spec_id:  $('#requirement_spec_id').val(),
     current_content_type: $('#current_content_type').val(),
     current_content_id:   $('#current_content_id').val(),
     clicked_type:         type,
@@ -134,3 +135,88 @@ function submit_section_form(id) {
 function cancel_section_form(id) {
   $('#content-column').html('intentionally empty');
 }
+
+function find_text_block_id(clicked_elt) {
+  console.log("id: " + $(clicked_elt).attr('id'));
+  var id = $(clicked_elt).attr('id');
+  if (/^text-block-\d+$/.test(id)) {
+    console.log("find_text_block_id: case 1: " + id.substr(11));
+    return id.substr(11) * 1;
+  }
+
+  id = $(clicked_elt).closest("[id*=text-block-]").attr('id')
+  if (/^text-block-\d+$/.test(id)) {
+    console.log("find_text_block_id: case 2: " + id.substr(11));
+    return id.substr(11) * 1;
+  }
+
+  id = $(clicked_elt).closest("[id*=tb-]").attr('id')
+  if (/^tb-\d+$/.test(id)) {
+    console.log("find_text_block_id: case 3: " + id.substr(3));
+    return id.substr(3) * 1;
+  }
+
+  console.log("find_text_block_id: case undef");
+  return undefined;
+}
+
+function find_text_block_output_position(clicked_elt) {
+  var output_position = $(clicked_elt).closest('#text-block-list-container').find('#text_block_output_position').val();
+  if (output_position)
+    return output_position;
+
+  var type = $(clicked_elt).closest('#tb-back,#tb-front').data('type');
+  if (/^textblocks-(front|back)/.test(type))
+    return type == "textblocks-front" ? 0 : 1;
+
+  return undefined;
+}
+
+function disable_edit_text_block_commands(key, opt) {
+  return find_text_block_id(opt.$trigger) == undefined;
+}
+
+function edit_text_block(key, opt) {
+  var data = {
+    action:               "RequirementSpecTextBlock/ajax_edit",
+    id:                   find_text_block_id(opt.$trigger),
+    current_content_type: $('#current_content_type').val(),
+    current_content_id:   $('#current_content_id').val()
+  };
+  $.post("controller.pl", data, eval_json_result);
+  return true;
+}
+
+function add_text_block(key, opt) {
+  return true;
+}
+
+function delete_text_block(key, opt) {
+  var data = {
+    action:               "RequirementSpecTextBlock/ajax_delete",
+    id:                   find_text_block_id(opt.$trigger),
+    current_content_type: $('#current_content_type').val(),
+    current_content_id:   $('#current_content_id').val()
+  };
+  $.post("controller.pl", data, eval_json_result);
+  return true;
+}
+
+function submit_edit_text_block_form(id_base) {
+  var url  = "controller.pl?" + $('#' + id_base + '_form').serialize();
+  var data = {
+    action:      'RequirementSpecTextBlock/update.js',
+    id:          $('#' + id_base + '_id').val(),
+    form_prefix: id_base
+  };
+  console.log("posting edit text block: " + url);
+  console.log(data);
+  $.post(url, data, eval_json_result);
+}
+
+function cancel_edit_text_block_form(id_base) {
+  var id = $('#' + id_base + '_id').val();
+  $('#' + id_base + '_form').remove();
+  if (id)
+    $('#text-block-' + id).show();
+}
index 5c73a51..8df8c00 100644 (file)
 <script type="text/javascript">
  <!--
      var tree_data = [
-       { "data": [% JSON.json(LxERP.t8("Text blocks front")) %],
-         "metadata": { "type": "textblocks-front" },
-         "attr": { "id": "tb-front" },
-         "children": [
+       { data:     [% JSON.json(LxERP.t8("Text blocks front")) %],
+         metadata: { type: "textblocks-front" },
+         attr:     { id: "tb-front", class: "text-block-context-menu" },
+         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": [
+       { 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 %]
          ]
        },
 
-       { "data": [% JSON.json(LxERP.t8("Text blocks back")) %],
-         "metadata": { "type": "textblocks-back" },
-         "attr": { "id": "tb-back" },
-         "children": [
+       { data:     [% JSON.json(LxERP.t8("Text blocks back")) %],
+         metadata: { type: "textblocks-back" },
+         attr:     { id: "tb-back", class: "text-block-context-menu" },
+         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 %]
@@ -75,9 +75,9 @@
 
      $(function() {
        $('#tree').jstree({
-         "core": {
-           "animation": 0,
-           "initially_open": [ "tb-front", "tb-back", "sections"
+         core: {
+           animation: 0,
+           initially_open: [ "tb-front", "tb-back", "sections"
 [%- FOREACH section = SELF.requirement_spec.sections -%]
  , "fb-[% section.id %]"
  [%- FOREACH function_block = section.children -%]
 [%- END -%]
  ]
          },
-         "json_data": {
-           "data": tree_data
+         json_data: {
+           data: tree_data
          },
-         "crrm": {
-           "move": {
-             "check_move": check_move,
-             "open_move": true
+         crrm: {
+           move: {
+             check_move: requirement_spec_tree_check_move,
+             open_move:  true
            }
          },
-         "themes": {
-           "theme": "requirement-spec"
+         themes: {
+           theme: "requirement-spec"
          },
-         "plugins": [ "themes", "json_data", "ui", "crrm", "dnd" ]
+         plugins: [ "themes", "json_data", "ui", "crrm", "dnd" ]
        })
-       .bind("move_node.jstree", node_moved)
-       .bind("click.jstree",     node_clicked)
+       .bind("move_node.jstree", requirement_spec_tree_node_moved)
+       .bind("click.jstree",     requirement_spec_tree_node_clicked)
      });
+
+function ask_delete_text_block(key, opt) {
+  if (confirm("[% LxERP.t8("Are you sure?") %]"))
+    delete_text_block(key, opt);
+  return true;
+}
+
+$(function(){
+    $.contextMenu({
+        selector: '.text-block-context-menu',
+        items: {
+          add:    { name: "[% LxERP.t8('Add text block') %]", icon: "add", callback: add_text_block },
+          edit:   { name: "[% LxERP.t8('Edit text block') %]", icon: "edit", callback: edit_text_block, disabled: disable_edit_text_block_commands },
+          delete: { name: "[% LxERP.t8('Delete text block') %]", icon: "delete", callback: ask_delete_text_block, disabled: disable_edit_text_block_commands },
+          sep1:   "---------",
+          copy:   { name: "[% LxERP.t8('Copy') %]", icon: "copy", disabled: disable_edit_text_block_commands },
+          paste:  { name: "[% LxERP.t8('Paste') %]", icon: "paste", disabled: disable_edit_text_block_commands }
+        }
+      });
+});
+
   -->
 </script>
diff --git a/templates/webpages/requirement_spec_text_block/_form.html b/templates/webpages/requirement_spec_text_block/_form.html
new file mode 100644 (file)
index 0000000..1fde6e5
--- /dev/null
@@ -0,0 +1,76 @@
+[%- USE LxERP -%][%- USE L -%][%- USE HTML -%][%- USE JavaScript -%][% SET style="width: 500px" %]
+[% DEFAULT id_base = 'edit_text_block_' _ SELF.text_block.id %]
+<form method="post" id="[% id_base %]_form">
+ [% L.hidden_tag(id_base _ '_id',                  SELF.text_block.id) %]
+ [% L.hidden_tag(id_base _ '.requirement_spec_id', SELF.text_block.requirement_spec_id) %]
+ [% L.hidden_tag(id_base _ '.output_position',     SELF.text_block.output_position) %]
+
+ <table>
+  <tr>
+   <th align="right">[%- LxERP.t8("Title") %]:</th>
+   <td>[% L.input_tag(id_base _ '.title', SELF.text_block.title, style = style) %]</td>
+  </tr>
+
+ [%- IF PREDEFINED_TEXTS.size %]
+  <tr>
+   <th align="right">[%- LxERP.t8("Pre-defined text block") %]:</th>
+   <td>
+    [%- L.select_tag(id_base _ '_predefined_text_block', PREDEFINED_TEXTS, title_key='description', style=style) %]
+    <a href="#" onclick="insert_selected_predefined_text()">[%- LxERP.t8("Insert") %]</a>
+   </td>
+  </tr>
+ [%- END %]
+
+  <tr>
+   <th align="right" valign="top">[%- LxERP.t8("Description") %]:</th>
+   <td valign="top">[% L.textarea_tag(id_base _ '.text', SELF.text_block.text, style = style, rows = 10) %]</td>
+  </tr>
+ </table>
+
+ <p>
+  [% L.button_tag('submit_edit_text_block_form("' _ id_base _ '")', LxERP.t8('Save')) %]
+  <a href="#" onclick="cancel_edit_text_block_form('[% id_base %]')">[%- LxERP.t8("Cancel") %]</a>
+ </p>
+
+[%- IF PREDEFINED_TEXTS.size %]
+ <script type="text/javascript">
+<!--
+function insert_selected_predefined_text() {
+  var data = {
+[%- FOREACH pt = PREDEFINED_TEXTS %]
+    [% HTML.escape(pt.id) %]: {
+      title: "[% JavaScript.escape(pt.title) %]",
+      text: "[% JavaScript.escape(pt.text) %]"
+    }[% UNLESS loop.last %],[% END %]
+[% END %]
+  }
+
+  var id = $('#[% id_base %]_predefined_text_block').val();
+  var pt = data[id];
+  if (!pt) {
+    console.log("insert: case 1; id: " + id);
+    return false;
+  }
+
+  var title_ctrl = $('#[% id_base %]_title');
+
+  if (   ((title_ctrl.val() || '') != '')
+      && ((pt.title         || '') != '')
+      && confirm('[%- LxERP.t8("Do you want to overwrite your current title?") %]'))
+    title_ctrl.val(pt.title);
+
+  if ((pt.text || '') != '') {
+    var text_ctrl = $('#[% id_base %]_text');
+    var text      = text_ctrl.val() || '';
+    if (text != '')
+      text += "\n\n";
+
+    text_ctrl.val(text + pt.text);
+  }
+
+  return false;
+}
+-->
+ </script>
+[%- END %]
+</form>
index ae371f3..6028289 100644 (file)
@@ -1,14 +1,14 @@
 [%- USE HTML -%][%- USE L -%][%- USE LxERP -%]
-<div id="text-block-[% HTML.escape(text_block.id) %]" class="requirement-spec-text-block">
- <h2 class="requirement-spec-text-block-title">[%- HTML.escape(text_block.title) %]</h2>
+<div id="text-block-[% HTML.escape(text_block.id) %]" class="requirement-spec-text-block text-block-context-menu">
+ <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>
 
- [% IF !text_block.description %]
-  <div class="requirement-spec-text-block-empty-description">
-   [%- LxERP.t8("No description has been entered yet.") %]
+ [% IF !text_block.text %]
+  <div class="dimmed-text">
+   [%- LxERP.t8("No text has been entered yet.") %]
   </div>
  [% ELSE %]
-  <div class="requirement-spec-text-block-description">
-   [% L.simple_format(text_block.description) %]
+  <div class="requirement-spec-text-block-text">
+   [% L.simple_format(text_block.text) %]
   </div>
  [% END %]
 </div>
index dacf154..73e1ab3 100644 (file)
@@ -1,14 +1,23 @@
-[%- USE LxERP -%][% nownow %]
-<h1>
- [%- IF output_position == 0 %][%- LxERP.t8("Text blocks front") %][%- ELSE %][%- LxERP.t8("Text blocks back") %][%- END %]
-</h1>
+[%- USE LxERP -%][%- USE L -%]
 
-<div id="text-block-list-empty"[% IF TEXT_BLOCKS.size %] style="display: none"[% END %]>
- [%- LxERP.t8("No text blocks have been created for this position.") %]
-</div>
+<div id="text-block-list-container">
+ [% L.hidden_tag('text_block_output_position', output_position) %]
+ <h1 class="text-block-context-menu">
+  [%- IF output_position == 0 %][%- LxERP.t8("Text blocks front") %][%- ELSE %][%- LxERP.t8("Text blocks back") %][%- END %]
+ </h1>
+
+ <div id="text-block-list-empty" class="text-block-context-menu"[% IF TEXT_BLOCKS.size %] style="display: none"[% END %]>
+  [%- LxERP.t8("No text blocks have been created for this position.") %]
+ </div>
 
-<div id="text-block-list">
- [%- FOREACH text_block = TEXT_BLOCKS %]
-  [%- INCLUDE 'requirement_spec_text_block/_text_block.html' %]
- [%- END %]
+ <div id="text-block-list">
+  [%- FOREACH text_block = TEXT_BLOCKS %]
+   [%- INCLUDE 'requirement_spec_text_block/_text_block.html' %]
+  [%- END %]
+ </div>
 </div>
+
+<script type="text/javascript">
+ <!--
+-->
+</script>