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
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

View file

@ -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)

View file

@ -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?
"<span class=\"grey\">#{format_date( @todo.completed_at )}</span>"
elsif @todo.pending?
"<a title='#{t('todos.depends_on')}: #{@todo.uncompleted_predecessors.map(&:description).join(', ')}'><span class=\"orange\">#{t('todos.pending')}</span></a> "
elsif @todo.deferred?
show_date( @todo.show_from )
def date_span(todo=@todo)
if todo.completed?
"<span class=\"grey\">#{format_date( todo.completed_at )}</span>"
elsif todo.pending?
"<a title='#{t('todos.depends_on')}: #{todo.uncompleted_predecessors.map(&:description).join(', ')}'><span class=\"orange\">#{t('todos.pending')}</span></a> "
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| "<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>"
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|
"<span class=\"tag\">" +
@ -130,30 +138,30 @@ module TodosHelper
if tag_list.empty? then "" else "<span class=\"tags\">#{tag_list}</span>" 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

View file

@ -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 <context, project>
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
@ -168,10 +170,14 @@ class Todo < ActiveRecord::Base
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|
@ -185,6 +191,10 @@ class Todo < ActiveRecord::Base
end
end
def removed_predecessors
return @removed_predecessors
end
def remove_predecessor(predecessor)
# remove predecessor and activate myself
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">
<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"),
{:url => {:controller => 'todos', :action => 'remove_predecessor', :id => successor.id},
:method => 'delete',
@ -19,7 +21,17 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
: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? %>
<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>

View file

@ -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'
%>
<div id="<%= dom_id(todo) %>" class="item-container">
<div id="<%= dom_id(todo, 'line') %>" class="item-show">
<%= 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 %>
<ul class="sf-menu sf-item-menu">
<li style="z-index:<%=@z_index_counter%>"><%= image_tag "downarrow.png", :alt=> "" %>
<ul id="ul<%= dom_id(todo) %>">
@ -25,13 +24,13 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
</li>
</ul>
<div class="description<%= staleness_class( todo ) %>">
<%= grip_span %>
<%= date_span -%>
<%= grip_span(todo) %>
<%= date_span(todo) -%>
<span class="todo.descr"><%= h todo.description %></span>
<%= 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? %>
</div>

View file

@ -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

View file

@ -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 }
end
parents += parent.predecessors -%>
$('#<%= dom_id(parent) %>').html("<%= escape_javascript(render(:partial => parent, :locals => { :parent_container_type => parent_container_type })) %>");
<%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'"}
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->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 })) : "" %>";
}

View file

@ -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 -%>
}

View file

@ -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

View file

@ -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
@ -237,3 +250,21 @@ 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
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)
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}") : ''

View file

@ -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() */