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 -%>
-
-
-
- <%= form_remote_tag_edit_todo( item, "deferred" ) -%>
- <% #note: edit form will load here remotely -%>
- <%= end_form_tag -%>
-
-
\ 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) %>
Notes
-<%= text_area( "todo", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2) %>
+<%= text_area( "todo", "notes", "cols" => 25, "rows" => 6, "tabindex" => 2) %>
-
Context
- <%= select("todo", "context_id", @contexts.collect {|c| [c.name, c.id] }, { :selected => @selected_context }, {"tabindex" => 3}) %>
+
Context
+
+
+
+
-
Project
- <%= select( "todo", "project_id", @projects.select{|x| x.active? }.collect {|p| [p.name, p.id] },
- { :selected => @selected_project, :include_blank => true }, {"tabindex" => 4}) %>
+
Project
+
+
+
+
Due
<%= text_field("todo", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5, "autocomplete" => "off") %>
-<% if controller.controller_name == "deferred" -%>
Show from
- <%= 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 -%>
-
-
-
-<%= form_remote_tag(
- :url => { :controller => controller.controller_name, :action => "add_item" },
- :html=> { :id=>'todo-form-new-action-lightbox', :name=>'todo', :class => 'inline-form' }) %>
-
-
Add a new next action:
-
-
-
-
- Description
- <%= text_field( "todo", "description", "size" => 30, "tabindex" => 1) %>
-
-
- Notes
- <%= text_area( "todo", "notes", "cols" => 25, "rows" => 10, "tabindex" => 2) %>
-
-
- Context
- <%= select("todo", "context_id", @contexts.collect {|c| [c.name, c.id] }, { :selected => @selected_context }, {"tabindex" => 3}) %>
-
-
- Project
- <%= select( "todo", "project_id", @projects.select{|x| x.active? }.collect {|p| [p.name, p.id] }, { :selected => @selected_project, :include_blank => true }, {"tabindex" => 4}) %>
-
-
- Due
- <%= text_field("todo", "due", "size" => 10, "class" => "Date", "onFocus" => "Calendar.setup", "tabindex" => 5, "autocomplete" => "off") %>
-
-
-<% if controller.controller_name == "tickler" -%>
-
- Show from
- <%= date_select( "todo", "show_from", :start_year => Date.today.year, :order => [:year, :month, :day] ) %>
-
-<% end -%>
-
-
-
-
-
-
-<%= end_form_tag %>
-<%= calendar_setup( "todo_due" ) %>
-
-
\ 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) %>
- Context
-
- <% for @place in @contexts %>
- <% if @item %>
- <% if @place.id == @item.context_id %>
- <%= @place.name %>
- <% else %>
- <%= @place.name %>
- <% end %>
- <% else %>
- <%= @place.name %>
- <% end %>
- <% end %>
-
+ Context
+
+
+
- Project
+ Project
-
- <% if !@item.project_id? %>
-
- <%= options_from_collection_for_select(@projects, "id", "name") %>
- <% else %>
-
- <%= options_from_collection_for_select(@projects, "id", "name", @item.project_id) %>
- <% end %>
-
+
+
+
Due
@@ -52,7 +44,7 @@
<% end -%>
- <% if controller.controller_name == "project" || @item.current_state == :deferred -%>
+ <% if controller.controller_name == "project" || @item.deferred? -%>
<% end -%>
@@ -62,6 +54,6 @@
<%= calendar_setup( "due_#{@item.id}" ) %>
-<% if @item.current_state == :deferred -%>
+<% if @item.deferred? -%>
<%= calendar_setup( "show_from_#{@item.id}" ) %>
<% end -%>
diff --git a/tracks/app/views/todo/_item.rhtml b/tracks/app/views/todo/_item.rhtml
index 21484c2f..8d9ca70f 100644
--- a/tracks/app/views/todo/_item.rhtml
+++ b/tracks/app/views/todo/_item.rhtml
@@ -1,35 +1,47 @@
-
<%= link_to_remote_todo item %>
+
+ <% unless source_view_is :deferred -%>
checked="checked" <% end %> />
-
<% # start of div which has a class 'description', and possibly 'stale_11', 'stale_12', 'stale_13' etc %>
-<% if item.completed? -%>
-
<%= format_date( item.completed_at ) %>
-<% else -%>
- <%= due_date( item.due ) -%>
-<% end -%>
+ <% end -%>
-<%= sanitize(item.description) %>
-<% if item.completed? -%>
- (<%= item.context.name %><%= ", " + item.project.name if item.project_id %>)
-<% else -%>
- <% if parent_container_type == "project" -%>
+
<% # start of div which has a class 'description', and possibly 'stale_11', 'stale_12', 'stale_13' etc %>
+ <% if item.completed? -%>
+ <%= format_date( item.completed_at ) %>
+ <% elsif item.deferred? -%>
+ <%= show_date( item.show_from ) -%>
+ <% else -%>
+ <%= due_date( item.due ) -%>
+ <% end -%>
+
+ <%= sanitize(item.description) %>
+
+ <% if item.deferred? && item.due -%>
+ (action due on <%= item.due.to_s %>)
+ <% end -%>
+
+ <% if item.completed? -%>
+ (<%= item.context.name %><%= ", " + item.project.name if item.project_id %>)
+ <% else -%>
+ <% if (parent_container_type == "project" || parent_container_type == "tickler") -%>
<%= item_link_to_context( item ) %>
- <% elsif item.project_id -%>
+ <% end -%>
+ <% if (parent_container_type == "context" || parent_container_type == "tickler") && item.project_id -%>
<%= item_link_to_project( item ) %>
- <% end -%>
-<% end -%>
-<% if item.notes? -%>
- <%= toggle_show_notes( item ) %>
-<% end -%>
-
+ <% end -%>
+ <% end -%>
+
+ <% if item.notes? -%>
+ <%= toggle_show_notes( item ) %>
+ <% end -%>
+
- <%= form_remote_tag_edit_todo( item, "immediate" ) -%>
+ <%= form_remote_tag_edit_todo( item ) -%>
<% #note: edit form will load here remotely -%>
<%= end_form_tag -%>
-
\ No newline at end of file
+
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