diff --git a/app/views/todos/_toggle_successors.html.erb b/app/views/todos/_toggle_successors.html.erb
new file mode 100644
index 00000000..47efc3df
--- /dev/null
+++ b/app/views/todos/_toggle_successors.html.erb
@@ -0,0 +1,15 @@
+<%
+suppress_button ||= false
+%>
+<%= link_to(image_tag( 'blank.png', :width=>'16', :height=>'16', :border=>'0' ), "#", {:class => 'show_successors', :title => 'Show successors'}) unless suppress_button %>
+
+
>
+ <%= render :partial => "todos/successor",
+ :collection => item.pending_successors,
+ :locals => { :todo => item,
+ :parent_container_type => parent_container_type,
+ :suppress_dependencies => true,
+ :predecessor => item }
+ %>
+
+
diff --git a/app/views/todos/add_predecessor.js.rjs b/app/views/todos/add_predecessor.js.rjs
new file mode 100644
index 00000000..dfe73a7d
--- /dev/null
+++ b/app/views/todos/add_predecessor.js.rjs
@@ -0,0 +1,27 @@
+if @saved
+ # show update message
+ status_message = "Added #{@predecessor.description} as dependency."
+ unless @original_state == 'pending'
+ status_message += " #{@todo.description} set to pending"
+ end
+ # remove successor from page
+ page[@todo].remove
+ # regenerate predecessor to add arrow
+ page[@predecessor].replace_html :partial => 'todos/todo', :locals => { :todo => @predecessor, :parent_container_type => parent_container_type }
+
+ # show in tickler box in project view
+ if source_view_is_one_of :project, :tag
+ page['tickler-empty-nd'].hide
+ page.replace "tickler", :partial => 'todos/deferred', :locals => { :deferred => @todo.project.deferred_todos,
+ :collapsible => false,
+ :append_descriptor => "in this project",
+ :parent_container_type => 'project',
+ :pending => @todo.project.pending_todos }
+ end
+
+ page << 'enable_rich_interaction();'
+ page.notify :notice, status_message, 5.0
+else
+ page.replace_html "status", content_tag("div", content_tag("h2", "Unable to add dependency"), "id" => "errorExplanation", "class" => "errorExplanation")
+end
+
diff --git a/app/views/todos/create.js.rjs b/app/views/todos/create.js.rjs
index b82a7b24..77a3be5a 100644
--- a/app/views/todos/create.js.rjs
+++ b/app/views/todos/create.js.rjs
@@ -2,6 +2,7 @@ if @saved
page.hide 'status'
status_message = 'Added new next action'
status_message += ' to tickler' if @todo.deferred?
+ status_message += ' in pending state' if @todo.pending?
status_message = 'Added new project / ' + status_message if @new_project_created
status_message = 'Added new context / ' + status_message if @new_context_created
page.notify :notice, status_message, 5.0
@@ -21,9 +22,19 @@ if @saved
page.visual_effect :highlight, dom_id(@todo), :duration => 3
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil?
end
- # make sure the behavior of the new/updated todo is enabled
- page['tickler-empty-nd'].hide if source_view_is :deferred
+ if (source_view_is :project and @todo.pending?) or (source_view_is :deferred)
+ page['tickler-empty-nd'].hide # For some reason this does not work: page['tickler-empty-nd'].hide if (@todo.pending? or (source_view_is :deferred))
+ end
end
+ # Update predecessors (if they exist and are visible)
+ @todo.uncompleted_predecessors.each do |p|
+ page << "if ($(\'#{item_container_id(p)}\')) {"
+ page[p].replace_html :partial => 'todos/todo',
+ :locals => { :todo => p, :parent_container_type => parent_container_type }
+ page << "}"
+ end
+ # make sure the behavior of the new/updated todo is enabled
+ page << "enable_rich_interaction();"
else
page.show 'status'
page.replace_html 'status', "#{error_messages_for('todo', :object_name => 'action')}"
diff --git a/app/views/todos/destroy.js.rjs b/app/views/todos/destroy.js.rjs
index 8d7d5666..f4e19622 100644
--- a/app/views/todos/destroy.js.rjs
+++ b/app/views/todos/destroy.js.rjs
@@ -23,6 +23,14 @@ if @saved
end
end
end
+
+ # Activate pending todos that are successors of the deleted
+ @pending_to_activate.each do |t|
+ logger.debug "#300: Removing #{t.description} from pending block and adding it to active"
+ page[t].remove if source_view_is(:project) or source_view_is(:tag)
+ page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
+ page.visual_effect :highlight, dom_id(t, 'line'), {'startcolor' => "'#99ff99'", :duration => 2}
+ end
else
page.notify :error, "There was an error deleting the item #{@todo.description}", 8.0
end
diff --git a/app/views/todos/remove_predecessor.js.rjs b/app/views/todos/remove_predecessor.js.rjs
new file mode 100644
index 00000000..74f639c7
--- /dev/null
+++ b/app/views/todos/remove_predecessor.js.rjs
@@ -0,0 +1,25 @@
+if @removed
+ status_message = "Removed #{@successor.description} as dependency from #{@predecessor.description}."
+ page.notify :notice, status_message, 5.0
+
+ # replace old predecessor with one without the successor
+ page.replace dom_id(@predecessor), :partial => 'todos/todo', :locals => {
+ :todo => @predecessor, :parent_container_type => parent_container_type }
+
+ # update display if pending->active
+ if @successor.active?
+ page[@successor].remove unless source_view_is_one_of(:todo, :context)
+ page.insert_html :bottom, item_container_id(@successor), :partial => 'todos/todo', :locals => {
+ :todo => @successor, :parent_container_type => parent_container_type }
+ page.visual_effect :highlight, dom_id(@successor, 'line'), {'startcolor' => "'#99ff99'"}
+ end
+
+ # update display if pending->deferred
+ if @successor.deferred?
+ page.replace dom_id(@successor), :partial => 'todos/todo', :locals => {
+ :todo => @successor, :parent_container_type => parent_container_type }
+ end
+ page << "enable_rich_interaction();"
+else
+ page.notify :error, "There was an error removing the dependency", 8.0
+end
diff --git a/app/views/todos/tag.html.erb b/app/views/todos/tag.html.erb
index c6418da5..7804c9b9 100644
--- a/app/views/todos/tag.html.erb
+++ b/app/views/todos/tag.html.erb
@@ -8,7 +8,13 @@
:locals => { :collapsible => true } %>
<% unless @deferred.nil? -%>
- <%= render :partial => "todos/deferred", :locals => { :deferred => @deferred, :collapsible => true, :append_descriptor => "tagged with ‘#{@tag_name}’", :parent_container_type => 'tag' } %>
+ <%= render :partial => "todos/deferred", :locals => {
+ :deferred => @deferred,
+ :pending => @pending,
+ :collapsible => true,
+ :append_descriptor => "tagged with ‘#{@tag_name}’",
+ :parent_container_type => 'tag'
+ } %>
<% end -%>
<% unless @hidden_todos.nil? -%>
diff --git a/app/views/todos/toggle_check.js.rjs b/app/views/todos/toggle_check.js.rjs
index 5f9e2734..bd2ff2b6 100644
--- a/app/views/todos/toggle_check.js.rjs
+++ b/app/views/todos/toggle_check.js.rjs
@@ -8,9 +8,16 @@ if @saved
page.insert_html :top, "completed_containeritems", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => "completed" }
page.visual_effect :highlight, dom_id(@todo, 'line'), {'startcolor' => "'#99ff99'"}
page[empty_container_msg_div_id].show if @down_count == 0 && !empty_container_msg_div_id.nil?
- page.show 'tickler-empty-nd' if source_view_is(:project) && @deferred_count == 0
+ page.show 'tickler-empty-nd' if source_view_is(:project) && @deferred_count == 0 && @pending_count == 0
page.hide 'empty-d' # If we've checked something as done, completed items can't be empty
end
+ # Activate pending todos that are successors of the completed
+ @pending_to_activate.each do |t|
+ logger.debug "#300: Removing #{t.description} from pending block and adding it to active"
+ page[t].remove if source_view_is(:project) or source_view_is(:tag)
+ page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
+ page.visual_effect :highlight, dom_id(t, 'line'), {'startcolor' => "'#99ff99'"}
+ end
# remove container if empty
if @remaining_in_context == 0 && source_view_is(:todo)
@@ -44,6 +51,15 @@ if @saved
page.show "empty-d" if @completed_count == 0
page.show "c"+@todo.context.id.to_s
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? # If we've checked something as undone, incomplete items can't be empty
+ # If active todos are successors of the reactivated todo they will be blocked
+ @active_to_block.each do |t|
+ logger.debug "#300: Block #{t.description} that are a successor of #{@todo.description}"
+ page[t].remove # Remove it from active
+ if source_view_is(:project) or source_view_is(:tag) # Insert it in deferred/pending block if existing
+ logger.debug "Insert #{t.description} in #{item_container_id(t)} block"
+ page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
+ end
+ end
end
page.hide "status"
diff --git a/app/views/todos/update.js.rjs b/app/views/todos/update.js.rjs
index 8a535028..6cdf7367 100644
--- a/app/views/todos/update.js.rjs
+++ b/app/views/todos/update.js.rjs
@@ -7,7 +7,7 @@ if @saved
page.notify :notice, status_message, 5.0
if source_view_is_one_of(:todo, :context, :tag)
- if @context_changed || @todo.deferred?
+ if @context_changed || @todo.deferred? || @todo.pending?
page[@todo].remove
if (@remaining_in_context == 0)
@@ -87,7 +87,14 @@ if @saved
page["p#{@todo.project_id}empty-nd"].hide
page.replace_html "badge_count", @down_count
else
- page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
+ page.replace_html "p#{@todo.project_id}items", :partial => 'todos/todo', :collection => @todo.project.not_done_todos,
+ :locals => { :parent_container_type => parent_container_type }
+ page.replace "tickler", :partial => 'todos/deferred', :locals => { :deferred => @todo.project.deferred_todos,
+ :collapsible => false,
+ :append_descriptor => "in this project",
+ :parent_container_type => 'project',
+ :pending => @todo.project.pending_todos }
+ page['tickler-empty-nd'].show if (@deferred_count == 0 and @pending_count == 0)
page.visual_effect :highlight, dom_id(@todo), :duration => 3
end
elsif source_view_is :deferred
@@ -135,6 +142,13 @@ if @saved
else
logger.error "unexpected source_view '#{params[:_source_view]}'"
end
+ # Update predecessors (if they exist and are visible)
+ @todo.uncompleted_predecessors.each do |p|
+ page << "if ($(\'#{item_container_id(p)}\')) {"
+ page[p].replace_html :partial => 'todos/todo',
+ :locals => { :todo => p, :parent_container_type => parent_container_type }
+ page << "}"
+ end
else
page.show 'error_status'
page.replace_html 'error_status', "#{error_messages_for('todo')}"
diff --git a/db/migrate/20090516000646_add_todo_dependencies.rb b/db/migrate/20090516000646_add_todo_dependencies.rb
new file mode 100644
index 00000000..2ca58dae
--- /dev/null
+++ b/db/migrate/20090516000646_add_todo_dependencies.rb
@@ -0,0 +1,13 @@
+class AddTodoDependencies < ActiveRecord::Migration
+ def self.up
+ create_table :dependencies do |t|
+ t.integer :successor_id, :null => false
+ t.integer :predecessor_id, :null => false
+ t.string :relationship_type
+ end
+ end
+
+ def self.down
+ drop_table :dependencies
+ end
+end
diff --git a/db/tracks-17-blank.db b/db/tracks-17-blank.db
index 941c0958..befc0396 100644
Binary files a/db/tracks-17-blank.db and b/db/tracks-17-blank.db differ
diff --git a/db/tracks-17-test.db b/db/tracks-17-test.db
new file mode 100644
index 00000000..3f79ee53
Binary files /dev/null and b/db/tracks-17-test.db differ
diff --git a/public/images/grip.png b/public/images/grip.png
new file mode 100644
index 00000000..40e3d9e4
Binary files /dev/null and b/public/images/grip.png differ
diff --git a/public/images/successor_off.png b/public/images/successor_off.png
new file mode 100644
index 00000000..01497e64
Binary files /dev/null and b/public/images/successor_off.png differ
diff --git a/public/images/successor_on.png b/public/images/successor_on.png
new file mode 100644
index 00000000..ea0b2bf3
Binary files /dev/null and b/public/images/successor_on.png differ
diff --git a/public/javascripts/application.js b/public/javascripts/application.js
index ee22fac9..1fcc3f73 100644
--- a/public/javascripts/application.js
+++ b/public/javascripts/application.js
@@ -191,6 +191,8 @@ function enable_rich_interaction(){
$('input[name=project[default_context_name]]').autocomplete(contextNames, {matchContains: true});
$('input[name=project_name]').autocomplete(projectNames, {matchContains: true});
$('input[name=tag_list]').autocomplete(tagNames, {multiple: true,multipleSeparator:',',matchContains:true});
+ $('input[name=predecessor_list]').autocomplete('/todos/auto_complete_for_predecessor',
+ {multiple: true,multipleSeparator:','});
/* have to bind on keypress because of limitataions of live() */
$('input[name=project_name]').live('keypress', function(){
@@ -203,6 +205,23 @@ function enable_rich_interaction(){
$('input[name=tag_list]').live('keypress', function(){
$(this).attr('edited', 'true');
});
+
+ /* Drag & Drop for successor/predecessor */
+ function drop_todo(evt, ui) {
+ dragged_todo = ui.draggable[0].id.split('_')[2];
+ dropped_todo = this.id.split('_')[2];
+ ui.draggable.hide();
+ $(this).block({message: null});
+ $.post('/todos/add_predecessor',
+ {successor: dragged_todo, predecessor: dropped_todo},
+ null, 'script');
+ }
+
+ $('.item-show').draggable({handle: '.grip', revert: 'invalid'});
+ $('.item-show').droppable({
+ drop: drop_todo,
+ hoverClass: 'hover'
+ });
}
$(document).ready(function() {
@@ -247,6 +266,10 @@ $(document).ready(function() {
$(this).next().toggle("fast"); return false;
});
+ $(".show_successors").live('click', function () {
+ $(this).next().toggle("fast"); return false;
+ });
+
/* fade flashes and alerts in automatically */
$(".alert").fadeOut(8000);
diff --git a/public/javascripts/jquery.autocomplete.js b/public/javascripts/jquery.autocomplete.js
index 9d12a29f..276736a3 100644
--- a/public/javascripts/jquery.autocomplete.js
+++ b/public/javascripts/jquery.autocomplete.js
@@ -364,6 +364,7 @@ $.Autocompleter = function(input, options) {
// limit abortion to this input
port: "autocomplete" + input.name,
dataType: options.dataType,
+ type: 'POST',
url: options.url,
data: $.extend({
q: lastWord(term),
@@ -805,4 +806,4 @@ $.fn.selection = function(start, end) {
}
};
-})(jQuery);
\ No newline at end of file
+})(jQuery);
diff --git a/public/stylesheets/standard.css b/public/stylesheets/standard.css
index 4bab7d7e..c32a167a 100644
--- a/public/stylesheets/standard.css
+++ b/public/stylesheets/standard.css
@@ -99,6 +99,9 @@ a.to_bottom:hover {background: transparent url(../images/bottom_on.png) no-repea
a.show_notes, a.link_to_notes {background-image: url(../images/notes_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
a.show_notes:hover, a.link_to_notes:hover {background-image: url(../images/notes_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
+a.show_successors, a.link_to_successors {background-image: url(/images/successor_off.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
+a.show_successors:hover, a.link_to_successors:hover {background-image: url(/images/successor_on.png); background-repeat: no-repeat; padding: 1px; background-color: transparent;}
+
/* Structural divs */
#content {
@@ -378,11 +381,7 @@ input.item-checkbox {
.description, .stale_l1, .stale_l2, .stale_l3 {
margin-left: 60px;
- position:relative;
-}
-
-.stale_l1, .stale_l2, .stale_l3 {
- padding-left: 3px;
+ position:relative;
}
.stale_l1 {
@@ -936,6 +935,15 @@ div.message {
color: #666;
}
+.grip {
+ cursor: move;
+}
+
+.hover {
+ background: #EAEAEA;
+ font-weight: bold;
+}
+
/* Error message styles */
.fieldWithErrors {
padding: 2px;
@@ -1262,6 +1270,7 @@ div.auto_complete ul {
list-style-type:none;
}
div.auto_complete ul li {
+ font-size: 1.0em;
margin:0;
padding:3px;
list-style-type: none;
diff --git a/test/selenium/project_detail/_add_deferred_todo.rsel b/test/selenium/project_detail/_add_deferred_todo.rsel
index 15fd15ef..2e798195 100644
--- a/test/selenium/project_detail/_add_deferred_todo.rsel
+++ b/test/selenium/project_detail/_add_deferred_todo.rsel
@@ -1,5 +1,5 @@
type "todo_description", "choose era"
type "todo_show_from", "1/1/2030"
click "css=#todo-form-new-action .submit_box button"
-wait_for_element_present "xpath=//div[@id='tickler'] //div[@class='item-container']"
-wait_for_element_present "xpath=//div[@id='tickler'] //div[@class='item-container'] //a[@title='01/01/2030']"
+wait_for_element_present "css=div#tickler div.item-container"
+wait_for_element_present "css=div#tickler div.item-container a[title=01/01/2030]"
diff --git a/test/selenium/project_detail/add_todo.rsel b/test/selenium/project_detail/add_todo.rsel
index 7575a3d6..d3f60e88 100644
--- a/test/selenium/project_detail/add_todo.rsel
+++ b/test/selenium/project_detail/add_todo.rsel
@@ -5,7 +5,7 @@ open "/projects/1"
# add new todo
type "todo_description", "a brand new todo"
click "css=#todo-form-new-action .submit_box button"
-wait_for_element_present "xpath=//div[@id='p1items'] //div[@class='item-container']"
+wait_for_element_present "css=div#p1items div.item-container"
# wait for flash to mention that todo was added and verify existence of new todo
wait_for_visible "flash"
diff --git a/test/selenium/tickler/create_deferred_todo_with_existing_context.rsel b/test/selenium/tickler/create_deferred_todo_with_existing_context.rsel
index 09fb6bc2..8f7fccb8 100644
--- a/test/selenium/tickler/create_deferred_todo_with_existing_context.rsel
+++ b/test/selenium/tickler/create_deferred_todo_with_existing_context.rsel
@@ -10,4 +10,4 @@ assert_context_count_incremented do
assert_confirmation "New context \"errands\" will be also created. Are you sure?"
end
wait_for_not_visible "tickler-empty-nd"
-wait_for_element_present "xpath=//div[@class='item-container'] //a[@title='01/01/2030']"
+wait_for_element_present "css=div.item-container a[title=01/01/2030]"