migrate dependencies

without functional changes
This commit is contained in:
Reinier Balt 2011-02-03 16:59:59 +01:00
parent bf5e378301
commit a02f1d2584
14 changed files with 334 additions and 143 deletions

View file

@ -36,7 +36,7 @@ class ApplicationController < ActionController::Base
before_filter :set_locale before_filter :set_locale
prepend_before_filter :login_required prepend_before_filter :login_required
prepend_before_filter :enable_mobile_content_negotiation prepend_before_filter :enable_mobile_content_negotiation
# after_filter :set_locale # after_filter :set_locale
after_filter :set_charset after_filter :set_charset
include ActionView::Helpers::TextHelper include ActionView::Helpers::TextHelper
@ -137,6 +137,12 @@ class ApplicationController < ActionController::Base
end end
end end
def auto_complete_result2(entries, phrase = nil)
json_elems = "[{" + entries.map {|item| "\"id\" : \"#{item.id}\", \"value\" : \"#{item.specification()}\""}.join("},{") + "}]"
return json_elems == "[{}]" ? "" : json_elems
end
# Uses RedCloth to transform text using either Textile or Markdown Need to # Uses RedCloth to transform text using either Textile or Markdown Need to
# require redcloth above RedCloth 3.0 or greater is needed to use Markdown, # require redcloth above RedCloth 3.0 or greater is needed to use Markdown,
# otherwise it only handles Textile # otherwise it only handles Textile

View file

@ -250,11 +250,12 @@ class TodosController < ApplicationController
end end
def remove_predecessor def remove_predecessor
@todo = current_user.todos.find(params['id'])
@source_view = params['_source_view'] || 'todo' @source_view = params['_source_view'] || 'todo'
@todo = current_user.todos.find(params['id'])
@predecessor = current_user.todos.find(params['predecessor']) @predecessor = current_user.todos.find(params['predecessor'])
@successor = @todo @successor = @todo
@removed = @successor.remove_predecessor(@predecessor) @removed = @successor.remove_predecessor(@predecessor)
determine_remaining_in_context_count
respond_to do |format| respond_to do |format|
format.js format.js
end end
@ -361,8 +362,13 @@ class TodosController < ApplicationController
update_attributes_of_todo update_attributes_of_todo
@saved = @todo.save @saved = @todo.save
# this is set after save and cleared after reload, so save it here
@removed_predecessors = @todo.removed_predecessors
@todo.reload # refresh context and project object too (not only their id's) @todo.reload # refresh context and project object too (not only their id's)
update_dependency_state
update_todo_state_if_project_changed update_todo_state_if_project_changed
determine_changes_by_this_update determine_changes_by_this_update
@ -660,7 +666,7 @@ class TodosController < ApplicationController
:conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND ' + :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND ' +
'NOT (id = ?) AND lower(description) LIKE ?', 'NOT (id = ?) AND lower(description) LIKE ?',
'active', 'pending', 'deferred', 'active', 'pending', 'deferred',
params[:id], '%' + params[:q].downcase + '%' ], params[:id], '%' + params[:term].downcase + '%' ],
:order => 'description ASC', :order => 'description ASC',
:limit => 10 :limit => 10
) )
@ -671,12 +677,12 @@ class TodosController < ApplicationController
:select => 'description, project_id, context_id, created_at', :select => 'description, project_id, context_id, created_at',
:conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?', :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?',
'active', 'pending', 'deferred', 'active', 'pending', 'deferred',
'%' + params[:q].downcase + '%' ], '%' + params[:term].downcase + '%' ],
:order => 'description ASC', :order => 'description ASC',
:limit => 10 :limit => 10
) )
end end
render :inline => "<%= auto_complete_result2(@items) %>" render :inline => auto_complete_result2(@items)
end end
def convert_to_project def convert_to_project
@ -1106,6 +1112,7 @@ class TodosController < ApplicationController
@original_item_project_id = @todo.project_id @original_item_project_id = @todo.project_id
@original_item_was_deferred = @todo.deferred? @original_item_was_deferred = @todo.deferred?
@original_item_was_hidden = @todo.hidden? @original_item_was_hidden = @todo.hidden?
@original_item_was_pending = @todo.pending?
@original_item_due = @todo.due @original_item_due = @todo.due
@original_item_due_id = get_due_id_for_calendar(@todo.due) @original_item_due_id = get_due_id_for_calendar(@todo.due)
@original_item_predecessor_list = @todo.predecessors.map{|t| t.specification}.join(', ') @original_item_predecessor_list = @todo.predecessors.map{|t| t.specification}.join(', ')
@ -1191,6 +1198,10 @@ class TodosController < ApplicationController
def update_dependencies def update_dependencies
@todo.add_predecessor_list(params[:predecessor_list]) @todo.add_predecessor_list(params[:predecessor_list])
end
def update_dependency_state
# assumes @todo.save was called so that the predecessor_list is persistent
if @original_item_predecessor_list != params[:predecessor_list] if @original_item_predecessor_list != params[:predecessor_list]
# Possible state change with new dependencies # Possible state change with new dependencies
if @todo.uncompleted_predecessors.empty? if @todo.uncompleted_predecessors.empty?
@ -1206,12 +1217,17 @@ class TodosController < ApplicationController
end end
def determine_changes_by_this_update def determine_changes_by_this_update
@todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active? @todo_was_activated_from_deferred_state = @todo.active? && @original_item_was_deferred
@todo_was_activated_from_pending_state = @todo.active? && @original_item_was_pending
@todo_was_deferred_from_active_state = @todo.deferred? && !@original_item_was_deferred @todo_was_deferred_from_active_state = @todo.deferred? && !@original_item_was_deferred
@todo_deferred_state_changed = @todo_was_deferred_from_active_state || @todo_was_activated_from_deferred_state @todo_was_blocked_from_active_state = @todo.pending? && !@original_item_was_pending
@due_date_changed = @original_item_due != @todo.due
@todo_deferred_state_changed = @original_item_was_deferred != @todo.deferred?
@todo_pending_state_changed = @original_item_was_pending != @todo.pending?
@todo_hidden_state_changed = @original_item_was_hidden != @todo.hidden? @todo_hidden_state_changed = @original_item_was_hidden != @todo.hidden?
@due_date_changed = @original_item_due != @todo.due
source_view do |page| source_view do |page|
page.calendar do page.calendar do
@old_due_empty = is_old_due_empty(@original_item_due_id) @old_due_empty = is_old_due_empty(@original_item_due_id)

View file

@ -1,17 +1,17 @@
module TodosHelper module TodosHelper
def remote_star_icon def remote_star_icon(todo=@todo)
link_to( image_tag_for_star(@todo), link_to( image_tag_for_star(todo),
toggle_star_todo_path(@todo), toggle_star_todo_path(todo),
:class => "icon star_item", :title => t('todos.star_action_with_description', :description => @todo.description)) :class => "icon star_item", :title => t('todos.star_action_with_description', :description => todo.description))
end end
def remote_edit_button def remote_edit_button(todo=@todo)
link_to( link_to(
image_tag("blank.png", :alt => t('todos.edit'), :align => "absmiddle", :id => 'edit_icon_todo_'+@todo.id.to_s, :class => 'edit_item'), image_tag("blank.png", :alt => t('todos.edit'), :align => "absmiddle", :id => 'edit_icon_todo_'+todo.id.to_s, :class => 'edit_item'),
{:controller => 'todos', :action => 'edit', :id => @todo.id}, {:controller => 'todos', :action => 'edit', :id => todo.id},
:class => "icon edit_item", :class => "icon edit_item",
:title => t('todos.edit_action_with_description', :description => @todo.description)) :title => t('todos.edit_action_with_description', :description => todo.description))
end end
def remote_delete_menu_item(todo) def remote_delete_menu_item(todo)
@ -28,9 +28,9 @@ module TodosHelper
:_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")} :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")}
url[:_tag_name] = @tag_name if @source_view == 'tag' url[:_tag_name] = @tag_name if @source_view == 'tag'
futuredate = (@todo.show_from || @todo.user.date) + days.days futuredate = (todo.show_from || todo.user.date) + days.days
options = {:x_defer_alert => false, :class => "icon_defer_item" } options = {:x_defer_alert => false, :class => "icon_defer_item" }
if @todo.due && futuredate > @todo.due if todo.due && futuredate > todo.due
options[:x_defer_alert] = true options[:x_defer_alert] = true
options[:x_defer_date_after_due_date] = t('todos.defer_date_after_due_date') options[:x_defer_date_after_due_date] = t('todos.defer_date_after_due_date')
end end
@ -38,6 +38,14 @@ module TodosHelper
return link_to(image_tag_for_defer(days), url, options) return link_to(image_tag_for_defer(days), url, options)
end end
def remote_delete_dependency(todo, predecessor)
link_to(
image_tag("blank.png", :title => t('todos.remove_dependency'), :align => "absmiddle", :class => "delete_item"),
url_for({:controller => 'todos', :action => 'remove_predecessor', :id => todo.id}),
{:class => "delete_dependency_button", :x_predecessors_id => predecessor.id}
)
end
def remote_promote_to_project_menu_item(todo) def remote_promote_to_project_menu_item(todo)
url = {:controller => 'todos', :action => 'convert_to_project', :id => todo.id, url = {:controller => 'todos', :action => 'convert_to_project', :id => todo.id,
:_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")} :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")}
@ -77,51 +85,51 @@ module TodosHelper
:class => "recurring_icon", :title => recurrence_pattern_as_text(todo.recurring_todo)) :class => "recurring_icon", :title => recurrence_pattern_as_text(todo.recurring_todo))
end end
def remote_toggle_checkbox def remote_toggle_checkbox(todo=@todo)
check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox', check_box_tag('item_id', toggle_check_todo_path(todo), todo.completed?, :class => 'item-checkbox',
:title => @todo.pending? ? t('todos.blocked_by', :predecessors => @todo.uncompleted_predecessors.map(&:description).join(', ')) : "", :readonly => @todo.pending?) :title => todo.pending? ? t('todos.blocked_by', :predecessors => todo.uncompleted_predecessors.map(&:description).join(', ')) : "", :readonly => todo.pending?)
end end
def date_span def date_span(todo=@todo)
if @todo.completed? if todo.completed?
"<span class=\"grey\">#{format_date( @todo.completed_at )}</span>" "<span class=\"grey\">#{format_date( todo.completed_at )}</span>"
elsif @todo.pending? elsif todo.pending?
"<a title='#{t('todos.depends_on')}: #{@todo.uncompleted_predecessors.map(&:description).join(', ')}'><span class=\"orange\">#{t('todos.pending')}</span></a> " "<a title='#{t('todos.depends_on')}: #{todo.uncompleted_predecessors.map(&:description).join(', ')}'><span class=\"orange\">#{t('todos.pending')}</span></a> "
elsif @todo.deferred? elsif todo.deferred?
show_date( @todo.show_from ) show_date( todo.show_from )
else else
due_date( @todo.due ) due_date( todo.due )
end end
end end
def successors_span def successors_span(todo=@todo)
unless @todo.pending_successors.empty? unless todo.pending_successors.empty?
pending_count = @todo.pending_successors.length pending_count = todo.pending_successors.length
title = "#{t('todos.has_x_pending', :count => pending_count)}: #{@todo.pending_successors.map(&:description).join(', ')}" title = "#{t('todos.has_x_pending', :count => pending_count)}: #{todo.pending_successors.map(&:description).join(', ')}"
image_tag( 'successor_off.png', :width=>'10', :height=>'16', :border=>'0', :title => title ) image_tag( 'successor_off.png', :width=>'10', :height=>'16', :border=>'0', :title => title )
end end
end end
def grip_span def grip_span(todo=@todo)
unless @todo.completed? unless todo.completed?
image_tag('grip.png', :width => '7', :height => '16', :border => '0', image_tag('grip.png', :width => '7', :height => '16', :border => '0',
:title => t('todos.drag_action_title'), :title => t('todos.drag_action_title'),
:class => 'grip') :class => 'grip')
end end
end end
def tag_list_text def tag_list_text(todo=@todo)
@todo.tags.collect{|t| t.name}.join(', ') todo.tags.collect{|t| t.name}.join(', ')
end end
def tag_list def tag_list(todo=@todo)
tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} tags_except_starred = todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + "</span>"}.join('') tag_list = tags_except_starred.collect{|t| "<span class=\"tag #{t.name.gsub(' ','-')}\">" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + "</span>"}.join('')
"<span class='tags'>#{tag_list}</span>" "<span class='tags'>#{tag_list}</span>"
end end
def tag_list_mobile def tag_list_mobile(todo=@todo)
tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} tags_except_starred = todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME}
# removed the link. TODO: add link to mobile view of tagged actions # removed the link. TODO: add link to mobile view of tagged actions
tag_list = tags_except_starred.collect{|t| tag_list = tags_except_starred.collect{|t|
"<span class=\"tag\">" + "<span class=\"tag\">" +
@ -130,30 +138,30 @@ module TodosHelper
if tag_list.empty? then "" else "<span class=\"tags\">#{tag_list}</span>" end if tag_list.empty? then "" else "<span class=\"tags\">#{tag_list}</span>" end
end end
def predecessor_list_text def predecessor_list_text(todo=@todo)
@todo.predecessors.map{|t| t.specification}.join(', ') todo.predecessors.map{|t| t.specification}.join(', ')
end end
def deferred_due_date def deferred_due_date(todo=@todo)
if @todo.deferred? && @todo.due if todo.deferred? && todo.due
t('todos.action_due_on', :date => format_date(@todo.due)) t('todos.action_due_on', :date => format_date(todo.due))
end end
end end
def project_and_context_links(parent_container_type, opts = {}) def project_and_context_links(todo, parent_container_type, opts = {})
str = '' str = ''
if @todo.completed? if todo.completed?
str += @todo.context.name unless opts[:suppress_context] str += todo.context.name unless opts[:suppress_context]
should_suppress_project = opts[:suppress_project] || @todo.project.nil? should_suppress_project = opts[:suppress_project] || todo.project.nil?
str += ", " unless str.blank? || should_suppress_project str += ", " unless str.blank? || should_suppress_project
str += @todo.project.name unless should_suppress_project str += todo.project.name unless should_suppress_project
str = "(#{str})" unless str.blank? str = "(#{str})" unless str.blank?
else else
if (['project', 'tag', 'stats', 'search'].include?(parent_container_type)) if (['project', 'tag', 'stats', 'search'].include?(parent_container_type))
str << item_link_to_context( @todo ) str << item_link_to_context( todo )
end end
if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && @todo.project_id if (['context', 'tickler', 'tag', 'stats', 'search'].include?(parent_container_type)) && @todo.project_id
str << item_link_to_project( @todo ) str << item_link_to_project( todo )
end end
end end
return str return str
@ -295,7 +303,7 @@ module TodosHelper
def update_needs_to_remove_todo_from_container def update_needs_to_remove_todo_from_container
source_view do |page| source_view do |page|
page.context { return @context_changed || @todo.deferred? || @todo.pending? } page.context { return @context_changed || @todo.deferred? || @todo.pending? }
page.project { return @todo_deferred_state_changed } page.project { return @todo_deferred_state_changed || @todo_pending_state_changed }
page.deferred { return @context_changed || !(@todo.deferred? || @todo.pending?) } page.deferred { return @context_changed || !(@todo.deferred? || @todo.pending?) }
page.calendar { return @due_date_changed || !@todo.due } page.calendar { return @due_date_changed || !@todo.due }
page.stats { return @todo.completed? } page.stats { return @todo.completed? }
@ -308,7 +316,7 @@ module TodosHelper
def replace_with_updated_todo def replace_with_updated_todo
source_view do |page| source_view do |page|
page.context { return !update_needs_to_remove_todo_from_container } page.context { return !update_needs_to_remove_todo_from_container }
page.project { return !@todo_deferred_state_changed} page.project { return !update_needs_to_remove_todo_from_container }
page.deferred { return !@context_changed && (@todo.deferred? || @todo.pending?) } page.deferred { return !@context_changed && (@todo.deferred? || @todo.pending?) }
page.calendar { return !@due_date_changed && @todo.due } page.calendar { return !@due_date_changed && @todo.due }
page.stats { return !@todo.completed? } page.stats { return !@todo.completed? }
@ -321,7 +329,7 @@ module TodosHelper
def append_updated_todo def append_updated_todo
source_view do |page| source_view do |page|
page.context { return false } page.context { return false }
page.project { return @todo_deferred_state_changed } page.project { return @todo_deferred_state_changed || @todo_pending_state_changed }
page.deferred { return @context_changed && (@todo.deferred? || @todo.pending?) } page.deferred { return @context_changed && (@todo.deferred? || @todo.pending?) }
page.calendar { return @due_date_changed && @todo.due } page.calendar { return @due_date_changed && @todo.due }
page.stats { return false } page.stats { return false }
@ -346,7 +354,7 @@ module TodosHelper
source_view do |page| source_view do |page|
page.project { page.project {
return "tickler-empty-nd" if @todo_was_deferred_from_active_state return "tickler-empty-nd" if @todo_was_deferred_from_active_state || @todo_was_blocked_from_active_state
return "p#{todo.project_id}empty-nd" return "p#{todo.project_id}empty-nd"
} }
page.tag { page.tag {
@ -367,8 +375,9 @@ module TodosHelper
source_view do |page| source_view do |page|
page.project { page.project {
container_id = "p#{@original_item_project_id}empty-nd" if @remaining_in_context == 0 container_id = "p#{@original_item_project_id}empty-nd" if @remaining_in_context == 0
container_id = "tickler-empty-nd" if (@todo_was_activated_from_deferred_state && @remaining_deferred_or_pending_count == 0) || container_id = "tickler-empty-nd" if (
(@original_item_was_deferred && @remaining_deferred_or_pending_count == 0 && @todo.completed?) ( (@todo_was_activated_from_deferred_state || @todo_was_activated_from_pending_state) && @remaining_deferred_or_pending_count == 0) ||
(@original_item_was_deferred && @remaining_deferred_or_pending_count == 0 && @todo.completed?) )
container_id = "empty-d" if @completed_count && @completed_count == 0 && !@todo.completed? container_id = "empty-d" if @completed_count && @completed_count == 0 && !@todo.completed?
} }
page.deferred { container_id = "c#{@original_item_context_id}empty-nd" if @remaining_in_context == 0 } page.deferred { container_id = "c#{@original_item_context_id}empty-nd" if @remaining_in_context == 0 }
@ -404,8 +413,4 @@ module TodosHelper
image_tag("blank.png", :title =>t('todos.star_action'), :class => class_str, :id => "star_img_"+todo.id.to_s) image_tag("blank.png", :title =>t('todos.star_action'), :class => class_str, :id => "star_img_"+todo.id.to_s)
end end
def auto_complete_result2(entries, phrase = nil)
return entries.map{|e| e.specification()}.join("\n") rescue ''
end
end end

View file

@ -39,11 +39,11 @@ class Todo < ActiveRecord::Base
STARRED_TAG_NAME = "starred" STARRED_TAG_NAME = "starred"
# regular expressions for dependencies # regular expressions for dependencies
RE_TODO = /[^"]+/ RE_TODO = /[^']+/
RE_CONTEXT = /[^"]+/ RE_CONTEXT = /[^']+/
RE_PROJECT = /[^"]+/ RE_PROJECT = /[^']+/
RE_PARTS = /"(#{RE_TODO})"\s<"(#{RE_CONTEXT})";\s"(#{RE_PROJECT})">/ # results in array RE_PARTS = /'(#{RE_TODO})'\s<'(#{RE_CONTEXT})';\s'(#{RE_PROJECT})'>/ # results in array
RE_SPEC = /"#{RE_TODO}"\s<"#{RE_CONTEXT}";\s"#{RE_PROJECT}">/ # results in string RE_SPEC = /'#{RE_TODO}'\s<'#{RE_CONTEXT}';\s'#{RE_PROJECT}'>/ # results in string
acts_as_state_machine :initial => :active, :column => 'state' acts_as_state_machine :initial => :active, :column => 'state'
@ -95,6 +95,7 @@ class Todo < ActiveRecord::Base
def initialize(*args) def initialize(*args)
super(*args) super(*args)
@predecessor_array = nil # Used for deferred save of predecessors @predecessor_array = nil # Used for deferred save of predecessors
@removed_predecessors = nil
end end
def no_uncompleted_predecessors_or_deferral? def no_uncompleted_predecessors_or_deferral?
@ -108,7 +109,7 @@ class Todo < ActiveRecord::Base
# Returns a string with description <context, project> # Returns a string with description <context, project>
def specification def specification
project_name = project.is_a?(NullProject) ? "(none)" : project.name project_name = project.is_a?(NullProject) ? "(none)" : project.name
return "\"#{description}\" <\"#{context.title}\"; \"#{project_name}\">" return "\'#{description}\' <\'#{context.title}\'; \'#{project_name}\'>"
end end
def todo_from_specification(specification) def todo_from_specification(specification)
@ -139,6 +140,7 @@ class Todo < ActiveRecord::Base
:project_id => project_id :project_id => project_id
} }
) )
return nil if todos.empty? return nil if todos.empty?
# TODO: what todo if there are more than one todo that fit the specification # TODO: what todo if there are more than one todo that fit the specification
@ -168,10 +170,14 @@ class Todo < ActiveRecord::Base
remove_array = current_array - @predecessor_array remove_array = current_array - @predecessor_array
add_array = @predecessor_array - current_array add_array = @predecessor_array - current_array
@removed_predecessors = []
# This is probably a bit naive code... # This is probably a bit naive code...
remove_array.each do |specification| remove_array.each do |specification|
t = todo_from_specification(specification) t = todo_from_specification(specification)
self.predecessors.delete(t) unless t.nil? unless t.nil?
@removed_predecessors << t
self.predecessors.delete(t)
end
end end
# ... as is this? # ... as is this?
add_array.each do |specification| add_array.each do |specification|
@ -185,6 +191,10 @@ class Todo < ActiveRecord::Base
end end
end end
def removed_predecessors
return @removed_predecessors
end
def remove_predecessor(predecessor) def remove_predecessor(predecessor)
# remove predecessor and activate myself # remove predecessor and activate myself
predecessors.delete(predecessor) predecessors.delete(predecessor)

View file

@ -11,7 +11,9 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
<div class="description<%= staleness_class( successor ) %>" style="margin-left: 20px"> <div class="description<%= staleness_class( successor ) %>" style="margin-left: 20px">
<span class="todo.descr"><%= h sanitize(successor.description) %></span> <span class="todo.descr"><%= h sanitize(successor.description) %></span>
<%= link_to_remote( <%= remote_delete_dependency(successor, predecessor) %>
<%# link_to_remote(
image_tag("blank.png", :title => t('todos.remove_dependency'), :align => "absmiddle", :class => "delete_item"), image_tag("blank.png", :title => t('todos.remove_dependency'), :align => "absmiddle", :class => "delete_item"),
{:url => {:controller => 'todos', :action => 'remove_predecessor', :id => successor.id}, {:url => {:controller => 'todos', :action => 'remove_predecessor', :id => successor.id},
:method => 'delete', :method => 'delete',
@ -19,7 +21,17 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
:before => successor_start_waiting_js(successor)}, :before => successor_start_waiting_js(successor)},
{:style => "background: transparent;"}) %> {:style => "background: transparent;"}) %>
<%= render(:partial => "todos/toggle_successors", :object => successor, :locals => { :suppress_button => true }) unless successor.pending_successors.empty? %> <% unless successor.pending_successors.empty? %>
<div class="todo_successors" id="<%= dom_id(successor, 'successors') %>">
<%= render :partial => "todos/successor",
:collection => successor.pending_successors,
:locals => { :todo => successor,
:parent_container_type => parent_container_type,
:suppress_dependencies => true,
:predecessor => successor }
%>
</div>
<% end %>
</div> </div>
</div> </div>
</div> </div>

View file

@ -1,5 +1,4 @@
<% <%
@todo = todo
suppress_context ||= false suppress_context ||= false
suppress_project ||= false suppress_project ||= false
suppress_edit_button ||= todo.completed? suppress_edit_button ||= todo.completed?
@ -9,9 +8,9 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
%> %>
<div id="<%= dom_id(todo) %>" class="item-container"> <div id="<%= dom_id(todo) %>" class="item-container">
<div id="<%= dom_id(todo, 'line') %>" class="item-show"> <div id="<%= dom_id(todo, 'line') %>" class="item-show">
<%= remote_star_icon %> <%= remote_star_icon(todo) %>
<%= remote_toggle_checkbox unless source_view_is :deferred %> <%= remote_toggle_checkbox(todo) unless source_view_is :deferred %>
<%= remote_edit_button unless suppress_edit_button %> <%= remote_edit_button(todo) unless suppress_edit_button %>
<ul class="sf-menu sf-item-menu"> <ul class="sf-menu sf-item-menu">
<li style="z-index:<%=@z_index_counter%>"><%= image_tag "downarrow.png", :alt=> "" %> <li style="z-index:<%=@z_index_counter%>"><%= image_tag "downarrow.png", :alt=> "" %>
<ul id="ul<%= dom_id(todo) %>"> <ul id="ul<%= dom_id(todo) %>">
@ -25,13 +24,13 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
</li> </li>
</ul> </ul>
<div class="description<%= staleness_class( todo ) %>"> <div class="description<%= staleness_class( todo ) %>">
<%= grip_span %> <%= grip_span(todo) %>
<%= date_span -%> <%= date_span(todo) -%>
<span class="todo.descr"><%= h todo.description %></span> <span class="todo.descr"><%= h todo.description %></span>
<%= image_tag_for_recurring_todo(todo) if @todo.from_recurring_todo? %> <%= image_tag_for_recurring_todo(todo) if todo.from_recurring_todo? %>
<%= tag_list %> <%= tag_list(todo) %>
<%= deferred_due_date %> <%= deferred_due_date(todo) %>
<%= project_and_context_links( parent_container_type, :suppress_context => suppress_context, :suppress_project => suppress_project ) %> <%= project_and_context_links( todo, parent_container_type, :suppress_context => suppress_context, :suppress_project => suppress_project ) %>
<%= collapsed_notes_image(todo) if todo.notes? %> <%= collapsed_notes_image(todo) if todo.notes? %>
<%= collapsed_successors_image(todo) unless todo.pending_successors.empty? %> <%= collapsed_successors_image(todo) unless todo.pending_successors.empty? %>
</div> </div>

View file

@ -19,7 +19,7 @@ function replace_updated_predecessor() {
function show_in_tickler_box() { function show_in_tickler_box() {
$("#tickler-empty-nd").hide(); $("#tickler-empty-nd").hide();
$('#tickler').html( html_for_deferred_todos() ); $('#tickler').append( html_for_deferred_todo() );
} }
function regenerate_predecessor_family() { function regenerate_predecessor_family() {
@ -37,12 +37,8 @@ function html_for_predecessor() {
return "<%= escape_javascript(render(:partial => @predecessor, :locals => { :parent_container_type => parent_container_type })) %>"; return "<%= escape_javascript(render(:partial => @predecessor, :locals => { :parent_container_type => parent_container_type })) %>";
} }
function html_for_deferred_todos() { function html_for_deferred_todo() {
return "<%= escape_javascript(render(:partial => 'todos/deferred', :locals => { :deferred => @todo.project.deferred_todos, return "<%= escape_javascript(render(:partial => @todo, :locals => { :parent_container_type => parent_container_type })) %>";
:collapsible => false,
:append_descriptor => t('todos.append_in_this_project'),
:parent_container_type => 'project',
:pending => @todo.project.pending_todos })) %>";
} }
<% end # if !@saved <% end # if !@saved

View file

@ -1,35 +1,63 @@
if @removed <% # TODO: lots of overlap with add_predecessor --> helpers?
status_message = t('todos.removed_predecessor', :successor => @successor.description, :predecessor => @predecessor.description) if @removed -%>
page.notify :notice, status_message, 5.0 TracksPages.page_notify('notice', "<%= t('todos.removed_predecessor', :successor => @successor.description, :predecessor => @predecessor.description) %>", 8);
# replace old predecessor with one without the successor replace_updated_predecessor();
page.replace dom_id(@predecessor), :partial => 'todos/todo', :locals => { regenerate_predecessor_family();
:todo => @predecessor, :parent_container_type => parent_container_type } update_successor();
<% else -%>
TracksPages.page_notify('error', "<%=t('todos.error_removing_dependency')%>", 8);
<% end -%>
# regenerate predecessor family function replace_updated_predecessor() {
$('#<%= dom_id(@predecessor) %>').html( html_for_predecessor() );
}
function regenerate_predecessor_family() {
<%
parents = @predecessor.predecessors parents = @predecessor.predecessors
until parents.empty? until parents.empty?
parent = parents.pop parent = parents.pop
parents += parent.predecessors parents += parent.predecessors -%>
page[parent].replace_html :partial => 'todos/todo', :locals => { :todo => parent, :parent_container_type => parent_container_type } $('#<%= dom_id(parent) %>').html("<%= escape_javascript(render(:partial => parent, :locals => { :parent_container_type => parent_container_type })) %>");
end <%end -%>
}
# update display if pending->active function update_successor() {
if @successor.active? <%
page[@successor].remove unless source_view_is_one_of(:todo, :context) if @successor.active? -%>
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? <%= "remove_successor();" unless source_view_is_one_of(:todo, :context) %>
page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@successor.context_id}" <%= "hide_empty_message();" unless empty_container_msg_div_id.nil? %>
page.insert_html :bottom, item_container_id(@successor), :partial => 'todos/todo', :locals => { <%= "show_empty_deferred_message(); " if @remaining_deferred_or_pending_count == 0 %>
:todo => @successor, :parent_container_type => parent_container_type } <% if source_view_is_one_of(:todo, :deferred, :tag) -%>
page.visual_effect :highlight, dom_id(@successor, 'line'), {'startcolor' => "'#99ff99'"} $('#c<%= @successor.context_id %>').fadeIn(500, function() {});
$('#no_todos_in_tag_view').slideUp(100);
<% end -%>
$('#<%=item_container_id(@successor)%>').append(html_for_new_successor());
$('#<%= dom_id(@successor, 'line')%>').effect('highlight', {}, 2000 ); <%
elsif @successor.deferred? -%>
$('#<%= dom_id(@successor)%>').html(html_for_new_successor()); <%
end end
%>
}
# update display if pending->deferred function hide_empty_message() {
if @successor.deferred? $('#<%=empty_container_msg_div_id%>').hide();
page.replace dom_id(@successor), :partial => 'todos/todo', :locals => { }
:todo => @successor, :parent_container_type => parent_container_type }
end function show_empty_deferred_message() {
page << "enable_rich_interaction();" $('#tickler-empty-nd').slideDown(100);
else }
page.notify :error, t('todos.error_removing_dependency'), 8.0 function remove_successor() {
end <% # TODO: last todo in context --> remove context??
-%>
$('#<%=dom_id(@successor)%>').remove();
}
function html_for_predecessor() {
return "<%= @removed ? escape_javascript(render(:partial => @predecessor, :locals => { :parent_container_type => parent_container_type })) : "" %>";
}
function html_for_new_successor() {
return "<%= @removed ? escape_javascript(render(:partial => @successor, :locals => { :parent_container_type => parent_container_type })) : "" %>";
}

View file

@ -16,6 +16,7 @@
animation << "hide_context" if update_needs_to_hide_context animation << "hide_context" if update_needs_to_hide_context
animation << "highlight_updated_todo" animation << "highlight_updated_todo"
animation << "update_empty_tag_container" if source_view_is(:tag) animation << "update_empty_tag_container" if source_view_is(:tag)
animation << "update_predecessors"
%> %>
<%= render_animation(animation) %> <%= render_animation(animation) %>
@ -35,7 +36,7 @@ function add_to_existing_container(next_steps) {
$('#<%= item_container_id(@todo) %>').append(html_for_todo()); $('#<%= item_container_id(@todo) %>').append(html_for_todo());
<% if source_view_is_one_of(:project,:calendar) -%> <% if source_view_is_one_of(:project,:calendar) -%>
next_steps.go(); next_steps.go();
<% if (@target_context_count==1) || (@todo.deferred? && @remaining_deferred_or_pending_count == 1) -%> <% if (@target_context_count==1) || ( (@todo.deferred? || @todo.pending?) && @remaining_deferred_or_pending_count == 1) -%>
$("#<%= empty_container_msg_div_id %>").slideUp(100); $("#<%= empty_container_msg_div_id %>").slideUp(100);
<% end -%> <% end -%>
<% else -%> <% else -%>
@ -109,4 +110,9 @@ function update_predecessors() {
$('#<%=dom_id(p)%>').html('<%=escape_javascript(render(:partial => p, :locals => { :parent_container_type => parent_container_type }))%>'); $('#<%=dom_id(p)%>').html('<%=escape_javascript(render(:partial => p, :locals => { :parent_container_type => parent_container_type }))%>');
} }
<% end -%> <% end -%>
<% @removed_predecessors.each do |p| -%>
if ($('#<%=item_container_id(p)%>')) {
$('#<%=dom_id(p)%>').html('<%=escape_javascript(render(:partial => p, :locals => { :parent_container_type => parent_container_type }))%>');
}
<% end -%>
} }

View file

@ -9,21 +9,21 @@ Feature: dependencies
| testuser | secret | false | | testuser | secret | false |
And I have logged in as "testuser" with password "secret" And I have logged in as "testuser" with password "secret"
@selenium @wip @selenium
Scenario: Adding dependency to dependency Scenario: Adding dependency to dependency by drag and drop
Given I have a project "dependencies" with 3 todos Given I have a project "dependencies" with 3 todos
And "Todo 2" depends on "Todo 1" And "Todo 2" depends on "Todo 1"
When I visit the "dependencies" project When I visit the "dependencies" project
And I drag "Todo 3" to "Todo 2" And I drag "Todo 3" to "Todo 2"
Then the dependencies of "Todo 2" should include "Todo 1" Then the successors of "Todo 1" should include "Todo 2"
And the dependencies of "Todo 3" should include "Todo 2" And the successors of "Todo 2" should include "Todo 3"
When I expand the dependencies of "Todo 1" When I expand the dependencies of "Todo 1"
Then I should see "Todo 2" within the dependencies of "Todo 1" Then I should see "Todo 2" within the dependencies of "Todo 1"
And I should see "Todo 3" within the dependencies of "Todo 1" And I should see "Todo 3" within the dependencies of "Todo 1"
When I expand the dependencies of "Todo 2" When I expand the dependencies of "Todo 2"
Then I should see "Todo 3" within the dependencies of "Todo 2" Then I should see "Todo 3" within the dependencies of "Todo 2"
@selenium @wip @selenium
Scenario: Adding dependency with comma to todo # for #975 Scenario: Adding dependency with comma to todo # for #975
Given I have a context called "@pc" Given I have a context called "@pc"
And I have a project "dependencies" that has the following todos And I have a project "dependencies" that has the following todos
@ -32,9 +32,49 @@ Feature: dependencies
| test me | @pc | | test me | @pc |
When I visit the "dependencies" project When I visit the "dependencies" project
And I drag "test me" to "test,1, 2,3" And I drag "test me" to "test,1, 2,3"
Then the dependencies of "test me" should include "test,1, 2,3" Then the successors of "test,1, 2,3" should include "test me"
When I edit the dependency of "test me" to '"test,1, 2,3" <"@pc"; "dependencies">,"test,1, 2,3" <"@pc"; "dependencies">' When I edit the dependency of "test me" to "'test,1, 2,3' <'@pc'; 'dependencies'>,'test,1, 2,3' <'@pc'; 'dependencies'>"
Then there should not be an error Then there should not be an error
Scenario: Deleting a predecessor will activate successors Scenario: Deleting a predecessor will activate successors
Given this is a pending scenario Given this is a pending scenario
@selenium
Scenario: I can edit a todo to add the todo as a dependency to another
Given I have a context called "@pc"
And I have a project "dependencies" that has the following todos
| description | context |
| test 1 | @pc |
| test 2 | @pc |
| test 3 | @pc |
When I visit the "dependencies" project
When I edit the dependency of "test 1" to "'test 2' <'@pc'; 'dependencies'>"
Then I should see "test 1" within the dependencies of "test 2"
And I should see "test 1" in the deferred container
When I edit the dependency of "test 1" to "'test 2' <'@pc'; 'dependencies'>, 'test 3' <'@pc'; 'dependencies'>"
Then I should see "test 1" within the dependencies of "test 2"
Then I should see "test 1" within the dependencies of "test 3"
When I edit the dependency of "test 1" to "'test 2' <'@pc'; 'dependencies'>"
And I edit the dependency of "test 2" to "'test 3' <'@pc'; 'dependencies'>"
Then I should see "test 1" within the dependencies of "test 3"
Then I should see "test 2" within the dependencies of "test 3"
@selenium
Scenario: I can remove a dependency by editing the todo
Given I have a context called "@pc"
And I have a project "dependencies" that has the following todos
| description | context |
| test 1 | @pc |
| test 2 | @pc |
And "test 1" depends on "test 2"
When I visit the "dependencies" project
Then I should see "test 1" in the deferred container
When I edit the dependency of "test 1" to ""
Then I should not see "test 1" within the dependencies of "test 2"
And I should not see "test 1" in the deferred container
Scenario: Deleting a predecessor will activate successors
Given this is a pending scenario
Scenario: Deleting a successor will update predecessor
Given this is a pending scenario

View file

@ -162,14 +162,15 @@ When /^I submit the new multiple actions form with$/ do |multi_line_descriptions
submit_multiple_next_action_form submit_multiple_next_action_form
end end
When /^I edit the dependency of "([^"]*)" to '([^'']*)'$/ do |todo_name, deps| When /^I edit the dependency of "([^"]*)" to "([^"]*)"$/ do |todo_name, deps|
todo = @dep_todo = @current_user.todos.find_by_description(todo_name) todo = @dep_todo = @current_user.todos.find_by_description(todo_name)
todo.should_not be_nil todo.should_not be_nil
# click edit # click edit
selenium.click("//div[@id='line_todo_#{todo.id}']//img[@id='edit_icon_todo_#{todo.id}']", :wait_for => :ajax, :javascript_framework => :jquery) selenium.click("//div[@id='line_todo_#{todo.id}']//img[@id='edit_icon_todo_#{todo.id}']", :wait_for => :ajax, :javascript_framework => :jquery)
fill_in "predecessor_list_todo_#{todo.id}", :with => deps fill_in "predecessor_list_todo_#{todo.id}", :with => deps
# submit form
selenium.click("//div[@id='edit_todo_#{todo.id}']//button[@id='submit_todo_#{todo.id}']", :wait_for => :ajax, :javascript_framework => :jquery) submit_edit_todo_form(todo)
sleep(1) # TODO: replace with some wait_for
end end
Then /^I should see ([0-9]+) todos$/ do |count| Then /^I should see ([0-9]+) todos$/ do |count|
@ -179,11 +180,12 @@ Then /^I should see ([0-9]+) todos$/ do |count|
end end
Then /^there should not be an error$/ do Then /^there should not be an error$/ do
# form should be gone and thus not errors visible sleep(5)
# form should be gone and thus no errors visible
selenium.is_visible("edit_todo_#{@dep_todo.id}").should == false selenium.is_visible("edit_todo_#{@dep_todo.id}").should == false
end end
Then /^the dependencies of "(.*)" should include "(.*)"$/ do |child_name, parent_name| Then /^the successors of "(.*)" should include "(.*)"$/ do |parent_name, child_name|
parent = @current_user.todos.find_by_description(parent_name) parent = @current_user.todos.find_by_description(parent_name)
parent.should_not be_nil parent.should_not be_nil
@ -206,6 +208,17 @@ Then /^I should see "([^\"]*)" within the dependencies of "([^\"]*)"$/ do |succe
selenium.wait_for_element(xpath, :timeout_in_seconds => 5) selenium.wait_for_element(xpath, :timeout_in_seconds => 5)
end end
Then /^I should not see "([^"]*)" within the dependencies of "([^"]*)"$/ do |successor_description, todo_description|
todo = @current_user.todos.find_by_description(todo_description)
todo.should_not be_nil
successor = @current_user.todos.find_by_description(successor_description)
successor.should_not be_nil
# let selenium look for the presence of the successor
xpath = "xpath=//div[@id='line_todo_#{todo.id}']//div[@id='successor_line_todo_#{successor.id}']//span"
selenium.is_element_present(xpath).should be_false
end
Then /^I should see the todo "([^\"]*)"$/ do |todo_description| Then /^I should see the todo "([^\"]*)"$/ do |todo_description|
selenium.is_element_present("//span[.=\"#{todo_description}\"]").should be_true selenium.is_element_present("//span[.=\"#{todo_description}\"]").should be_true
end end
@ -237,3 +250,21 @@ end
Then /^a confirmation for adding a new context "([^"]*)" should be asked$/ do |context_name| Then /^a confirmation for adding a new context "([^"]*)" should be asked$/ do |context_name|
selenium.get_confirmation.should == "New context \"#{context_name}\" will be also created. Are you sure?" selenium.get_confirmation.should == "New context \"#{context_name}\" will be also created. Are you sure?"
end end
Then /^I should see "([^"]*)" in the deferred container$/ do |todo_description|
todo = @current_user.todos.find_by_description(todo_description)
todo.should_not be_nil
xpath = "//div[@id='tickler']//div[@id='line_todo_#{todo.id}']"
selenium.is_element_present(xpath).should be_true
end
Then /^I should not see "([^"]*)" in the deferred container$/ do |todo_description|
todo = @current_user.todos.find_by_description(todo_description)
todo.should_not be_nil
xpath = "//div[@id='tickler']//div[@id='line_todo_#{todo.id}']"
selenium.is_element_present(xpath).should be_false
end

View file

@ -15,6 +15,10 @@ module TracksStepHelper
selenium.click("xpath=//form[@id='project_form']//button[@id='project_new_project_submit']", :wait_for => :ajax, :javascript_framework => :jquery) selenium.click("xpath=//form[@id='project_form']//button[@id='project_new_project_submit']", :wait_for => :ajax, :javascript_framework => :jquery)
end end
def submit_edit_todo_form (todo)
selenium.click("//div[@id='edit_todo_#{todo.id}']//button[@id='submit_todo_#{todo.id}']", :wait_for => :ajax, :javascript_framework => :jquery)
end
def format_date(date) def format_date(date)
# copy-and-past from ApplicationController::format_date # copy-and-past from ApplicationController::format_date
return date ? date.in_time_zone(@current_user.prefs.time_zone).strftime("#{@current_user.prefs.date_format}") : '' return date ? date.in_time_zone(@current_user.prefs.time_zone).strftime("#{@current_user.prefs.date_format}") : ''

View file

@ -338,7 +338,7 @@ var TodoItems = {
/* set behavior for toggle checkboxes for Recurring Todos */ /* set behavior for toggle checkboxes for Recurring Todos */
$(".item-container input.item-checkbox").live('click', function(ev){ $(".item-container input.item-checkbox").live('click', function(ev){
put_with_ajax_and_block_element(this.value, $(this)); put_with_ajax_and_block_element(this.value, $(this).parents(".item-container"));
return false; return false;
}); });
@ -362,14 +362,23 @@ var TodoItems = {
return false; return false;
}); });
// defer a todo
$(".item-container a.icon_defer_item").live('click', function(ev){ $(".item-container a.icon_defer_item").live('click', function(ev){
if ($(this).attr("x_defer_alert") == "true") if ($(this).attr("x_defer_alert") == "true")
alert ($(this).attr("x_defer_date_after_due_date")); alert ($(this).attr("x_defer_date_after_due_date"));
else else
put_with_ajax_and_block_element(this.href, $(this)); put_with_ajax_and_block_element(this.href, $(this).parents(".item-container"));
return false; return false;
}); });
/* delete button to delete a project from the list */
$('.item-container a.delete_dependency_button').live('click', function(evt){
predecessor_id=$(this).attr("x_predecessors_id");
ajax_options = default_ajax_options_for_scripts('DELETE', this.href, $(this).parents('.item-container'));
ajax_options.data += "&predecessor="+predecessor_id
$.ajax(ajax_options);
return false;
});
} }
} }
@ -941,8 +950,37 @@ function enable_rich_interaction(){
/* multiple: true, /* multiple: true,
multipleSeparator:',' */ multipleSeparator:',' */
$('input[name=predecessor_list]:not(.ac_input)').autocomplete({ $('input[name=predecessor_list]:not(.ac_input)')
source: relative_to_root('auto_complete_for_predecessor') .bind( "keydown", function( event ) { // don't navigate away from the field on tab when selecting an item
if ( event.keyCode === $.ui.keyCode.TAB &&
$( this ).data( "autocomplete" ).menu.active ) {
event.preventDefault();
}
})
.autocomplete({
minLength: 0,
source: function( request, response ) {
last_term = extractLast( request.term );
if (last_term != "" && last_term != " ")
$.getJSON( relative_to_root('auto_complete_for_predecessor'), {
term: last_term
}, response );
},
focus: function() {
// prevent value inserted on focus
return false;
},
select: function( event, ui ) {
var terms = split( this.value );
// remove the current input
terms.pop();
// add the selected item
terms.push( ui.item.value );
// add placeholder to get the comma-and-space at the end
//terms.push( "" );
this.value = terms.join( ", " );
return false;
}
}); });
/* have to bind on keypress because of limitations of live() */ /* have to bind on keypress because of limitations of live() */