diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 260aef71..779f9031 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -36,7 +36,7 @@ class ApplicationController < ActionController::Base before_filter :set_locale prepend_before_filter :login_required prepend_before_filter :enable_mobile_content_negotiation -# after_filter :set_locale + # after_filter :set_locale after_filter :set_charset include ActionView::Helpers::TextHelper @@ -137,6 +137,12 @@ class ApplicationController < ActionController::Base 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 # require redcloth above RedCloth 3.0 or greater is needed to use Markdown, # otherwise it only handles Textile diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index b58c0e6b..83a8ef74 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -250,11 +250,12 @@ class TodosController < ApplicationController end def remove_predecessor - @todo = current_user.todos.find(params['id']) @source_view = params['_source_view'] || 'todo' + @todo = current_user.todos.find(params['id']) @predecessor = current_user.todos.find(params['predecessor']) @successor = @todo @removed = @successor.remove_predecessor(@predecessor) + determine_remaining_in_context_count respond_to do |format| format.js end @@ -361,8 +362,13 @@ class TodosController < ApplicationController update_attributes_of_todo @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) + update_dependency_state update_todo_state_if_project_changed determine_changes_by_this_update @@ -660,7 +666,7 @@ class TodosController < ApplicationController :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND ' + 'NOT (id = ?) AND lower(description) LIKE ?', 'active', 'pending', 'deferred', - params[:id], '%' + params[:q].downcase + '%' ], + params[:id], '%' + params[:term].downcase + '%' ], :order => 'description ASC', :limit => 10 ) @@ -671,12 +677,12 @@ class TodosController < ApplicationController :select => 'description, project_id, context_id, created_at', :conditions => [ '(todos.state = ? OR todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?', 'active', 'pending', 'deferred', - '%' + params[:q].downcase + '%' ], + '%' + params[:term].downcase + '%' ], :order => 'description ASC', :limit => 10 ) end - render :inline => "<%= auto_complete_result2(@items) %>" + render :inline => auto_complete_result2(@items) end def convert_to_project @@ -1106,6 +1112,7 @@ class TodosController < ApplicationController @original_item_project_id = @todo.project_id @original_item_was_deferred = @todo.deferred? @original_item_was_hidden = @todo.hidden? + @original_item_was_pending = @todo.pending? @original_item_due = @todo.due @original_item_due_id = get_due_id_for_calendar(@todo.due) @original_item_predecessor_list = @todo.predecessors.map{|t| t.specification}.join(', ') @@ -1191,6 +1198,10 @@ class TodosController < ApplicationController def update_dependencies @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] # Possible state change with new dependencies if @todo.uncompleted_predecessors.empty? @@ -1206,12 +1217,17 @@ class TodosController < ApplicationController end 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_deferred_state_changed = @todo_was_deferred_from_active_state || @todo_was_activated_from_deferred_state - @due_date_changed = @original_item_due != @todo.due + @todo_was_blocked_from_active_state = @todo.pending? && !@original_item_was_pending + + @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? + @due_date_changed = @original_item_due != @todo.due + source_view do |page| page.calendar do @old_due_empty = is_old_due_empty(@original_item_due_id) diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index c4fbe5ee..70d5847a 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -1,17 +1,17 @@ module TodosHelper - def remote_star_icon - link_to( image_tag_for_star(@todo), - toggle_star_todo_path(@todo), - :class => "icon star_item", :title => t('todos.star_action_with_description', :description => @todo.description)) + def remote_star_icon(todo=@todo) + link_to( image_tag_for_star(todo), + toggle_star_todo_path(todo), + :class => "icon star_item", :title => t('todos.star_action_with_description', :description => todo.description)) end - def remote_edit_button + def remote_edit_button(todo=@todo) link_to( - 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}, + 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}, :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 def remote_delete_menu_item(todo) @@ -28,9 +28,9 @@ module TodosHelper :_source_view => (@source_view.underscore.gsub(/\s+/,'_') rescue "")} 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" } - if @todo.due && futuredate > @todo.due + if todo.due && futuredate > todo.due options[:x_defer_alert] = true options[:x_defer_date_after_due_date] = t('todos.defer_date_after_due_date') end @@ -38,6 +38,14 @@ module TodosHelper return link_to(image_tag_for_defer(days), url, options) 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) url = {:controller => 'todos', :action => 'convert_to_project', :id => todo.id, :_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)) end - def remote_toggle_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?) + def remote_toggle_checkbox(todo=@todo) + 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?) end - def date_span - if @todo.completed? - "#{format_date( @todo.completed_at )}" - elsif @todo.pending? - "#{t('todos.pending')} " - elsif @todo.deferred? - show_date( @todo.show_from ) + def date_span(todo=@todo) + if todo.completed? + "#{format_date( todo.completed_at )}" + elsif todo.pending? + "#{t('todos.pending')} " + elsif todo.deferred? + show_date( todo.show_from ) else - due_date( @todo.due ) + due_date( todo.due ) end end - def successors_span - unless @todo.pending_successors.empty? - pending_count = @todo.pending_successors.length - title = "#{t('todos.has_x_pending', :count => pending_count)}: #{@todo.pending_successors.map(&:description).join(', ')}" + def successors_span(todo=@todo) + unless todo.pending_successors.empty? + pending_count = todo.pending_successors.length + 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 ) end end - def grip_span - unless @todo.completed? + def grip_span(todo=@todo) + unless todo.completed? image_tag('grip.png', :width => '7', :height => '16', :border => '0', :title => t('todos.drag_action_title'), :class => 'grip') end end - def tag_list_text - @todo.tags.collect{|t| t.name}.join(', ') + def tag_list_text(todo=@todo) + todo.tags.collect{|t| t.name}.join(', ') end - def tag_list - tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} + def tag_list(todo=@todo) + tags_except_starred = todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} tag_list = tags_except_starred.collect{|t| "" + link_to(t.name, :controller => "todos", :action => "tag", :id => t.name) + ""}.join('') "#{tag_list}" end - def tag_list_mobile - tags_except_starred = @todo.tags.reject{|t| t.name == Todo::STARRED_TAG_NAME} + def tag_list_mobile(todo=@todo) + 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 tag_list = tags_except_starred.collect{|t| "" + @@ -130,30 +138,30 @@ module TodosHelper if tag_list.empty? then "" else "#{tag_list}" end end - def predecessor_list_text - @todo.predecessors.map{|t| t.specification}.join(', ') + def predecessor_list_text(todo=@todo) + todo.predecessors.map{|t| t.specification}.join(', ') end - def deferred_due_date - if @todo.deferred? && @todo.due - t('todos.action_due_on', :date => format_date(@todo.due)) + def deferred_due_date(todo=@todo) + if todo.deferred? && todo.due + t('todos.action_due_on', :date => format_date(todo.due)) end end - def project_and_context_links(parent_container_type, opts = {}) + def project_and_context_links(todo, parent_container_type, opts = {}) str = '' - if @todo.completed? - str += @todo.context.name unless opts[:suppress_context] - should_suppress_project = opts[:suppress_project] || @todo.project.nil? + if todo.completed? + str += todo.context.name unless opts[:suppress_context] + should_suppress_project = opts[:suppress_project] || todo.project.nil? 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? else if (['project', 'tag', 'stats', 'search'].include?(parent_container_type)) - str << item_link_to_context( @todo ) + str << item_link_to_context( todo ) end 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 return str @@ -295,7 +303,7 @@ module TodosHelper def update_needs_to_remove_todo_from_container source_view do |page| 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.calendar { return @due_date_changed || !@todo.due } page.stats { return @todo.completed? } @@ -308,7 +316,7 @@ module TodosHelper def replace_with_updated_todo source_view do |page| 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.calendar { return !@due_date_changed && @todo.due } page.stats { return !@todo.completed? } @@ -321,7 +329,7 @@ module TodosHelper def append_updated_todo source_view do |page| 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.calendar { return @due_date_changed && @todo.due } page.stats { return false } @@ -346,7 +354,7 @@ module TodosHelper source_view do |page| 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" } page.tag { @@ -367,8 +375,9 @@ module TodosHelper source_view do |page| page.project { 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) || - (@original_item_was_deferred && @remaining_deferred_or_pending_count == 0 && @todo.completed?) + container_id = "tickler-empty-nd" if ( + ( (@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? } 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) end - def auto_complete_result2(entries, phrase = nil) - return entries.map{|e| e.specification()}.join("\n") rescue '' - end - end diff --git a/app/models/todo.rb b/app/models/todo.rb index adc0a5de..fdbe1304 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -39,11 +39,11 @@ class Todo < ActiveRecord::Base STARRED_TAG_NAME = "starred" # regular expressions for dependencies - RE_TODO = /[^"]+/ - RE_CONTEXT = /[^"]+/ - RE_PROJECT = /[^"]+/ - 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_TODO = /[^']+/ + RE_CONTEXT = /[^']+/ + RE_PROJECT = /[^']+/ + 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 acts_as_state_machine :initial => :active, :column => 'state' @@ -95,6 +95,7 @@ class Todo < ActiveRecord::Base def initialize(*args) super(*args) @predecessor_array = nil # Used for deferred save of predecessors + @removed_predecessors = nil end def no_uncompleted_predecessors_or_deferral? @@ -108,7 +109,7 @@ class Todo < ActiveRecord::Base # Returns a string with description def specification project_name = project.is_a?(NullProject) ? "(none)" : project.name - return "\"#{description}\" <\"#{context.title}\"; \"#{project_name}\">" + return "\'#{description}\' <\'#{context.title}\'; \'#{project_name}\'>" end def todo_from_specification(specification) @@ -139,6 +140,7 @@ class Todo < ActiveRecord::Base :project_id => project_id } ) + return nil if todos.empty? # TODO: what todo if there are more than one todo that fit the specification @@ -167,11 +169,15 @@ class Todo < ActiveRecord::Base current_array = predecessors.map{|p| p.specification} remove_array = current_array - @predecessor_array add_array = @predecessor_array - current_array - + + @removed_predecessors = [] # This is probably a bit naive code... remove_array.each do |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 # ... as is this? add_array.each do |specification| @@ -182,7 +188,11 @@ class Todo < ActiveRecord::Base logger.error "Could not find #{specification}" # Unexpected since validation passed end end - end + end + end + + def removed_predecessors + return @removed_predecessors end def remove_predecessor(predecessor) diff --git a/app/views/todos/_successor.html.erb b/app/views/todos/_successor.html.erb index 4dcca50d..a82fad19 100644 --- a/app/views/todos/_successor.html.erb +++ b/app/views/todos/_successor.html.erb @@ -10,16 +10,28 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
<%= h sanitize(successor.description) %> - - <%= 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"), {:url => {:controller => 'todos', :action => 'remove_predecessor', :id => successor.id}, - :method => 'delete', - :with => "'#{parameters}&predecessor=#{predecessor.id}'", - :before => successor_start_waiting_js(successor)}, + :method => 'delete', + :with => "'#{parameters}&predecessor=#{predecessor.id}'", + :before => successor_start_waiting_js(successor)}, {:style => "background: transparent;"}) %> - - <%= render(:partial => "todos/toggle_successors", :object => successor, :locals => { :suppress_button => true }) unless successor.pending_successors.empty? %> + + <% unless successor.pending_successors.empty? %> +
+ <%= render :partial => "todos/successor", + :collection => successor.pending_successors, + :locals => { :todo => successor, + :parent_container_type => parent_container_type, + :suppress_dependencies => true, + :predecessor => successor } + %> +
+ <% end %>
diff --git a/app/views/todos/_todo.html.erb b/app/views/todos/_todo.html.erb index e98af6ad..052412df 100644 --- a/app/views/todos/_todo.html.erb +++ b/app/views/todos/_todo.html.erb @@ -1,5 +1,4 @@ <% -@todo = todo suppress_context ||= false suppress_project ||= false suppress_edit_button ||= todo.completed? @@ -9,9 +8,9 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag' %>
- <%= remote_star_icon %> - <%= remote_toggle_checkbox unless source_view_is :deferred %> - <%= remote_edit_button unless suppress_edit_button %> + <%= remote_star_icon(todo) %> + <%= remote_toggle_checkbox(todo) unless source_view_is :deferred %> + <%= remote_edit_button(todo) unless suppress_edit_button %>
  • <%= image_tag "downarrow.png", :alt=> "" %>
      @@ -25,13 +24,13 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
    - <%= grip_span %> - <%= date_span -%> + <%= grip_span(todo) %> + <%= date_span(todo) -%> <%= h todo.description %> - <%= image_tag_for_recurring_todo(todo) if @todo.from_recurring_todo? %> - <%= tag_list %> - <%= deferred_due_date %> - <%= project_and_context_links( parent_container_type, :suppress_context => suppress_context, :suppress_project => suppress_project ) %> + <%= image_tag_for_recurring_todo(todo) if todo.from_recurring_todo? %> + <%= tag_list(todo) %> + <%= deferred_due_date(todo) %> + <%= project_and_context_links( todo, parent_container_type, :suppress_context => suppress_context, :suppress_project => suppress_project ) %> <%= collapsed_notes_image(todo) if todo.notes? %> <%= collapsed_successors_image(todo) unless todo.pending_successors.empty? %>
    diff --git a/app/views/todos/add_predecessor.js.erb b/app/views/todos/add_predecessor.js.erb index aaa5f45a..25517fbc 100644 --- a/app/views/todos/add_predecessor.js.erb +++ b/app/views/todos/add_predecessor.js.erb @@ -19,7 +19,7 @@ function replace_updated_predecessor() { function show_in_tickler_box() { $("#tickler-empty-nd").hide(); - $('#tickler').html( html_for_deferred_todos() ); + $('#tickler').append( html_for_deferred_todo() ); } 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 })) %>"; } -function html_for_deferred_todos() { - return "<%= escape_javascript(render(:partial => 'todos/deferred', :locals => { :deferred => @todo.project.deferred_todos, - :collapsible => false, - :append_descriptor => t('todos.append_in_this_project'), - :parent_container_type => 'project', - :pending => @todo.project.pending_todos })) %>"; +function html_for_deferred_todo() { + return "<%= escape_javascript(render(:partial => @todo, :locals => { :parent_container_type => parent_container_type })) %>"; } <% end # if !@saved diff --git a/app/views/todos/remove_predecessor.js.erb b/app/views/todos/remove_predecessor.js.erb index 5b6ff3bb..85ea8fa5 100644 --- a/app/views/todos/remove_predecessor.js.erb +++ b/app/views/todos/remove_predecessor.js.erb @@ -1,35 +1,63 @@ -if @removed - status_message = t('todos.removed_predecessor', :successor => @successor.description, :predecessor => @predecessor.description) - page.notify :notice, status_message, 5.0 +<% # TODO: lots of overlap with add_predecessor --> helpers? + if @removed -%> + TracksPages.page_notify('notice', "<%= t('todos.removed_predecessor', :successor => @successor.description, :predecessor => @predecessor.description) %>", 8); - # 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 } + replace_updated_predecessor(); + regenerate_predecessor_family(); + 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 until parents.empty? parent = parents.pop - parents += parent.predecessors - page[parent].replace_html :partial => 'todos/todo', :locals => { :todo => parent, :parent_container_type => parent_container_type } + parents += parent.predecessors -%> + $('#<%= dom_id(parent) %>').html("<%= escape_javascript(render(:partial => parent, :locals => { :parent_container_type => parent_container_type })) %>"); +<%end -%> +} + +function update_successor() { + <% + if @successor.active? -%> + <%= "remove_successor();" unless source_view_is_one_of(:todo, :context) %> + <%= "hide_empty_message();" unless empty_container_msg_div_id.nil? %> + <%= "show_empty_deferred_message(); " if @remaining_deferred_or_pending_count == 0 %> + <% if source_view_is_one_of(:todo, :deferred, :tag) -%> + $('#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 - - # update display if pending->active - if @successor.active? - page[@successor].remove unless source_view_is_one_of(:todo, :context) - page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? - page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@successor.context_id}" - 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, t('todos.error_removing_dependency'), 8.0 -end + %> +} + +function hide_empty_message() { + $('#<%=empty_container_msg_div_id%>').hide(); +} + +function show_empty_deferred_message() { + $('#tickler-empty-nd').slideDown(100); +} +function remove_successor() { + <% # 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 })) : "" %>"; +} \ No newline at end of file diff --git a/app/views/todos/update.js.erb b/app/views/todos/update.js.erb index 2c638cef..f7813960 100644 --- a/app/views/todos/update.js.erb +++ b/app/views/todos/update.js.erb @@ -16,6 +16,7 @@ animation << "hide_context" if update_needs_to_hide_context animation << "highlight_updated_todo" animation << "update_empty_tag_container" if source_view_is(:tag) + animation << "update_predecessors" %> <%= render_animation(animation) %> @@ -35,7 +36,7 @@ function add_to_existing_container(next_steps) { $('#<%= item_container_id(@todo) %>').append(html_for_todo()); <% if source_view_is_one_of(:project,:calendar) -%> 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); <% end -%> <% else -%> @@ -109,4 +110,9 @@ function update_predecessors() { $('#<%=dom_id(p)%>').html('<%=escape_javascript(render(:partial => p, :locals => { :parent_container_type => parent_container_type }))%>'); } <% 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 -%> } \ No newline at end of file diff --git a/app/views/users/destroy.js.rjs b/app/views/users/destroy.js.erb similarity index 100% rename from app/views/users/destroy.js.rjs rename to app/views/users/destroy.js.erb diff --git a/features/dependencies.feature b/features/dependencies.feature index 78ce5696..1cef6d98 100644 --- a/features/dependencies.feature +++ b/features/dependencies.feature @@ -9,21 +9,21 @@ Feature: dependencies | testuser | secret | false | And I have logged in as "testuser" with password "secret" - @selenium @wip - Scenario: Adding dependency to dependency + @selenium + Scenario: Adding dependency to dependency by drag and drop Given I have a project "dependencies" with 3 todos And "Todo 2" depends on "Todo 1" When I visit the "dependencies" project And I drag "Todo 3" to "Todo 2" - Then the dependencies of "Todo 2" should include "Todo 1" - And the dependencies of "Todo 3" should include "Todo 2" + Then the successors of "Todo 1" should include "Todo 2" + And the successors of "Todo 2" should include "Todo 3" When I expand 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" When I expand 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 Given I have a context called "@pc" And I have a project "dependencies" that has the following todos @@ -32,9 +32,49 @@ Feature: dependencies | test me | @pc | When I visit the "dependencies" project And I drag "test me" to "test,1, 2,3" - Then the dependencies of "test me" should include "test,1, 2,3" - When I edit the dependency of "test me" to '"test,1, 2,3" <"@pc"; "dependencies">,"test,1, 2,3" <"@pc"; "dependencies">' + 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'>" Then there should not be an error Scenario: Deleting a predecessor will activate successors 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 \ No newline at end of file diff --git a/features/step_definitions/todo_steps.rb b/features/step_definitions/todo_steps.rb index 557da9b3..04e39fc2 100644 --- a/features/step_definitions/todo_steps.rb +++ b/features/step_definitions/todo_steps.rb @@ -162,14 +162,15 @@ When /^I submit the new multiple actions form with$/ do |multi_line_descriptions submit_multiple_next_action_form 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.should_not be_nil # click edit 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 - # 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 Then /^I should see ([0-9]+) todos$/ do |count| @@ -179,11 +180,12 @@ Then /^I should see ([0-9]+) todos$/ do |count| end 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 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.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) 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| selenium.is_element_present("//span[.=\"#{todo_description}\"]").should be_true end @@ -236,4 +249,22 @@ end 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?" -end \ No newline at end of file +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 diff --git a/features/support/world.rb b/features/support/world.rb index f3da0aa1..1ecc5a26 100644 --- a/features/support/world.rb +++ b/features/support/world.rb @@ -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) 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) # copy-and-past from ApplicationController::format_date return date ? date.in_time_zone(@current_user.prefs.time_zone).strftime("#{@current_user.prefs.date_format}") : '' diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 4ab33189..76ce56be 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -338,7 +338,7 @@ var TodoItems = { /* set behavior for toggle checkboxes for Recurring Todos */ $(".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; }); @@ -362,14 +362,23 @@ var TodoItems = { return false; }); + // defer a todo $(".item-container a.icon_defer_item").live('click', function(ev){ if ($(this).attr("x_defer_alert") == "true") alert ($(this).attr("x_defer_date_after_due_date")); else - put_with_ajax_and_block_element(this.href, $(this)); + put_with_ajax_and_block_element(this.href, $(this).parents(".item-container")); 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, multipleSeparator:',' */ - $('input[name=predecessor_list]:not(.ac_input)').autocomplete({ - source: relative_to_root('auto_complete_for_predecessor') + $('input[name=predecessor_list]:not(.ac_input)') + .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() */