diff --git a/tracks/app/controllers/application.rb b/tracks/app/controllers/application.rb index f7c8c1b2..089c1797 100644 --- a/tracks/app/controllers/application.rb +++ b/tracks/app/controllers/application.rb @@ -54,6 +54,18 @@ class ApplicationController < ActionController::Base render :text => message, :status => status end + def rescue_action(exception) + log_error(exception) if logger + respond_to do |wants| + wants.html do + notify :warning, "An error occurred on the server." + render :action => "index" + end + wants.js { render :action => 'error' } + wants.xml { render :text => 'An error occurred on the server.' + $! } + end + end + private def get_current_user diff --git a/tracks/app/controllers/context_controller.rb b/tracks/app/controllers/context_controller.rb index a646c7b8..73174427 100644 --- a/tracks/app/controllers/context_controller.rb +++ b/tracks/app/controllers/context_controller.rb @@ -61,46 +61,6 @@ class ContextController < ApplicationController end end - # Called by a form button - # Parameters from form fields are passed to create new action - # in the selected context. - def add_item - self.init - @item = @user.todos.build - @item.attributes = params["todo"] - - if @item.due? - @date = parse_date_per_user_prefs(params["todo"]["due"]) - @item.due = @date.to_s(:db) - else - @item.due = "" - end - - @saved = @item.save - if @saved - @up_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.state = ? and todos.context_id = ?", @user.id, 'active', @item.context_id]).size.to_s - end - - return if request.xhr? - - # fallback for standard requests - if @saved - notify :notice, 'Added new next action.' - redirect_to :controller => 'todo', :action => 'list' - else - notify :warning, 'The next action was not added. Please try again.' - redirect_to :controller => 'todo', :action => 'list' - end - - rescue - if request.xhr? # be sure to include an error.rjs - render :action => 'error' - else - notify :warning, 'An error occurred on the server.' - redirect_to :controller => 'todo', :action => 'list' - end - end - # Edit the details of the context # def update diff --git a/tracks/app/controllers/deferred_controller.rb b/tracks/app/controllers/deferred_controller.rb deleted file mode 100644 index f8a4c2ee..00000000 --- a/tracks/app/controllers/deferred_controller.rb +++ /dev/null @@ -1,131 +0,0 @@ -class DeferredController < ApplicationController - - model :user - model :project - model :context - - helper :todo - - prepend_before_filter :login_required - layout "standard" - - - def index - @source_view = 'deferred' - init_data_for_sidebar - @page_title = "TRACKS::Tickler" - @tickles = @user.todos.find_in_state(:all, :deferred, :order => "show_from ASC") - @count = @tickles.size - end - - def create - @source_view = 'deferred' - @item = Todo.new_deferred - @item.attributes = params["todo"] - if params["todo"]["show_from"] - @item.show_from = parse_date_per_user_prefs(params["todo"]["show_from"]) - end - @item.user_id = @user.id - - if @item.due? - @item.due = parse_date_per_user_prefs(params["todo"]["due"]) - else - @item.due = "" - end - - @saved = @item.save - - if @saved - @up_count = @user.todos.count_in_state(:deferred) - end - - respond_to do |wants| - wants.html { redirect_to :action => "index" } - wants.js - wants.xml { render :xml => @item.to_xml( :root => 'todo', :except => :user_id ) } - end - end - - def edit - @source_view = 'deferred' - init_projects_and_contexts - @item = check_user_return_item - render :template => 'todo/edit.rjs' - end - - def update - @source_view = 'deferred' - @item = check_user_return_item - @original_item_context_id = @item.context_id - @item.attributes = params["item"] - - if params["item"]["show_from"] - @item.show_from = parse_date_per_user_prefs(params["item"]["show_from"]) - end - - if @item.due? - @item.due = parse_date_per_user_prefs(params["item"]["due"]) - else - @item.due = "" - end - - @saved = @item.save - end - - def destroy - @item = check_user_return_item - context_id = @item.context_id - @saved = @item.destroy - - respond_to do |wants| - wants.html do - notify :notice, 'Successfully deleted next action' if @saved - redirect_to :action => "index" - end - wants.js do - @down_count = @user.todos.count_in_state(:deferred) if @saved - render - end - wants.xml { render :xml => @item.to_xml( :root => 'todo', :except => :user_id ) } - end - - rescue - respond_to do |wants| - wants.html do - notify :warning, 'An error occurred on the server.' - redirect_to :action => "index" - end - wants.js { render :action => 'error' } - wants.xml { render :text => "500 Server Error: There was an error deleting the action.", :status => 500 } - end - end - - # Check for any due tickler items, activate them - # Called by periodically_call_remote - def check_tickler - now = Date.today() - @due_tickles = @user.todos.find_in_state(:all, :deferred, :conditions => ['show_from < ? OR show_from = ?', now, now ], :order => "show_from ASC") - # Change the due tickles to active - @due_tickles.each do |t| - t.activate! - t.save - end - respond_to do |wants| - wants.html { redirect_to :controller => 'todo', :action => 'index' } - wants.js - end - end - - private - - def check_user_return_item - item = Todo.find( params['id'] ) - if @user == item.user - return item - else - notify :warning, "Item and session user mis-match: #{item.user.name} and #{@user.name}!" - render_text "" - end - end - -end \ No newline at end of file diff --git a/tracks/app/controllers/project_controller.rb b/tracks/app/controllers/project_controller.rb index deceb4c0..b156fa33 100644 --- a/tracks/app/controllers/project_controller.rb +++ b/tracks/app/controllers/project_controller.rb @@ -86,46 +86,6 @@ class ProjectController < ApplicationController end end - # Called by a form button - # Parameters from form fields are passed to create new action - # in the selected context. - def add_item - self.init - @item = @user.todos.build - @item.attributes = params["todo"] - - if @item.due? - @date = parse_date_per_user_prefs(params["todo"]["due"]) - @item.due = @date.to_s(:db) - else - @item.due = "" - end - - @saved = @item.save - if @saved - @up_count = Todo.find(:all, :conditions => ["todos.user_id = ? and todos.state = ? and todos.project_id = ?", @user.id, 'active', @item.project_id]).size.to_s - end - - return if request.xhr? - - # fallback for standard requests - if @saved - notify :notice, 'Added new next action.' - redirect_to :controller => 'todo', :action => 'index' - else - notify :warning, 'The next action was not added. Please try again.' - redirect_to :controller => 'todo', :action => 'index' - end - - rescue - if request.xhr? # be sure to include an error.rjs - render :action => 'error' - else - notify :warning, 'An error occurred on the server.' - redirect_to :controller => 'todo', :action => 'index' - end - end - # Edit the details of the project # def update diff --git a/tracks/app/controllers/todo_controller.rb b/tracks/app/controllers/todo_controller.rb index 5de57808..a6cd93d6 100644 --- a/tracks/app/controllers/todo_controller.rb +++ b/tracks/app/controllers/todo_controller.rb @@ -62,15 +62,41 @@ class TodoController < ApplicationController def create init @item = @user.todos.build - p = params['todo'] || params['request']['todo'] - @item.attributes = p + p = params['request'] || params + @item.attributes = p['todo'] + + if p['todo']['project_id'].blank? && !p['project_name'].blank? && p['project_name'] != 'None' + project = @user.projects.find_by_name(p['project_name'].strip) + unless project + project = @user.projects.build + project.name = p['project_name'].strip + project.save + @new_project_created = true + end + @item.project_id = project.id + end + + if p['todo']['context_id'].blank? && !p['context_name'].blank? + context = @user.contexts.find_by_name(p['context_name'].strip) + unless context + context = @user.contexts.build + context.name = p['context_name'].strip + context.save + @new_context_created = true + end + @item.context_id = context.id + end if @item.due? - @date = parse_date_per_user_prefs(p["due"]) + @date = parse_date_per_user_prefs(p['todo']['due']) @item.due = @date.to_s(:db) else @item.due = "" end + + if p['todo']['show_from'] + @item.show_from = parse_date_per_user_prefs(p['todo']['show_from']) + end @saved = @item.save @@ -78,28 +104,12 @@ class TodoController < ApplicationController wants.html { redirect_to :action => "index" } wants.js do if @saved - init_todos - @up_count = @todos.reject { |x| !x.active? or x.context.hide? }.size.to_s + determine_down_count end render :action => 'create' end wants.xml { render :xml => @item.to_xml( :root => 'todo', :except => :user_id ) } end - - # if you're seeing the message 'An error occurred on the server.' and you want to debug, comment out the rescue section and check the Ajax response for an exception message - rescue - respond_to do |wants| - wants.html do - notify :warning, "An error occurred on the server." - render :action => "index" - end - wants.js { render :action => 'error' } - wants.xml { render :text => 'An error occurred on the server.' + $! } - end - end - - def add_item - create end def edit @@ -119,7 +129,6 @@ class TodoController < ApplicationController # def toggle_check init - logger.info "source view is " + @source_view @item = check_user_return_item @item.toggle_completion() @saved = @item.save @@ -144,7 +153,28 @@ class TodoController < ApplicationController @item = check_user_return_item @original_item_context_id = @item.context_id @original_item_project_id = @item.project_id - @item.attributes = params["item"] + + if params['item']['project_id'].blank? && !params['project_name'].blank? && params['project_name'] != 'None' + project = @user.projects.find_by_name(params['project_name'].strip) + unless project + project = @user.projects.build + project.name = params['project_name'].strip + project.save + @new_project_created = true + end + params["item"]["project_id"] = project.id + end + + if params['item']['context_id'].blank? && !params['context_name'].blank? + context = @user.contexts.find_by_name(params['context_name'].strip) + unless context + context = @user.contexts.build + context.name = params['context_name'].strip + context.save + @new_context_created = true + end + params["item"]["context_id"] = context.id + end if params["item"].has_key?("due") params["item"]["due"] = parse_date_per_user_prefs(params["item"]["due"]) else @@ -155,42 +185,9 @@ class TodoController < ApplicationController if @context_changed then @remaining_undone_in_context = @user.contexts.find(@original_item_context_id).not_done_todos.length; end @project_changed = @original_item_project_id != @item.project_id if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = @user.projects.find(@original_item_project_id).not_done_todos.length; end + determine_down_count end - - def update_context - init - @item = check_user_return_item - context = Context.find(params['context_id']); - if @user == context.user - @original_item_context_id = @item.context_id - @item.context_id = context.id - @item.context = context - @saved = @item.save - render :action => 'update' - else - render :update do |page| - page.notify :warning, content_tag("div", "Error updating the context of the dragged item. Item and context user mis-match: #{@item.user.name} and #{@context.user.name}! - refresh the page to see them."), 8.0 - end - end - end - - def update_project - init - @item = check_user_return_item - project = Project.find(params['project_id']); - if @user == project.user - @original_item_context_id = @item.context_id - @item.project_id = project.id - @item.project = project - @saved = @item.save - render :action => 'update' - else - render :update do |page| - page.notify :warning, content_tag("div", "Error updating the project of the dragged item. Item and project user mis-match: #{@item.user.name} and #{@project.user.name}! - refresh the page to see them."), 8.0 - end - end - end - + def destroy @item = check_user_return_item @context_id = @item.context_id @@ -224,16 +221,6 @@ class TodoController < ApplicationController wants.xml { render :text => '200 OK. Action deleted.', :status => 200 } end - - rescue - respond_to do |wants| - wants.html do - notify :error, 'An error occurred on the server.', 8.0 - redirect_to :action => 'index' - end - wants.js { render :action => 'error' } - wants.xml { render :text => 'An error occurred on the server.' + $! } - end end def completed @@ -250,6 +237,31 @@ class TodoController < ApplicationController @done_archive = @done.completed_more_than 28.day.ago end + def tickler + init + @source_view = 'deferred' + @page_title = "TRACKS::Tickler" + @tickles = @user.todos.find_in_state(:all, :deferred, :order => "show_from ASC") + @count = @tickles.size + end + + # Check for any due tickler items, activate them + # Called by periodically_call_remote + def check_tickler + now = Date.today() + @due_tickles = @user.todos.find_in_state(:all, :deferred, :conditions => ['show_from < ? OR show_from = ?', now, now ], :order => "show_from ASC") + # Change the due tickles to active + @due_tickles.each do |t| + t.activate! + t.save + end + respond_to do |wants| + wants.html { redirect_to :controller => 'todo', :action => 'index' } + wants.js + end + end + + private def check_user_return_item @@ -296,6 +308,9 @@ class TodoController < ApplicationController @down_count = @user.projects.find(@item.project_id).todos.count_in_state(:active) end end + from.deferred do + @down_count = @user.todos.count_in_state(:deferred) + end end end diff --git a/tracks/app/helpers/deferred_helper.rb b/tracks/app/helpers/deferred_helper.rb deleted file mode 100644 index 506ce1f1..00000000 --- a/tracks/app/helpers/deferred_helper.rb +++ /dev/null @@ -1,2 +0,0 @@ -module DeferredHelper -end diff --git a/tracks/app/helpers/todo_helper.rb b/tracks/app/helpers/todo_helper.rb index b3e0eb69..12c7fb7d 100644 --- a/tracks/app/helpers/todo_helper.rb +++ b/tracks/app/helpers/todo_helper.rb @@ -7,16 +7,14 @@ module TodoHelper count = Todo.find_all("done=0 AND context_id=#{context.id}").length end - def form_remote_tag_edit_todo( item, type ) - (type == "deferred") ? controller_name = 'deferred' : controller_name = 'todo' - form_remote_tag( :url => { :controller => controller_name, :action => 'update', :id => item.id }, + def form_remote_tag_edit_todo( item ) + form_remote_tag( :url => { :controller => 'todo', :action => 'update', :id => item.id }, :html => { :id => "form-action-#{item.id}", :class => "inline-form" } ) end - def link_to_remote_todo( item, options = {}) - (options[:type] == "deferred") ? controller_name = 'deferred' : controller_name = 'todo' - url_options = { :controller => controller_name, :action => 'destroy', :id => item.id, :_source_view => @source_view } + def link_to_remote_todo(item) + url_options = { :controller => 'todo', :action => 'destroy', :id => item.id, :_source_view => @source_view } str = link_to_remote( image_tag_for_delete, { :url => url_options, :confirm => "Are you sure that you want to delete the action, \'#{item.description}\'?" }, @@ -107,6 +105,33 @@ module TodoHelper javascript_tag str end + def item_container_id + return "tickler-items" if source_view_is :deferred + return "p#{@item.project_id}" if source_view_is :project + return "c#{@item.context_id}" + end + + def parent_container_type + return 'tickler' if source_view_is :deferred + return 'project' if source_view_is :project + return 'context' + end + + def empty_container_msg_div_id + return "p#{@item.project_id}empty-nd" if source_view_is :project + return "c#{@item.context_id}empty-nd" if source_view_is :context + return "tickler-empty-nd" if source_view_is :deferred + nil + end + + def project_names_for_autocomplete + array_or_string_for_javascript( ['None'] + @projects.collect{|p| p.name } ) + end + + def context_names_for_autocomplete + array_or_string_for_javascript( @contexts.collect{|c| c.name } ) + end + private def image_tag_for_delete diff --git a/tracks/app/models/todo.rb b/tracks/app/models/todo.rb index 66f54df3..7d3f5ade 100644 --- a/tracks/app/models/todo.rb +++ b/tracks/app/models/todo.rb @@ -42,6 +42,7 @@ class Todo < ActiveRecord::Base validates_length_of :notes, :maximum => 60000, :allow_nil => true # validates_chronic_date :due, :allow_nil => true validates_presence_of :show_from, :if => :deferred? + validates_presence_of :context def validate if deferred? && show_from != nil && show_from < Date.today() @@ -63,12 +64,14 @@ class Todo < ActiveRecord::Base original_project.nil? ? Project.null_object : original_project end - def self.new_deferred - todo = self.new - def todo.set_initial_state - self.state = 'deferred' + alias_method :original_set_initial_state, :set_initial_state + + def set_initial_state + if show_from && (show_from > Date.today()) + write_attribute self.class.state_column, 'deferred' + else + original_set_initial_state end - todo end def self.not_done( id=id ) diff --git a/tracks/app/views/context/show.rhtml b/tracks/app/views/context/show.rhtml index 8462e873..60e59eba 100644 --- a/tracks/app/views/context/show.rhtml +++ b/tracks/app/views/context/show.rhtml @@ -7,8 +7,6 @@
- <%= render :partial => "shared/add_new_item_form", :locals => {:hide_link => false, :msg => ""} %> - <%= render :partial => "shared/new_action_form", :locals => {:hide_link => false, :msg => ""} %> - <%= render :partial => "shared/new_action_form" %> + <%= render :partial => "shared/add_new_item_form" %> <%= render "shared/sidebar" %>
\ No newline at end of file diff --git a/tracks/app/views/deferred/_item.rhtml b/tracks/app/views/deferred/_item.rhtml deleted file mode 100644 index c65ac1eb..00000000 --- a/tracks/app/views/deferred/_item.rhtml +++ /dev/null @@ -1,25 +0,0 @@ -
-
- <%= link_to_remote_todo item, { :type => 'deferred' } %> -
- <%= show_date( item.show_from ) -%> - -<%= sanitize(item.description) %> -<% if item.due -%> - (action due on <%= item.due.to_s %>) -<% end -%> -<%= link_to( "[C]", { :controller => "context", :action => "show", :name => urlize(item.context.name) }, :title => "View context: #{item.context.name}" ) %> -<% if item.project_id -%> - <%= link_to( "[P]", { :controller => "project", :action => "show", :name => urlize(item.project.name) }, :title => "View project: #{item.project.name}" ) %> -<% end -%> -<% if item.notes? -%> - <%= toggle_show_notes( item ) %> -<% end -%> -
-
- -
\ No newline at end of file diff --git a/tracks/app/views/deferred/_items.rhtml b/tracks/app/views/deferred/_items.rhtml deleted file mode 100644 index dafcaa82..00000000 --- a/tracks/app/views/deferred/_items.rhtml +++ /dev/null @@ -1,12 +0,0 @@ -
-

Deferred actions

- -
-
-

Currently there are no deferred actions

-
- - <%= render :partial => "item", :collection => @tickles %> - -
-
\ No newline at end of file diff --git a/tracks/app/views/deferred/create.rjs b/tracks/app/views/deferred/create.rjs deleted file mode 100644 index 864abc39..00000000 --- a/tracks/app/views/deferred/create.rjs +++ /dev/null @@ -1,12 +0,0 @@ -page.hide "status" -if @saved - page.notify :notice, "Added new next action", 5.0 - page.replace_html "badge_count", @up_count - page.send :record, "Form.reset('todo-form-new-action');Form.focusFirstElement('todo-form-new-action')" - page.insert_html :bottom, "tickler", :partial => 'deferred/item', :object => @item - page.visual_effect :highlight, "item-#{@item.id}-container", :duration => 3 - page["tickler-empty-nd"].hide -else - page.show 'status' - page.replace_html 'status', "#{error_messages_for('item')}" -end \ No newline at end of file diff --git a/tracks/app/views/deferred/destroy.rjs b/tracks/app/views/deferred/destroy.rjs deleted file mode 100644 index e55eba3f..00000000 --- a/tracks/app/views/deferred/destroy.rjs +++ /dev/null @@ -1,9 +0,0 @@ -if @saved - page["item-#{@item.id}-container"].remove - page['badge_count'].replace_html @down_count - if @down_count == 0 - page.visual_effect :appear, "tickler-empty-nd", :duration => 0.4 - end -else - page.notify :error, "There was an error deleting the item #{@item.description}", 8.0 -end \ No newline at end of file diff --git a/tracks/app/views/deferred/error.rjs b/tracks/app/views/deferred/error.rjs deleted file mode 100644 index 4f82b3e4..00000000 --- a/tracks/app/views/deferred/error.rjs +++ /dev/null @@ -1 +0,0 @@ -page.notify :error, @error_message || "An error occurred on the server.", 8.0 \ No newline at end of file diff --git a/tracks/app/views/deferred/index.rhtml b/tracks/app/views/deferred/index.rhtml deleted file mode 100644 index bfc7c3db..00000000 --- a/tracks/app/views/deferred/index.rhtml +++ /dev/null @@ -1,10 +0,0 @@ -
- - <%= render :partial => "items" %> - -
- -
- <%= render :partial => "shared/add_new_item_form", :locals => {:hide_link => false, :msg => ""} %> - <%= render "shared/sidebar" %> -
\ No newline at end of file diff --git a/tracks/app/views/deferred/update.rjs b/tracks/app/views/deferred/update.rjs deleted file mode 100644 index fcf7bafd..00000000 --- a/tracks/app/views/deferred/update.rjs +++ /dev/null @@ -1,14 +0,0 @@ -page.hide "info" -if @saved - if @item.context_id == @original_item_context_id - page.replace "item-#{@item.id}-container", :partial => 'deferred/item' - page.visual_effect :highlight, "item-#{@item.id}-container", :duration => 3 - else - page["item-#{@item.id}-container"].remove - page.insert_html :bottom, "c#{@item.context_id}items", :partial => 'deferred/item' - page.visual_effect :highlight, "item-#{@item.id}-container", :duration => 3 - end -else - page.show 'status' - page.replace_html 'status', "#{error_messages_for('item')}" -end \ No newline at end of file diff --git a/tracks/app/views/layouts/standard.rhtml b/tracks/app/views/layouts/standard.rhtml index f2c08a8a..a62a4fef 100644 --- a/tracks/app/views/layouts/standard.rhtml +++ b/tracks/app/views/layouts/standard.rhtml @@ -46,7 +46,7 @@
  • <%= navigation_link("Home", {:controller => "todo", :action => "index"}, {:accesskey => "t", :title => "Home"} ) %>
  • <%= navigation_link( "Contexts", {:controller => "context", :action => "list"}, {:accesskey=>"c", :title=>"Contexts"} ) %>
  • <%= navigation_link( "Projects", {:controller => "project", :action => "list"}, {:accesskey=>"p", :title=>"Projects"} ) %>
  • -
  • <%= navigation_link( "Tickler", {:controller => "deferred", :action => "index"}, :title => "Ticker" ) %>
  • +
  • <%= navigation_link( "Tickler", {:controller => "todo", :action => "tickler"}, :title => "Tickler" ) %>
  • <%= navigation_link( "Done", {:controller => "todo", :action => "completed"}, {:accesskey=>"d", :title=>"Completed"} ) %>
  • <%= navigation_link( "Notes", {:controller => "note", :action => "index"}, {:accesskey => "o", :title => "Show all notes"} ) %>
  • <%= navigation_link( "Preferences", {:controller => "user", :action => "preferences"}, {:accesskey => "u", :title => "Show my preferences"} ) %>
  • @@ -63,7 +63,7 @@ <%= periodically_call_remote( :url => {:controller => "login", :action => "check_expiry"}, :frequency => (5*60)) %> <% end -%> -<%= periodically_call_remote( :url => {:controller => "deferred", :action => "check_tickler"}, +<%= periodically_call_remote( :url => {:controller => "todo", :action => "check_tickler"}, :frequency => (10*60)) %> <%= yield %> diff --git a/tracks/app/views/project/show.rhtml b/tracks/app/views/project/show.rhtml index 7e542150..be083d3b 100644 --- a/tracks/app/views/project/show.rhtml +++ b/tracks/app/views/project/show.rhtml @@ -46,8 +46,6 @@
    - - <%= render :partial => "shared/add_new_item_form", :locals => {:hide_link => false, :msg => ""} %> - <%= render :partial => "shared/new_action_form", :locals => {:hide_link => false, :msg => ""} %> -<%= render "shared/sidebar" %> + <%= render :partial => "shared/add_new_item_form" %> + <%= render "shared/sidebar" %>
    diff --git a/tracks/app/views/shared/_add_new_item_form.rhtml b/tracks/app/views/shared/_add_new_item_form.rhtml index bef9d908..83f334d8 100644 --- a/tracks/app/views/shared/_add_new_item_form.rhtml +++ b/tracks/app/views/shared/_add_new_item_form.rhtml @@ -1,44 +1,19 @@ <% - case controller.controller_name - when "context" - add_string = "Add a next action in this context »" - @selected_context = @context.id - @selected_project = nil - when "project" - add_string = "Add a next action in this project »" - @selected_context = @contexts[0].id unless @contexts.empty? - @selected_project = @project.id - when "deferred" - add_string = "Add a deferred action »" - @selected_context = @contexts[0].id unless @contexts.empty? - @selected_project = nil - else - add_string = "Add a next action »" - @selected_context = @contexts[0].id unless @contexts.empty? - @selected_project = nil - end + @initial_context_name = @context.name unless @context.nil? + @initial_context_name ||= @contexts[0].name unless @contexts[0].nil? + @initial_project_name = @project.name unless @project.nil? %> -<% hide_link ||= false %> -<% unless hide_link -%> -<%= link_to_function( - add_string, -"Element.toggle('todo_new_action');Form.focusFirstElement('todo-form-new-action');", - {:title => "Add the next action", :accesskey => "n"}) %> -<% end -%> +<%= link_to_function("Add a next action »", + "Element.toggle('todo_new_action');Form.focusFirstElement('todo-form-new-action');", + {:title => "Add a next action", :accesskey => "n"}) %> -
    +
    -<% if controller.controller_name == "deferred" -%> - <%= form_remote_tag( - :url => { :controller => "deferred", :action => "create" }, - :html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' }) %> -<% else -%> <%= form_remote_tag( - :url => { :controller => controller.controller_name, :action => "add_item" }, + :url => { :controller => "todo", :action => "create" }, :html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' }) %> -<% end -%>
    <%= error_messages_for("item") %>
    @@ -46,28 +21,40 @@ <%= text_field( "todo", "description", "size" => 25, "tabindex" => 1) %>

    -<%= text_area( "todo", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2) %>
    +<%= text_area( "todo", "notes", "cols" => 25, "rows" => 6, "tabindex" => 2) %>
    -
    - <%= select("todo", "context_id", @contexts.collect {|c| [c.name, c.id] }, { :selected => @selected_context }, {"tabindex" => 3}) %> +
    + + + +
    -
    - <%= select( "todo", "project_id", @projects.select{|x| x.active? }.collect {|p| [p.name, p.id] }, - { :selected => @selected_project, :include_blank => true }, {"tabindex" => 4}) %> +
    + + + +

    <%= text_field("todo", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5, "autocomplete" => "off") %> -<% if controller.controller_name == "deferred" -%>

    - <%= text_field("todo", "show_from", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5, "autocomplete" => "off") %>
    -<% end -%> + <%= text_field("todo", "show_from", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 6, "autocomplete" => "off") %>
    + -

    - +<%= source_view_tag( @source_view ) %> + <%= end_form_tag %> <%= observe_field "todo_due", @@ -78,7 +65,5 @@ :url => { :controller => "todo", :action => "date_preview" } %> <%= calendar_setup( "todo_due" ) %> -<% if controller.controller_name == "deferred" -%> <%= calendar_setup( "todo_show_from" ) %> -<% end -%>
    diff --git a/tracks/app/views/shared/_new_action_form.rhtml b/tracks/app/views/shared/_new_action_form.rhtml deleted file mode 100644 index 843dc643..00000000 --- a/tracks/app/views/shared/_new_action_form.rhtml +++ /dev/null @@ -1,71 +0,0 @@ -<% - @selected_context = @contexts[0].id unless @contexts.empty? - @selected_project = nil - case controller.controller_name - when "context" - add_string = "Add a next action in this context »" - @selected_context = @context.id - when "project" - add_string = "Add a next action in this project »" - @selected_project = @project.id - when "deferred" - add_string = "Add a next action »" - else - add_string = "Add a deferred action »" - end -%> -<% hide_link ||= false %> -<% unless hide_link -%> -<%= link_to_function( - " ", - "onclick=new Lightbox.base('mybox', { closeOnOverlayClick : true });Form.focusFirstElement('todo-form-new-action-lightbox'); return false;", - {:title => "Add the next action", :accesskey => "q"}) %> -<% end -%> - - \ No newline at end of file diff --git a/tracks/app/views/todo/_edit_form.rhtml b/tracks/app/views/todo/_edit_form.rhtml index 6670c0b0..16a3a503 100644 --- a/tracks/app/views/todo/_edit_form.rhtml +++ b/tracks/app/views/todo/_edit_form.rhtml @@ -13,34 +13,26 @@ <%= text_area( "item", "notes", "cols" => 20, "rows" => 5, "tabindex" => 2) %> - - + + + + - + - + + +
    diff --git a/tracks/app/views/deferred/check_tickler.rjs b/tracks/app/views/todo/check_tickler.rjs similarity index 100% rename from tracks/app/views/deferred/check_tickler.rjs rename to tracks/app/views/todo/check_tickler.rjs diff --git a/tracks/app/views/todo/create.rjs b/tracks/app/views/todo/create.rjs index 9deb6540..5251d4f3 100644 --- a/tracks/app/views/todo/create.rjs +++ b/tracks/app/views/todo/create.rjs @@ -1,13 +1,18 @@ if @saved - page.notify :notice, "Added new next action", 5.0 - page['badge_count'].replace_html @up_count + status_message = 'Added new next action' + status_message += ' to tickler' if @item.deferred? + status_message = 'Added new project / ' + status_message if @new_project_created + status_message = 'Added new context / ' + status_message if @new_context_created + page.notify :notice, status_message, 5.0 + page['badge_count'].replace_html @down_count page.send :record, "Form.reset('todo-form-new-action');Form.focusFirstElement('todo-form-new-action')" - page.send :record, "Form.reset('todo-form-new-action-lightbox');Form.focusFirstElement('todo-form-new-action-lightbox')" - page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@item.context_id}" - page.insert_html :bottom, "c#{@item.context_id}items", :partial => 'todo/item', :locals => { :parent_container_type => "context" } + page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@item.context_id}" if source_view_is(:todo) + page << "contextAutoCompleter.options.array = #{context_names_for_autocomplete}" if @new_context_created + page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}" if @new_project_created + page.insert_html :bottom, item_container_id, :partial => 'todo/item', :locals => { :parent_container_type => parent_container_type, :source_view => @source_view } page.visual_effect :highlight, "item-#{@item.id}-container", :duration => 3 - page["c#{@item.context_id}empty-nd"].hide + page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? else page.show 'status' page.replace_html 'status', "#{error_messages_for('item')}" -end \ No newline at end of file +end diff --git a/tracks/app/views/todo/destroy.rjs b/tracks/app/views/todo/destroy.rjs index 43a65faf..614c7c9a 100644 --- a/tracks/app/views/todo/destroy.rjs +++ b/tracks/app/views/todo/destroy.rjs @@ -1,11 +1,8 @@ if @saved page["item-#{@item.id}-container"].remove page['badge_count'].replace_html @down_count - source_view do |from| - from.todo { page.visual_effect :fade, "c#{@item.context_id}", :duration => 0.4 if @remaining_undone_in_context == 0 } - from.context { page["c#{@item.context_id}empty-nd"].show if @down_count == 0 } - from.project { page["p#{@item.project_id}empty-nd"].show if @down_count == 0 } - end + page.visual_effect :fade, "c#{@item.context_id}", :duration => 0.4 if source_view_is :todo && @remaining_undone_in_context == 0 + page[empty_container_msg_div_id].show if !empty_container_msg_div_id.nil? && @down_count == 0 else page.notify :error, "There was an error deleting the item #{@item.description}", 8.0 end \ No newline at end of file diff --git a/tracks/app/views/todo/index.rhtml b/tracks/app/views/todo/index.rhtml index 4575de20..e48ff323 100644 --- a/tracks/app/views/todo/index.rhtml +++ b/tracks/app/views/todo/index.rhtml @@ -9,8 +9,6 @@
    - <%= render :partial => "shared/add_new_item_form", :locals => {:hide_link => false, :msg => ""} %> - <%= render :partial => "shared/new_action_form", :locals => {:hide_link => false, :msg => ""} %> - <%= render :partial => "shared/new_action_form" %> + <%= render :partial => "shared/add_new_item_form" %> <%= render "shared/sidebar" %>
    \ No newline at end of file diff --git a/tracks/app/views/todo/tickler.rhtml b/tracks/app/views/todo/tickler.rhtml index 37192a7a..07ec3e22 100644 --- a/tracks/app/views/todo/tickler.rhtml +++ b/tracks/app/views/todo/tickler.rhtml @@ -1,10 +1,21 @@
    - <%= render :partial => "tickler_items" %> +
    +

    Deferred actions

    +
    +
    +

    Currently there are no deferred actions

    +
    + + <%= render :partial => "todo/item", :collection => @tickles, :locals => { :parent_container_type => 'tickler' } %> + +
    +
    +
    - <%= render :partial => "shared/add_new_item_form", :locals => {:hide_link => false, :msg => ""} %> + <%= render :partial => "shared/add_new_item_form" %> <%= render "shared/sidebar" %>
    \ No newline at end of file diff --git a/tracks/app/views/todo/toggle_check.rjs b/tracks/app/views/todo/toggle_check.rjs index 495df1d7..8d8af3d8 100644 --- a/tracks/app/views/todo/toggle_check.rjs +++ b/tracks/app/views/todo/toggle_check.rjs @@ -1,15 +1,4 @@ -if @saved - - item_container_id = "c#{@item.context_id}" - parent_container_type = "context" - if source_view_is :context - empty_container_msg_div_id = "c#{@item.context_id}empty-nd" - elsif source_view_is :project - item_container_id = "p#{@item.project_id}" - empty_container_msg_div_id = "p#{@item.project_id}empty-nd" - parent_container_type = "project" - end - +if @saved page.remove "item-#{@item.id}-container" if @item.completed? # Don't try to insert contents into a non-existent container! diff --git a/tracks/app/views/todo/update.rjs b/tracks/app/views/todo/update.rjs index a093482d..ed94f054 100644 --- a/tracks/app/views/todo/update.rjs +++ b/tracks/app/views/todo/update.rjs @@ -1,5 +1,12 @@ if @saved item_container_id = "item-#{@item.id}-container" + status_message = 'Action saved' + status_message += ' to tickler' if @item.deferred? + status_message = 'Added new project / ' + status_message if @new_project_created + status_message = 'Added new context / ' + status_message if @new_context_created + page.notify :notice, status_message, 5.0 + page << "contextAutoCompleter.options.array = #{context_names_for_autocomplete}; contextAutoCompleter.changed = true" if @new_context_created + page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}; projectAutoCompleter.changed = true" if @new_project_created if source_view_is_one_of [:todo, :context] if @context_changed page[item_container_id].remove @@ -12,7 +19,7 @@ if @saved if source_view_is :todo page.call "todoItems.ensureVisibleWithEffectAppear", "c#{@item.context_id}" page.call "todoItems.expandNextActionListingByContext", "c#{@item.context_id}items", true - page.insert_html :bottom, "c#{@item.context_id}items", :partial => 'todo/item', :locals => { :parent_container_type => "context" } + page.insert_html :bottom, "c#{@item.context_id}items", :partial => 'todo/item', :locals => { :parent_container_type => parent_container_type } end page.replace_html("badge_count", @remaining_undone_in_context) if source_view_is :context page.delay(0.5) do @@ -23,7 +30,7 @@ if @saved end end else - page.replace item_container_id, :partial => 'todo/item', :locals => { :parent_container_type => "context" } + page.replace item_container_id, :partial => 'todo/item', :locals => { :parent_container_type => parent_container_type } page.visual_effect :highlight, item_container_id, :duration => 3 end elsif source_view_is :project @@ -32,9 +39,18 @@ if @saved page.show("p#{@original_item_project_id}empty-nd") if (@remaining_undone_in_project == 0) page.replace_html "badge_count", @remaining_undone_in_project else - page.replace item_container_id, :partial => 'todo/item', :locals => { :parent_container_type => "project" } + page.replace item_container_id, :partial => 'todo/item', :locals => { :parent_container_type => parent_container_type } page.visual_effect :highlight, item_container_id, :duration => 3 end + elsif source_view_is :deferred + if @item.deferred? + page.replace item_container_id, :partial => 'todo/item', :locals => { :parent_container_type => parent_container_type } + page.visual_effect :highlight, item_container_id, :duration => 3 + else + page[item_container_id].remove + page.show(empty_container_msg_div_id) if (@down_count == 0) + page.replace_html "badge_count", @down_count + end else logger.error "unexpected source_view '#{params[:_source_view]}'" end diff --git a/tracks/config/routes.rb b/tracks/config/routes.rb index 0ebdcae4..809e438f 100644 --- a/tracks/config/routes.rb +++ b/tracks/config/routes.rb @@ -28,14 +28,10 @@ ActionController::Routing::Routes.draw do |map| # ToDo Routes map.connect 'done', :controller => 'todo', :action => 'completed' - - # Deferred (Tickler) Routes - map.connect 'tickler', :controller => 'deferred', :action => 'index' - map.connect 'tickler/:action/:id', :controller => 'deferred' + map.connect 'tickler', :controller => 'todo', :action => 'tickler' # Context Routes map.connect 'context/create', :controller => 'context', :action => 'create' - map.connect 'context/add_item', :controller => 'context', :action => 'add_item' map.connect 'context/order', :controller => 'context', :action => 'order' map.connect 'context/:id', :controller=> 'context', :action => 'show', :requirements => {:id => /\d+/} map.connect 'context/:context/feed/:action/:name/:token', :controller => 'feed' @@ -46,7 +42,6 @@ ActionController::Routing::Routes.draw do |map| # Projects Routes map.connect 'project/create', :controller => 'project', :action => 'create' - map.connect 'project/add_item/:id', :controller => 'project', :action => 'add_item' map.connect 'project/toggle_check/:id', :controller => 'project', :action => 'toggle_check' map.connect 'project/order', :controller => 'project', :action => 'order' map.connect 'project/:project/feed/:action/:name/:token', :controller => 'feed' diff --git a/tracks/lib/source_view.rb b/tracks/lib/source_view.rb index cdda7be0..9f649235 100644 --- a/tracks/lib/source_view.rb +++ b/tracks/lib/source_view.rb @@ -40,7 +40,7 @@ module Tracks end def source_view_is( s ) - s == params[:_source_view].to_sym + s == (params[:_source_view] || @source_view).to_sym end def source_view_is_one_of( s=[] ) diff --git a/tracks/public/javascripts/dragdrop.js b/tracks/public/javascripts/dragdrop.js index f1dc7ea2..a01b7be2 100644 --- a/tracks/public/javascripts/dragdrop.js +++ b/tracks/public/javascripts/dragdrop.js @@ -1,4 +1,5 @@ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) // // See scriptaculous.js for full license. @@ -15,7 +16,8 @@ var Droppables = { element = $(element); var options = Object.extend({ greedy: true, - hoverclass: null + hoverclass: null, + tree: false }, arguments[1] || {}); // cache containers @@ -37,12 +39,27 @@ var Droppables = { this.drops.push(options); }, - - isContained: function(element, drop) { - var parentNode = element.parentNode; - return drop._containers.detect(function(c) { return parentNode == c }); + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; }, + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + isAffected: function(point, element, drop) { return ( (drop.element!=element) && @@ -68,18 +85,22 @@ var Droppables = { show: function(point, element) { if(!this.drops.length) return; + var affected = []; if(this.last_active) this.deactivate(this.last_active); this.drops.each( function(drop) { - if(Droppables.isAffected(point, element, drop)) { - if(drop.onHover) - drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); - if(drop.greedy) { - Droppables.activate(drop); - throw $break; - } - } + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } }, fire: function(event, element) { @@ -207,8 +228,10 @@ Draggable.prototype = { this.element = $(element); - if(options.handle && (typeof options.handle == 'string')) - this.handle = Element.childrenWithClassName(this.element, options.handle, true)[0]; + if(options.handle && (typeof options.handle == 'string')) { + var h = Element.childrenWithClassName(this.element, options.handle, true); + if(h.length>0) this.handle = h[0]; + } if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; @@ -412,6 +435,7 @@ Draggable.prototype = { if(this.scrollInterval) { clearInterval(this.scrollInterval); this.scrollInterval = null; + Draggables._lastScrollPointer = null; } }, @@ -440,7 +464,14 @@ Draggable.prototype = { Position.prepare(); Droppables.show(Draggables._lastPointer, this.element); Draggables.notify('onDrag', this); - this.draw(Draggables._lastPointer); + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); if(this.options.change) this.options.change(this); }, @@ -492,30 +523,41 @@ SortableObserver.prototype = { } var Sortable = { - sortables: new Array(), + sortables: {}, - options: function(element){ - element = $(element); - return this.sortables.detect(function(s) { return s.element == element }); + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; }, destroy: function(element){ - element = $(element); - this.sortables.findAll(function(s) { return s.element == element }).each(function(s){ + var s = Sortable.options(element); + + if(s) { Draggables.removeObserver(s.element); s.droppables.each(function(d){ Droppables.remove(d) }); s.draggables.invoke('destroy'); - }); - this.sortables = this.sortables.reject(function(s) { return s.element == element }); + + delete Sortable.sortables[s.element.id]; + } }, - + create: function(element) { element = $(element); var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' dropOnEmpty: false, - tree: false, // fixme: unimplemented + tree: false, + treeTag: 'ul', overlap: 'vertical', // one of 'vertical', 'horizontal' constraint: 'vertical', // one of 'vertical', 'horizontal', false containment: element, // also takes array of elements (or id's); or false @@ -565,9 +607,17 @@ var Sortable = { var options_for_droppable = { overlap: options.overlap, containment: options.containment, + tree: options.tree, hoverclass: options.hoverclass, - onHover: Sortable.onHover, - greedy: !options.dropOnEmpty + onHover: Sortable.onHover + //greedy: !options.dropOnEmpty + } + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass } // fix for gecko engine @@ -576,12 +626,9 @@ var Sortable = { options.draggables = []; options.droppables = []; - // make it so - // drop on empty handling - if(options.dropOnEmpty) { - Droppables.add(element, - {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); options.droppables.push(element); } @@ -592,11 +639,20 @@ var Sortable = { options.draggables.push( new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; options.droppables.push(e); }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } // keep reference - this.sortables.push(options); + this.sortables[element.id] = options; // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); @@ -605,24 +661,21 @@ var Sortable = { // return all suitable-for-sortable elements in a guaranteed order findElements: function(element, options) { - if(!element.hasChildNodes()) return null; - var elements = []; - var only = options.only ? [options.only].flatten() : null; - $A(element.childNodes).each( function(e) { - if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() && - (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) - elements.push(e); - if(options.tree) { - var grandchildren = this.findElements(e, options); - if(grandchildren) elements.push(grandchildren); - } - }); - - return (elements.length>0 ? elements.flatten() : null); + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); }, onHover: function(element, dropon, overlap) { - if(overlap>0.5) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { Sortable.mark(dropon, 'before'); if(dropon.previousSibling != element) { var oldParentNode = element.parentNode; @@ -645,13 +698,37 @@ var Sortable = { } } }, - - onEmptyHover: function(element, dropon) { - if(element.parentNode!=dropon) { - var oldParentNode = element.parentNode; - dropon.appendChild(element); + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + Sortable.options(oldParentNode).onChange(element); - Sortable.options(dropon).onChange(element); + droponOptions.onChange(element); } }, @@ -683,6 +760,75 @@ var Sortable = { Element.show(Sortable._marker); }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: new Array, + position: parent.children.length, + container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase()) + } + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child) + + parent.children.push (child); + } + + return parent; + }, + + /* Finds the first element of the given tag type within a parent element. + Used for finding the first LI[ST] within a L[IST]I[TEM].*/ + _findChildrenElement: function (element, containerTag) { + if (element && element.hasChildNodes) + for (var i = 0; i < element.childNodes.length; ++i) + if (element.childNodes[i].tagName == containerTag) + return element.childNodes[i]; + + return null; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || {}); + + var root = { + id: null, + parent: null, + children: new Array, + container: element, + position: 0 + } + + return Sortable._tree (element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, sequence: function(element) { element = $(element); @@ -705,20 +851,63 @@ var Sortable = { }); new_sequence.each(function(ident) { - var n = nodeMap[ident]; - if (n) { - n[1].appendChild(n[0]); - delete nodeMap[ident]; - } + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } }); }, - + serialize: function(element) { element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || {}); var name = encodeURIComponent( (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); - return Sortable.sequence(element, arguments[1]).map( function(item) { - return name + "[]=" + encodeURIComponent(item); - }).join('&'); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } } } + +/* Returns true if child is contained within element */ +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + + if (child.parentNode == element) return true; + + return Element.isParent(child.parentNode, element); +} + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + if (type == 'vertical' || type == 'height') + return element.offsetHeight; + else + return element.offsetWidth; +} \ No newline at end of file diff --git a/tracks/public/javascripts/effects.js b/tracks/public/javascripts/effects.js index bcf836b8..92740050 100644 --- a/tracks/public/javascripts/effects.js +++ b/tracks/public/javascripts/effects.js @@ -77,9 +77,12 @@ Element.getInlineOpacity = function(element){ } Element.childrenWithClassName = function(element, className, findFirst) { - return [$A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { - return c.className ? Element.hasClassName(c, className) : false; - })].flatten(); + var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)"); + var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { + return (c.className && c.className.match(classNameRegExp)); + }); + if(!results) results = []; + return results; } Element.forceRerendering = function(element) { @@ -91,11 +94,6 @@ Element.forceRerendering = function(element) { } catch(e) { } }; -['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', - 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( - function(f) { Element.Methods[f] = Element[f]; } -); - /*--------------------------------------------------------------------------*/ Array.prototype.call = function() { @@ -943,11 +941,18 @@ Effect.Fold = function(element) { effect.element.setStyle(oldStyle); } }); }}, arguments[1] || {})); -} +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( + function(f) { Element.Methods[f] = Element[f]; } +); Element.Methods.visualEffect = function(element, effect, options) { s = effect.gsub(/_/, '-').camelize(); effect_class = s.charAt(0).toUpperCase() + s.substring(1); new Effect[effect_class](element, options); return $(element); -}; \ No newline at end of file +}; + +Element.addMethods(); \ No newline at end of file diff --git a/tracks/public/javascripts/prototype.js b/tracks/public/javascripts/prototype.js index 2d4ba34c..0caf9cd7 100644 --- a/tracks/public/javascripts/prototype.js +++ b/tracks/public/javascripts/prototype.js @@ -1,4 +1,4 @@ -/* Prototype JavaScript framework, version 1.5.0_pre1 +/* Prototype JavaScript framework, version 1.5.0_rc0 * (c) 2005 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. @@ -7,7 +7,7 @@ /*--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.5.0_pre1', + Version: '1.5.0_rc0', ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', emptyFunction: function() {}, @@ -25,7 +25,7 @@ var Class = { var Abstract = new Object(); Object.extend = function(destination, source) { - for (property in source) { + for (var property in source) { destination[property] = source[property]; } return destination; @@ -176,7 +176,7 @@ Object.extend(String.prototype, { }, evalScripts: function() { - return this.extractScripts().map(eval); + return this.extractScripts().map(function(script) { return eval(script) }); }, escapeHTML: function() { @@ -355,7 +355,7 @@ var Enumerable = { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); - if (value >= (result || value)) + if (result == undefined || value >= result) result = value; }); return result; @@ -365,7 +365,7 @@ var Enumerable = { var result; this.each(function(value, index) { value = (iterator || Prototype.K)(value, index); - if (value <= (result || value)) + if (result == undefined || value < result) result = value; }); return result; @@ -447,7 +447,8 @@ var $A = Array.from = function(iterable) { Object.extend(Array.prototype, Enumerable); -Array.prototype._reverse = Array.prototype.reverse; +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; Object.extend(Array.prototype, { _each: function(iterator) { @@ -476,7 +477,7 @@ Object.extend(Array.prototype, { flatten: function() { return this.inject([], function(array, value) { - return array.concat(value.constructor == Array ? + return array.concat(value && value.constructor == Array ? value.flatten() : [value]); }); }, @@ -498,21 +499,13 @@ Object.extend(Array.prototype, { return (inline !== false ? this : this.toArray())._reverse(); }, - shift: function() { - var result = this[0]; - for (var i = 0; i < this.length - 1; i++) - this[i] = this[i + 1]; - this.length--; - return result; - }, - inspect: function() { return '[' + this.map(Object.inspect).join(', ') + ']'; } }); var Hash = { _each: function(iterator) { - for (key in this) { + for (var key in this) { var value = this[key]; if (typeof value == 'function') continue; @@ -590,9 +583,9 @@ var $R = function(start, end, exclusive) { var Ajax = { getTransport: function() { return Try.these( + function() {return new XMLHttpRequest()}, function() {return new ActiveXObject('Msxml2.XMLHTTP')}, - function() {return new ActiveXObject('Microsoft.XMLHTTP')}, - function() {return new XMLHttpRequest()} + function() {return new ActiveXObject('Microsoft.XMLHTTP')} ) || false; }, @@ -644,6 +637,7 @@ Ajax.Base.prototype = { this.options = { method: 'post', asynchronous: true, + contentType: 'application/x-www-form-urlencoded', parameters: '' } Object.extend(this.options, options || {}); @@ -707,8 +701,7 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*']; if (this.options.method == 'post') { - requestHeaders.push('Content-type', - 'application/x-www-form-urlencoded'); + requestHeaders.push('Content-type', this.options.contentType); /* Force "Connection: close" for Mozilla browsers to work around * a bug where XMLHttpReqeuest sends an incorrect Content-length @@ -739,7 +732,7 @@ Ajax.Request.prototype = Object.extend(new Ajax.Base(), { evalJSON: function() { try { - return eval(this.header('X-JSON')); + return eval('(' + this.header('X-JSON') + ')'); } catch (e) {} }, @@ -900,13 +893,14 @@ if (!window.Element) Element.extend = function(element) { if (!element) return; + if (_nativeExtensions) return element; if (!element._extended && element.tagName && element != window) { - var methods = Element.Methods; + var methods = Element.Methods, cache = Element.extend.cache; for (property in methods) { var value = methods[property]; if (typeof value == 'function') - element[property] = value.bind(null, element); + element[property] = cache.findOrStore(value); } } @@ -914,6 +908,14 @@ Element.extend = function(element) { return element; } +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +} + Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; @@ -1035,7 +1037,7 @@ Element.Methods = { setStyle: function(element, style) { element = $(element); - for (name in style) + for (var name in style) element.style[name.camelize()] = style[name]; }, @@ -1105,6 +1107,29 @@ Element.Methods = { Object.extend(Element, Element.Methods); +var _nativeExtensions = false; + +if(!HTMLElement && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + var HTMLElement = {} + HTMLElement.prototype = document.createElement('div').__proto__; +} + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + if(typeof HTMLElement != 'undefined') { + var methods = Element.Methods, cache = Element.extend.cache; + for (property in methods) { + var value = methods[property]; + if (typeof value == 'function') + HTMLElement.prototype[property] = cache.findOrStore(value); + } + _nativeExtensions = true; + } +} + +Element.addMethods(); + var Toggle = new Object(); Toggle.display = Element.toggle; @@ -1123,7 +1148,8 @@ Abstract.Insertion.prototype = { try { this.element.insertAdjacentHTML(this.adjacency, this.content); } catch (e) { - if (this.element.tagName.toLowerCase() == 'tbody') { + var tagName = this.element.tagName.toLowerCase(); + if (tagName == 'tbody' || tagName == 'tr') { this.insertContent(this.contentFromAnonymousTable()); } else { throw e; @@ -1396,7 +1422,7 @@ var Form = { form = $(form); var elements = new Array(); - for (tagName in Form.Element.Serializers) { + for (var tagName in Form.Element.Serializers) { var tagElements = form.getElementsByTagName(tagName); for (var j = 0; j < tagElements.length; j++) elements.push(tagElements[j]); @@ -1518,23 +1544,17 @@ Form.Element.Serializers = { var value = '', opt, index = element.selectedIndex; if (index >= 0) { opt = element.options[index]; - value = opt.value; - if (!value && !('value' in opt)) - value = opt.text; + value = opt.value || opt.text; } return [element.name, value]; }, selectMany: function(element) { - var value = new Array(); + var value = []; for (var i = 0; i < element.length; i++) { var opt = element.options[i]; - if (opt.selected) { - var optValue = opt.value; - if (!optValue && !('value' in opt)) - optValue = opt.text; - value.push(optValue); - } + if (opt.selected) + value.push(opt.value || opt.text); } return [element.name, value]; } @@ -1751,7 +1771,8 @@ Object.extend(Event, { }); /* prevent memory leaks in IE */ -Event.observe(window, 'unload', Event.unloadCache, false); +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); var Position = { // set to true if needed, warning: firefox performance problems // NOT neeeded for page scrolling, only if draggable contained in diff --git a/tracks/public/stylesheets/standard.css b/tracks/public/stylesheets/standard.css index b232c62e..2fbcaf31 100644 --- a/tracks/public/stylesheets/standard.css +++ b/tracks/public/stylesheets/standard.css @@ -698,4 +698,34 @@ input.open_id { color: #000; padding-left: 18px; width:182px; +} +div.page_name_auto_complete { + width: 100%; + background: #fff; + display: inline; +} + +div.page_name_auto_complete ul { + border: 1px solid #888; + margin: 0; + padding: 0; + width: 100%; + list-style-type: none; +} + +div.page_name_auto_complete ul li { + margin: 0; + padding: 2px; + font-weight:bold; + list-style-type: none; +} + +div.page_name_auto_complete ul li.selected { + background-color: #ffb; +} + +div.page_name_auto_complete ul strong.highlight { + color: #800; + margin: 0; + padding: 0; } \ No newline at end of file diff --git a/tracks/test/functional/deferred_controller_test.rb b/tracks/test/functional/deferred_controller_test.rb deleted file mode 100644 index 5e5a1253..00000000 --- a/tracks/test/functional/deferred_controller_test.rb +++ /dev/null @@ -1,22 +0,0 @@ -require File.dirname(__FILE__) + '/../test_helper' -require 'deferred_controller' - -# Re-raise errors caught by the controller. -class DeferredController; def rescue_action(e) raise e end; end - -class DeferredControllerTest < Test::Unit::TestCase - fixtures :users, :preferences, :projects, :contexts, :todos - - def setup - @controller = DeferredController.new - @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new - end - - def test_create - @request.session['user_id'] = users(:admin_user).id - xhr :post, :create, :todo => {:description => 'deferred controller test create', :notes => 'notes', :context_id => 1, :project_id => 1, :due => '', :show_from => '11/30/2030'} - t = Todo.find_by_description('deferred controller test create') - assert_equal :deferred, t.current_state.to_sym - end - -end