diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 12f5454a..3e75ee06 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -324,122 +324,33 @@ class TodosController < ApplicationController def update @source_view = params['_source_view'] || 'todo' init_data_for_sidebar unless mobile? - if params[:tag_list] - @todo.tag_with(params[:tag_list]) - @todo.tags(true) #force a reload for proper rendering - end - @original_item_context_id = @todo.context_id - @original_item_project_id = @todo.project_id - @original_item_was_deferred = @todo.deferred? - @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(', ') - - if params['todo']['project_id'].blank? && !params['project_name'].nil? - if params['project_name'] == 'None' - project = Project.null_object - else - project = current_user.projects.find_by_name(params['project_name'].strip) - unless project - project = current_user.projects.build - project.name = params['project_name'].strip - project.save - @new_project_created = true - end - end - params["todo"]["project_id"] = project.id - end - - if params['todo']['context_id'].blank? && !params['context_name'].blank? - context = current_user.contexts.find_by_name(params['context_name'].strip) - unless context - context = current_user.contexts.build - context.name = params['context_name'].strip - context.save - @new_context_created = true - @not_done_todos = [@todo] - end - params["todo"]["context_id"] = context.id - end - - if params["todo"].has_key?("due") - params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"]) - else - params["todo"]["due"] = "" - end - - if params['todo']['show_from'] - params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from']) - end - - if params['done'] == '1' && !@todo.completed? - @todo.complete! - @todo.pending_to_activate.each do |t| - t.activate! - end - end - # strange. if checkbox is not checked, there is no 'done' in params. - # Therefore I've used the negation - if !(params['done'] == '1') && @todo.completed? - @todo.activate! - @todo.active_to_block.each do |t| - t.block! - end - end + + cache_attributes_from_before_update + + update_tags + update_project if params['todo']['project_id'].blank? && !params['project_name'].nil? + update_context if params['todo']['context_id'].blank? && !params['context_name'].blank? + update_due_and_show_from_dates + update_completed_state @todo.attributes = params["todo"] + @saved = @todo.save @todo.add_predecessor_list(params[:predecessor_list]) - @saved = @todo.save - if @saved && params[:predecessor_list] - if @original_item_predecessor_list != params[:predecessor_list] - # Possible state change with new dependencies - if @todo.uncompleted_predecessors.empty? - if @todo.state == 'pending' - @todo.activate! # Activate pending if no uncompleted predecessors - end - else - if @todo.state == 'active' - @todo.block! # Block active if we got uncompleted predecessors - end - end - end - @todo.save! - end + update_pending_state if @saved && params[:predecessor_list] - @context_changed = @original_item_context_id != @todo.context_id - @todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active? - - if source_view_is :calendar - @due_date_changed = @original_item_due != @todo.due - if @due_date_changed - @old_due_empty = is_old_due_empty(@original_item_due_id) - if @todo.due.nil? - # do not act further on date change when date is changed to nil - @due_date_changed = false - else - @new_due_id = get_due_id_for_calendar(@todo.due) - end - end - end - - if @context_changed - determine_remaining_in_context_count(@original_item_context_id) - else - determine_remaining_in_context_count(@todo.context_id) - end - - @project_changed = @original_item_project_id != @todo.project_id - if (@project_changed && !@original_item_project_id.nil?) then - @todo.update_state_from_project - @todo.save! - @remaining_undone_in_project = current_user.projects.find(@original_item_project_id).not_done_todos.count - end + determine_changes_by_this_update + determine_remaining_in_context_count(@context_changed ? @original_item_context_id : @todo.context_id) + update_todo_state_if_project_changed determine_down_count determine_deferred_tag_count(params['_tag_name']) if @source_view == 'tag' respond_to do |format| - format.js + format.js { + @status_message = @todo.deferred? ? t('todos.action_saved_to_tickler') : t('todos.action_saved') + @status_message = t('todos.added_new_project') + ' / ' + @status_message if @new_project_created + @status_message = t('todos.added_new_context') + ' / ' + @status_message if @new_context_created + } format.xml { render :xml => @todo.to_xml( :except => :user_id ) } format.m do if @saved @@ -899,13 +810,12 @@ class TodosController < ApplicationController def determine_down_count source_view do |from| from.todo do - @down_count = current_user.todos.count( + @down_count = current_user.todos.active.count( :all, - :conditions => ['todos.state = ? and contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', 'active', false, 'active'], - :include => [ :project, :context ]) + :conditions => ['contexts.hide = ? AND (projects.state = ? OR todos.project_id IS NULL)', false, 'active'], :include => [:project,:context]) end from.context do - @down_count = current_user.contexts.find(@todo.context_id).not_done_todo_count + @down_count = current_user.contexts.find(@todo.context_id).todos.not_completed.count(:all) end from.project do unless @todo.project_id == nil @@ -915,7 +825,7 @@ class TodosController < ApplicationController end end from.deferred do - @down_count = current_user.todos.count_in_state(:deferred) + @down_count = current_user.todos.deferred_or_blocked.count(:all) end from.tag do @tag_name = params['_tag_name'] @@ -932,7 +842,11 @@ class TodosController < ApplicationController def determine_remaining_in_context_count(context_id = @todo.context_id) source_view do |from| - from.deferred { @remaining_in_context = current_user.contexts.find(context_id).deferred_todo_count } + from.deferred { + # force reload to todos to get correct count and not a cached one + @remaining_in_context = current_user.contexts.find(context_id).todos(true).deferred_or_blocked.count(:all) + @target_context_count = current_user.contexts.find(@todo.context_id).todos(true).deferred_or_blocked.count(:all) + } from.tag { tag = Tag.find_by_name(params['_tag_name']) if tag.nil? @@ -1226,6 +1140,116 @@ class TodosController < ApplicationController private + def cache_attributes_from_before_update + @original_item_context_id = @todo.context_id + @original_item_project_id = @todo.project_id + @original_item_was_deferred = @todo.deferred? + @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(', ') + end + + def update_project + if params['project_name'] == 'None' + project = Project.null_object + else + project = current_user.projects.find_by_name(params['project_name'].strip) + unless project + project = current_user.projects.build + project.name = params['project_name'].strip + project.save + @new_project_created = true + end + end + params["todo"]["project_id"] = project.id + end + + def update_todo_state_if_project_changed + if (@project_changed && !@original_item_project_id.nil?) then + @todo.update_state_from_project + @todo.save! + @remaining_undone_in_project = current_user.projects.find(@original_item_project_id).not_done_todos.count + end + end + + def update_context + context = current_user.contexts.find_by_name(params['context_name'].strip) + unless context + @new_context = current_user.contexts.build + @new_context.name = params['context_name'].strip + @new_context.save + @new_context_created = true + @not_done_todos = [@todo] + context = @new_context + end + params["todo"]["context_id"] = context.id + end + + def update_tags + if params[:tag_list] + @todo.tag_with(params[:tag_list]) + @todo.tags(true) #force a reload for proper rendering + end + end + + def update_due_and_show_from_dates + if params["todo"].has_key?("due") + params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"]) + else + params["todo"]["due"] = "" + end + if params['todo']['show_from'] + params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from']) + end + end + + def update_completed_state + if params['done'] == '1' && !@todo.completed? + @todo.complete! + @todo.pending_to_activate.each do |t| + t.activate! + end + end + # strange. if checkbox is not checked, there is no 'done' in params. + # Therefore I've used the negation + if !(params['done'] == '1') && @todo.completed? + @todo.activate! + @todo.active_to_block.each do |t| + t.block! + end + end + end + + def update_pending_state + if @original_item_predecessor_list != params[:predecessor_list] + # Possible state change with new dependencies + if @todo.uncompleted_predecessors.empty? + if @todo.state == 'pending' + @todo.activate! # Activate pending if no uncompleted predecessors + end + else + if @todo.state == 'active' + @todo.block! # Block active if we got uncompleted predecessors + end + end + end + @todo.save! + end + + def determine_changes_by_this_update + @context_changed = @original_item_context_id != @todo.context_id + @todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active? + @todo_was_deferred = !@original_item_was_deferred && @todo.deferred? + + if source_view_is :calendar + @due_date_changed = ((@original_item_due != @todo.due) && @todo.due) || !@todo.due + @old_due_empty = is_old_due_empty(@original_item_due_id) + @new_due_id = get_due_id_for_calendar(@todo.due) if @due_date_changed + end + + @project_changed = @original_item_project_id != @todo.project_id + end + def project_specified_by_name(project_name) return false unless params['project_id'].blank? return false if project_name.blank? diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb index cb403092..1be6ae13 100644 --- a/app/helpers/todos_helper.rb +++ b/app/helpers/todos_helper.rb @@ -1,23 +1,5 @@ module TodosHelper - # #require 'users_controller' Counts the number of incomplete items in the - # specified context - # - def count_items(context) - count = Todo.find_all("done=0 AND context_id=#{context.id}").length - end - - def form_remote_tag_edit_todo( &block ) - form_remote_tag( - :url => todo_path(@todo), - :loading => "$('#submit_todo_#{@todo.id}').block({message: null})", - :html => { - :method => :put, - :id => dom_id(@todo, 'form'), - :class => dom_id(@todo, 'form') + " inline-form edit_todo_form" }, - &block ) - end - def remote_star_icon link_to( image_tag_for_star(@todo), toggle_star_todo_path(@todo), @@ -68,7 +50,8 @@ module TodosHelper return link_to(image_tag("to_project_off.png", :align => "absmiddle")+" " + t('todos.convert_to_project'), url) end - + + # waiting stuff can be deleted after migration of defer def todo_start_waiting_js(todo) return "$('#ul#{dom_id(todo)}').css('visibility', 'hidden'); $('##{dom_id(todo)}').block({message: null})" end @@ -87,7 +70,6 @@ module TodosHelper {:controller => "recurring_todos", :action => "index"}, :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', @@ -224,9 +206,10 @@ module TodosHelper end def item_container_id (todo) - return "c#{todo.context_id}items" if source_view_is :tickler + return "c#{todo.context_id}items" if source_view_is :deferred return "tickleritems" if todo.deferred? or todo.pending? return "p#{todo.project_id}items" if source_view_is :project + return @new_due_id if source_view_is :calendar return "c#{todo.context_id}items" end @@ -262,7 +245,7 @@ module TodosHelper def empty_container_msg_div_id todo = @todo || @successor return "" unless todo # empty id if no todo or successor given - return "tickler-empty-nd" if source_view_is_one_of(:project, :tag, :deferred) && todo.deferred? + return "tickler-empty-nd" if source_view_is_one_of(:project, :tag) && todo.deferred? return "p#{todo.project_id}empty-nd" if source_view_is :project return "c#{todo.context_id}empty-nd" end @@ -299,7 +282,56 @@ module TodosHelper def date_field_tag(name, id, value = nil, options = {}) text_field_tag name, value, {"size" => 12, "id" => id, "class" => "Date", "onfocus" => "Calendar.setup", "autocomplete" => "off"}.update(options.stringify_keys) end - + + def update_needs_to_hide_context + return (@remaining_in_context == 0) && !source_view_is(:context) + end + + def update_needs_to_remove_todo_from_container + source_view do |page| + page.context { return @context_changed || @todo.deferred? || @todo.pending?} + page.project { return updated_todo_changed_deferred_state } + page.deferred { return @context_changed || !(@todo.deferred? || @todo.pending?) } + page.calendar { return @due_date_changed || !@todo.due } + end + return false + end + + def replace_with_updated_todo + source_view do |page| + page.context { return !update_needs_to_remove_todo_from_container } + page.project { return !updated_todo_changed_deferred_state} + page.deferred { return !@context_changed && (@todo.deferred? || @todo.pending?) } + page.calendar { return !@due_date_changed && @todo.due } + end + end + + def append_updated_todo + source_view do |page| + page.context { return false } + page.project { return updated_todo_changed_deferred_state } + page.deferred { return @context_changed && (@todo.deferred? || @todo.pending?) } + page.calendar { return @due_date_changed && @todo.due } + end + return false + end + + def updated_todo_changed_deferred_state + return (@todo.deferred? && !@original_item_was_deferred) || @todo_was_activated_from_deferred_state + end + + def render_animation(animation) + html = "" + animation.each do |step| + puts "step='#{step}'" + unless step.blank? + html += step + "({ go: function() {\r\n" + end + end + html += "}})" * animation.count + return html + end + private def image_tag_for_star(todo) diff --git a/app/models/todo.rb b/app/models/todo.rb index 6fe3e5ae..d53fb840 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -18,10 +18,11 @@ class Todo < ActiveRecord::Base named_scope :active, :conditions => { :state => 'active' } named_scope :not_completed, :conditions => ['NOT (todos.state = ? )', 'completed'] - named_scope :completed, :conditions => ["NOT completed_at IS NULL"] + named_scope :completed, :conditions => ["NOT todos.completed_at IS NULL"] named_scope :are_due, :conditions => ['NOT (todos.due IS NULL)'] - named_scope :deferred, :conditions => ["completed_at IS NULL AND NOT show_from IS NULL"] + named_scope :deferred, :conditions => ["todos.completed_at IS NULL AND NOT todos.show_from IS NULL"] named_scope :blocked, :conditions => ['todos.state = ?', 'pending'] + named_scope :deferred_or_blocked, :conditions => ["(todos.completed_at IS NULL AND NOT todos.show_from IS NULL) OR (todos.state = ?)", "pending"] STARRED_TAG_NAME = "starred" diff --git a/app/views/projects/create.js.erb b/app/views/projects/create.js.erb index f8a339f2..a00f52c0 100644 --- a/app/views/projects/create.js.erb +++ b/app/views/projects/create.js.erb @@ -8,6 +8,7 @@ update_active_projects_container(); add_project(); clear_form(); + TracksPages.page_notify('notice', "Created new project '<%= @project.name%>'", 5); <% end -%> <% else -%> TracksPages.show_errors(html_for_error_messages()); diff --git a/app/views/projects/update.js.erb b/app/views/projects/update.js.erb index 5c0f2298..d8e2913c 100644 --- a/app/views/projects/update.js.erb +++ b/app/views/projects/update.js.erb @@ -1,42 +1,40 @@ <% if @saved -%> - TracksPages.page_notify('notice', '<%=t('projects.project_saved_status')%>', 5); - <% if source_view_is :project_list -%> - - <% if @state_changed -%> - remove_and_re_add_project(); - <% else -%> - replace_project_form_with_updated_project(); - <% end -%> - - ProjectListPage.update_all_states_count(<%=@active_projects_count%>, <%=@hidden_projects_count%>, <%=@completed_projects_count%>); - ProjectListPage.show_or_hide_all_state_containers(<%= @show_active_projects %>, <%= @show_hidden_projects %>, <%= @show_completed_projects %>); - + update_project_list_page(); <% else # assume source_view :project -%> - - remove_project_edit_form(); - update_and_show_project_settings(); - - TracksForm.set_project_name("<%= escape_javascript(@project.name)%>"); - <% if @project.default_context %> - TracksForm.set_context_name_and_default_context_name("<%= escape_javascript(@project.default_context.name)%>"); - <% end %> - <% if @project.default_tags %> - TracksForm.set_tag_list("<%= escape_javascript(@project.default_tags)%>"); - <% end %> - - TracksPages.update_sidebar(html_for_sidebar()); - + update_project_page(); <% end %> - TracksForm.set_project_name_and_default_project_name("<%= escape_javascript(@project.name)%>"); - <% else -%> TracksPages.show_edit_errors(html_for_error_messages()); <% end %> +function update_project_list_page() { + <% if @state_changed -%> + remove_and_re_add_project(); + <% else -%> + replace_project_form_with_updated_project(); + <% end -%> + + ProjectListPage.update_all_states_count(<%=@active_projects_count%>, <%=@hidden_projects_count%>, <%=@completed_projects_count%>); + ProjectListPage.show_or_hide_all_state_containers(<%= @show_active_projects %>, <%= @show_hidden_projects %>, <%= @show_completed_projects %>); +} + +function update_project_page() { + remove_project_edit_form(); + update_and_show_project_settings(); + TracksForm.set_project_name("<%= escape_javascript(@project.name)%>"); + <% if @project.default_context %> + TracksForm.set_context_name_and_default_context_name("<%= escape_javascript(@project.default_context.name)%>"); + <% end %> + <% if @project.default_tags %> + TracksForm.set_tag_list("<%= escape_javascript(@project.default_tags)%>"); + <% end %> + TracksPages.update_sidebar(html_for_sidebar()); +} + function remove_project_edit_form() { $('#<%=dom_id(@project, 'edit')%>').hide(500, function() {$('#<%=dom_id(@project, 'edit')%>').remove();} ); } diff --git a/app/views/todos/_edit_form.rhtml b/app/views/todos/_edit_form.rhtml index 2c2eabff..89d71ea7 100644 --- a/app/views/todos/_edit_form.rhtml +++ b/app/views/todos/_edit_form.rhtml @@ -1,60 +1,61 @@ -
<%= error_messages_for("todo", :object_name => 'action') %>
+<% +todo = edit_form +form_for(todo, :html=> { :name=>'todo', :id => dom_id(@todo, 'form'), :class => 'inline-form edit_todo_form' }) do |t|%> +
<%= error_messages_for("todo", :object_name => 'action') %>
-<%= hidden_field( "todo", "id" ) -%> -<%= source_view_tag( @source_view ) -%> -<%= "" unless @tag_name.nil? -%> + <%= t.hidden_field( "id" ) -%> + <%= source_view_tag( @source_view ) -%> + <%= content_tag(:input, "", :type=>"hidden", :name=>"_tag_name", :value=>"#{@tag_name}") if @tag_name -%> - -<%= text_field( "todo", "description", "size" => 30, "tabindex" => 8, "maxlength" => 100) %> + + <%= t.text_field( "description", "size" => 30, "tabindex" => 8, "maxlength" => 100) %> - -<%= text_area( "todo", "notes", "cols" => 29, "rows" => 4, "tabindex" => 9) %> + + <%= t.text_area( "notes", "cols" => 29, "rows" => 4, "tabindex" => 9) %> -
- - " /> -
+
+ + " /> +
-
- - -
+
+ + +
- -<%= text_field_tag 'tag_list', tag_list_text, :id => dom_id(@todo, 'tag_list'), :size => 30, :tabindex => 12 %> + + <%= text_field_tag 'tag_list', tag_list_text, :id => dom_id(@todo, 'tag_list'), :size => 30, :tabindex => 12 %> -
- - <%= date_field_tag("todo[due]", dom_id(@todo, 'due'), format_date(@todo.due), "tabindex" => 13) %> - - <%= image_tag("delete_off.png", :alt => t('todos.clear_due_date') ) %> - -
- -
- - <%= date_field_tag("todo[show_from]", dom_id(@todo, 'show_from'), format_date(@todo.show_from), "tabindex" => 14) %> - - <%= image_tag("delete_off.png", :alt => t('todos.clear_show_from_date') ) %> - -
- - -<%= text_field_tag 'predecessor_list', predecessor_list_text, :id => dom_id(@todo, 'predecessor_list'), :size => 30, :tabindex => 15 %> - -<% if controller.controller_name == "project" || @todo.deferred? -%> - -<% end -%> - -
-
- - - <%= image_tag("cancel.png", :alt => "") %> - <%= t('common.cancel') %> + -
+ +
+ + <%= date_field_tag("todo[show_from]", dom_id(@todo, 'show_from'), format_date(@todo.show_from), "tabindex" => 14) %> + + <%= image_tag("delete_off.png", :alt => "Clear show from date") %> + +
+ + + <%= text_field_tag 'predecessor_list', predecessor_list_text, :id => dom_id(@todo, 'predecessor_list'), :size => 30, :tabindex => 15 %> + +
+
+ + + <%=image_tag("cancel.png", :alt => "") %> + Cancel + +
+
+ +<% end %> diff --git a/app/views/todos/_todo.html.erb b/app/views/todos/_todo.html.erb index 1600ec91..cade8e29 100644 --- a/app/views/todos/_todo.html.erb +++ b/app/views/todos/_todo.html.erb @@ -37,9 +37,7 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
\ No newline at end of file diff --git a/app/views/todos/check_deferred.js.erb b/app/views/todos/check_deferred.js.erb index f5cef365..ecd8bed6 100644 --- a/app/views/todos/check_deferred.js.erb +++ b/app/views/todos/check_deferred.js.erb @@ -1,4 +1,3 @@ -unless @due_tickles.empty? - #TODO: why not just add the new items here in addition to notifying? - page.notify :notice, t('todos.tickler_items_due', :count => @due_tickles.length), 5.0 -end \ No newline at end of file +<% unless @due_tickles.empty? -%> + TracksPages.page_notify('notice', "<%=t('todos.tickler_items_due', :count => @due_tickles.length)%>", 5); +<% end -%> diff --git a/app/views/todos/create.js.erb b/app/views/todos/create.js.erb index f42079d0..fab9c6b5 100644 --- a/app/views/todos/create.js.erb +++ b/app/views/todos/create.js.erb @@ -40,6 +40,7 @@ function insert_new_context_with_new_todo() { function add_todo_to_existing_context() { <% if source_view_is_one_of(:todo, :deferred, :tag) -%> TodoItemsContainer.ensureVisibleWithEffectAppear("c<%=@todo.context_id%>"); + $('#<%=empty_container_msg_div_id%>').hide(); <% end -%> $('#<%=item_container_id(@todo)%>').append(html_for_new_todo()); $('#<%= dom_id(@todo)%>').effect('highlight', {}, 2000 ); diff --git a/app/views/todos/edit.js.erb b/app/views/todos/edit.js.erb index 55c57c34..a836ca32 100644 --- a/app/views/todos/edit.js.erb +++ b/app/views/todos/edit.js.erb @@ -1,5 +1,17 @@ -page[dom_id(@todo, 'form')].find('.placeholder').show().replace_html :partial => 'todos/edit_form' -page[dom_id(@todo, 'edit')].show -page[dom_id(@todo, 'line')].hide -page[dom_id(@todo, 'form')].find('input#todo_description').show().focus -page << "enable_rich_interaction();" \ No newline at end of file +hide_todo(); +replace_placeholder_with_form(); +enable_rich_interaction(); + +function hide_todo() { + $('#<%= dom_id(@todo, 'line') %>').hide(); +} + +function replace_placeholder_with_form() { + $('#<%=dom_id(@todo, 'edit')%>').html(html_for_edit_form()); + $('#<%=dom_id(@todo, 'edit')%>').show(); + $('#<%=dom_id(@todo, 'form')%> input#todo_description').focus(); +} + +function html_for_edit_form() { + return "<%= escape_javascript(render(:partial => 'todos/edit_form', :object => @todo)) %>" +} \ No newline at end of file diff --git a/app/views/todos/update.js.erb b/app/views/todos/update.js.erb index e6d53500..5970d323 100644 --- a/app/views/todos/update.js.erb +++ b/app/views/todos/update.js.erb @@ -1,164 +1,136 @@ -if @saved - # show update message - status_message = @todo.deferred? ? t('todos.action_saved_to_tickler') : t('todos.action_saved') - status_message = t('todos.added_new_project') + ' / ' + status_message if @new_project_created - status_message = t('todos.added_new_context') + ' / ' + status_message if @new_context_created - status_message = @message || status_message - page.notify :notice, status_message, 5.0 - - if source_view_is_one_of(:todo, :context, :tag) - if @context_changed || @todo.deferred? || @todo.pending? - page[@todo].remove - - if (@remaining_in_context == 0) - # remove context container from page if empty - if @context_changed - source_view do |from| - from.todo { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 } - from.tag { page.visual_effect :fade, "c#{@original_item_context_id}", :duration => 0.4 } - from.context { page.show "c#{@original_item_context_id}empty-nd" } - end - else - source_view do |from| - from.todo { page.visual_effect :fade, "c#{@todo.context.id}", :duration => 0.4 } - from.tag { page.visual_effect :fade, "c#{@todo.context.id}", :duration => 0.4 } - from.context { page.show "c#{@original_item_context_id}empty-nd" } - end - end - end +<% if !@saved -%> + TracksPages.show_edit_errors(html_for_error_messages()); +<% else - if source_view_is_one_of(:todo, :tag) && @todo.active? - page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@todo.context_id}" - page.call "todoItems.expandNextActionListingByContext", "c#{@todo.context_id}items", true - page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? - # show all todos in context - page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - end + # jquery animations are async, so first collect all animation steps, + # then execute them sequential. All steps are functions which are passed a function + # with the next animation steps - if source_view_is(:tag) && @todo.deferred? - # show todo in deferred container - page.insert_html :bottom, "tickleritems", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? - end - - # update badge count - page.replace_html("badge_count", @remaining_in_context) if source_view_is :context - page.replace_html("badge_count", @down_count) if source_view_is :todo - - # show todo in context - page.delay(0.3) do - page.call "todoItems.ensureContainerHeight", "c#{@original_item_context_id}items" - if source_view_is_one_of(:todo, :tag) && @todo.active? - page.call "todoItems.ensureContainerHeight", "c#{@todo.context_id}items" - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - end - if @context_changed - source_view do |from| - from.todo {page << "$('#c#{@todo.context_id} h2').effect('highlight', {}, 3000)" } - from.tag {page << "$('#c#{@todo.context_id} h2').effect('highlight')" } - end - end - end - else - if @original_item_was_deferred && source_view_is(:tag) - # we go from the deferred container to a context container in tag view - page[@todo].remove - page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@todo.context_id}" - page.call "todoItems.expandNextActionListingByContext", "c#{@todo.context_id}items", true - page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? - # show all todos in context - page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - page['tickler-empty-nd'].show if @deferred_tag_count == 0 - else - page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - end - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - end - elsif source_view_is :project - if @project_changed - page[@todo].remove - page.show("p#{@original_item_project_id}empty-nd") if (@remaining_undone_in_project == 0) - page.replace_html "badge_count", @remaining_undone_in_project - elsif @todo.deferred? - page[@todo].remove - page.show("p#{@original_item_project_id}empty-nd") if (@remaining_undone_in_project == 0) - page.insert_html :bottom, "tickler", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - page['tickler-empty-nd'].hide - page.replace_html "badge_count", @down_count - elsif @todo_was_activated_from_deferred_state - page[@todo].remove - page['tickler-empty-nd'].show if (@deferred_count == 0) - page.insert_html :bottom, "p#{@todo.project_id}items", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - page["p#{@todo.project_id}empty-nd"].hide - page.replace_html "badge_count", @down_count - else - page.replace_html "p#{@todo.project_id}items", :partial => 'todos/todo', :collection => @todo.project.not_done_todos, - :locals => { :parent_container_type => parent_container_type } - page.replace "tickler", :partial => 'todos/deferred', :locals => { - :deferred => @todo.project.deferred_todos, - :collapsible => false, - :append_descriptor => "in this project", - :parent_container_type => 'project', - :pending => @todo.project.pending_todos } - page['tickler-empty-nd'].show if (@deferred_count == 0 and @pending_count == 0) - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - end - elsif source_view_is :deferred - if !@todo.deferred? - page[@todo].remove - page.show(empty_container_msg_div_id) if (@down_count == 0) - page.replace_html "badge_count", @down_count - elsif @context_changed - page[@todo].remove - page.visual_effect(:fade, "c#{@original_item_context_id}", :duration => 0.4) if (@remaining_in_context == 0) - page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@todo.context_id}" - page.call "todoItems.expandNextActionListingByContext", "c#{@todo.context_id}items", true - page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? - page.insert_html :bottom, "c#{@todo.context_id}items", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - page.replace_html("badge_count", @down_count) - page.delay(0.5) do - page.call "todoItems.ensureContainerHeight", "c#{@original_item_context_id}items" - page.call "todoItems.ensureContainerHeight", "c#{@todo.context_id}items" - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - end - else - page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - end - elsif source_view_is :stats - page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - elsif source_view_is :calendar - if @due_date_changed - page[@todo].remove - page.show "empty_"+@original_item_due_id if @old_due_empty - page.hide "empty_"+@new_due_id - page.insert_html :bottom, @new_due_id, :partial => 'todos/todo', :locals => {:todo => @todo} - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - else - if @todo.due.nil? - # due date removed - page[@todo].remove - page.show "empty_"+@original_item_due_id if @old_due_empty - else - page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type } - page.visual_effect :highlight, dom_id(@todo), :duration => 3 - end - end - else - logger.error "unexpected source_view '#{params[:_source_view]}'" + animation = [] + animation << "remove_todo" if update_needs_to_remove_todo_from_container + if replace_with_updated_todo + animation << "replace_todo" + elsif append_updated_todo + animation << (@new_context_created ? "insert_new_context_with_updated_todo" : "add_to_existing_container") end - # Update predecessors (if they exist and are visible) - @todo.uncompleted_predecessors.each do |p| - page << "if ($(\'#{item_container_id(p)}\')) {" - page[p].replace_html :partial => 'todos/todo', - :locals => { :todo => p, :parent_container_type => parent_container_type } - page << "}" - end -else - page.show 'error_status' - page.replace_html 'error_status', "#{error_messages_for('todo')}" -end + animation << "hide_context" if update_needs_to_hide_context -page << "enable_rich_interaction();" + # update relevant empty messaged + source_view do |page| + page.context { + animation << "show_empty_message_source_container" if (@remaining_in_context == 0) + } + page.project { + animation << "show_empty_message_project" if (@down_count == 0) + if @todo_was_activated_from_deferred_state + animation << "show_empty_message_tickler" if @deferred_count == 0 + animation << "hide_empty_message_project" + end + animation << "hide_empty_message_tickler" if @todo_was_deferred && (@deferred_count == 1) + } + page.deferred { + animation << "show_empty_message_source_container" if (@remaining_in_context == 0) + animation << "hide_empty_message_target_container" if (@context_changed && @target_context_count==1) + } + page.calendar { + animation << "show_empty_message_source_container" if @old_due_empty + animation << "hide_empty_message_target_container" if @todo.due + } + end + animation << "highlight_updated_todo" +%> + + <%= render_animation(animation) %> + TracksPages.page_notify('notice', '<%=@status_message%>', 5); + update_badge_count(); +<% end %> + +function remove_todo(next_steps) { + $('#<%= dom_id(@todo) %>').fadeOut(400, function() { + $('#<%= dom_id(@todo) %>').remove(); + next_steps.go(); + }); +} + +function replace_todo(next_steps) { + $('#<%= dom_id(@todo) %>').html(html_for_todo()); + next_steps.go(); +} + +function hide_context(next_steps) { + <% context_id = @context_changed ? @original_item_context_id : @todo.context_id -%> + $('#c<%= context_id %>').fadeOut(400, function(){ next_steps.go(); }); +} + +function highlight_updated_todo(next_steps) { + $('#<%= dom_id(@todo)%>').effect('highlight', {}, 2000, function(){ next_steps.go(); }); +} + +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(); + <% else -%> + $('#c<%= @todo.context_id %>').fadeIn(500, function() { next_steps.go(); }); + <% end -%> +} + +function update_badge_count() { + <% + count = source_view_is(:context) ? @remaining_in_context : @down_count + count = @project_changed ? @remaining_undone_in_project : count + -%> + TracksPages.set_page_badge(<%= count %>); +} + +function show_empty_message_source_container(next_steps) { + <% container_id = source_view_is(:calendar) ? "empty_#{@original_item_due_id}" : "c#{@original_item_context_id}empty-nd" -%> + $("#<%= container_id%>").slideDown(100, function() { next_steps.go(); }); +} + +function hide_empty_message_target_container(next_steps) { + <% container_id = source_view_is(:calendar) ? "empty_#{@new_due_id}" : "c#{@todo.context_id}empty-nd" -%> + $("#<%=container_id%>").slideUp(100, function() { next_steps.go(); }); +} + +function show_empty_message_project(next_steps) {<% + id = updated_todo_changed_deferred_state ? "p#{@todo.project_id}empty-nd" : empty_container_msg_div_id %> + $('#<%= id %>').slideDown(100, function() { next_steps.go(); }); +} + +function hide_empty_message_project(next_steps) { + $('#<%= empty_container_msg_div_id %>').slideUp(100, function() { next_steps.go(); }); +} + +function show_empty_message_tickler(next_steps) { + $('#tickler-empty-nd').slideDown(100, function() { next_steps.go(); }); +} + +function hide_empty_message_tickler(next_steps) { + $('#tickler-empty-nd').slideUp(100, function(){ next_steps.go(); }); +} + +function insert_new_context_with_updated_todo(next_steps) { + $('#display_box').prepend(html_for_new_context()); + $('#c<%= @todo.context_id %>').fadeIn(500, function() { next_steps.go(); }); +} + +function html_for_todo() { + return "<%= @saved ? escape_javascript(render(:partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type })) : "" %>"; +} + +function html_for_new_context() { + return "<%= @saved && @new_context_created ? escape_javascript(render(:partial => 'contexts/context', :locals => { :context => @new_context, :collapsible => true })) : "" %>"; +} + +function html_for_error_messages() { + return "<%= escape_javascript(error_messages_for('todo')) %>"; +} + +function update_predecessors() { + <% @todo.uncompleted_predecessors.each do |p| -%> + if ($('#<%=item_container_id(p)%>')) { + $('#<%=dom_id(p)%>').html('<%=escape_javascript(render(:partial => 'todos/todo', :locals => { :todo => p, :parent_container_type => parent_container_type }))%>'); + } + <% end -%> +} \ No newline at end of file diff --git a/features/context_edit.feature b/features/context_edit.feature index bc91e689..cd51bf88 100644 --- a/features/context_edit.feature +++ b/features/context_edit.feature @@ -18,3 +18,12 @@ Feature: Edit a context When I go to the contexts page Then he should see that a context named "Errands" is not present And he should see that a context named "OutAndAbout" is present + + Scenario: Editing the context of a todo will remove the todo + Given this scenario is pending + + Scenario: Editing the description of a a todo will update that todo + Given this scenario is pending + + Scenario: Editing the context of the last todo will remove the todo and show empty message + Given this scenario is pending diff --git a/features/edit_a_todo.feature b/features/edit_a_todo.feature index b0c5a061..40c29f41 100644 --- a/features/edit_a_todo.feature +++ b/features/edit_a_todo.feature @@ -27,6 +27,9 @@ Feature: Edit a next action from every page Scenario: I can mark a completed todo active Given this is a pending scenario + Scenario: I can edit a todo to change its description + Given this is a pending scenario + Scenario: I can edit a todo to move it to another context Given this is a pending scenario @@ -53,3 +56,9 @@ Feature: Edit a next action from every page Scenario: I can edit the tags of a todo Given this is a pending scenario + + Scenario: Editing the context of a todo to a new context will show new context + Given this scenario is pending # for home and tickler and tag + + Scenario: Making an error when editing a todo will show error message + Given this scenario is pending \ No newline at end of file diff --git a/features/project_edit.feature b/features/project_edit.feature index 375c58cf..cdb43ba5 100644 --- a/features/project_edit.feature +++ b/features/project_edit.feature @@ -23,15 +23,6 @@ Feature: Edit a project Then I should see the italic text "successfull outcome" in the project description And I should see the bold text "done" in the project description - # Ticket #1043 - @selenium @wip - Scenario: I can move a todo out of the current project - Given I have a project "foo" with 2 todos - When I visit the "foo" project - And I change the project_name field of "Todo 1" to "bar" - Then I should not see the todo "Todo 1" - And I should see the todo "Todo 2" - @selenium Scenario: I can edit the project name in place Given I have a project "release tracks 1.8" with 1 todos @@ -93,3 +84,21 @@ Feature: Edit a project Scenario: Cancelling editing a project will restore project settings Given this scenario is pending + + Scenario: Editing the description of a todo will update todo + Given this scenario is pending + + Scenario: Moving the todo to the tickler will move todo to tickler container + Given this scenario is pending + + Scenario: Moving the todo out of the tickler will move todo to active container + Given this scenario is pending + + # Ticket #1043 + @selenium @wip + Scenario: I can move a todo out of the current project + Given I have a project "foo" with 2 todos + When I visit the "foo" project + And I change the project_name field of "Todo 1" to "bar" + Then I should not see the todo "Todo 1" + And I should see the todo "Todo 2" diff --git a/features/tickler.feature.feature b/features/tickler.feature.feature new file mode 100644 index 00000000..02121f64 --- /dev/null +++ b/features/tickler.feature.feature @@ -0,0 +1,20 @@ +Feature: Manage deferred todos + In order to hide todos that require attention in the future and not now + As a Tracks user + I want to defer these and manage them in a tickler + + Background: + Given the following user record + | login | password | is_admin | + | testuser | secret | false | + And there exists a project "manage me" for user "testuser" + And I have logged in as "testuser" with password "secret" + + Scenario: Editing the description of a todo updated the todo + Given this scenario is pending + + Scenario: Editing the context of a todo moves it to the new context + Given this scenario is pending + + Scenario: Removing the show from date from a todo removes it from the tickler + Given this scenario is pending diff --git a/public/javascripts/application.js b/public/javascripts/application.js index e46206ea..caf1739a 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -217,7 +217,7 @@ var TracksPages = { var TodoItemsContainer = { // public ensureVisibleWithEffectAppear: function(elemId){ - $('#'+elemId).fadeIn(400); + $('#'+elemId).fadeIn(500); }, expandNextActionListing: function(itemsElem, skipAnimation) { itemsElem = $(itemsElem); @@ -347,14 +347,20 @@ var TodoItems = { return false; }); - /* delete button to delete a project from the list - * :with => "'#{parameters}'",*/ + /* delete button to delete a project from the list */ $('.item-container a.icon_delete_item').live('click', function(evt){ if(confirm(this.title)){ delete_with_ajax_and_block_element(this.href, $(this).parents('.project')); } return false; }); + + /* submit todo form after edit */ + $("form.edit_todo_form button.positive").live('click', function (ev) { + submit_with_ajax_and_block_element('form.edit_todo_form', $(this)); + return false; + }); + } }