mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-29 05:18:49 +01:00
Merge branch 'bsag'
Conflicts: app/views/todos/_todo.html.erb
This commit is contained in:
commit
28de00791f
220 changed files with 4775 additions and 14363 deletions
|
|
@ -27,6 +27,7 @@ class ApplicationController < ActionController::Base
|
|||
helper_method :current_user, :prefs
|
||||
|
||||
layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
|
||||
exempt_from_layout /\.js\.erb$/
|
||||
|
||||
before_filter :set_session_expiration
|
||||
before_filter :set_time_zone
|
||||
|
|
@ -129,14 +130,6 @@ class ApplicationController < ActionController::Base
|
|||
RedCloth.new(text).to_html
|
||||
end
|
||||
|
||||
def build_default_project_context_name_map(projects)
|
||||
Hash[*projects.reject{ |p| p.default_context.nil? }.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
|
||||
end
|
||||
|
||||
def build_default_project_tags_map(projects)
|
||||
Hash[*projects.reject{ |p| p.default_tags.nil? }.map{ |p| [p.name, p.default_tags] }.flatten].to_json
|
||||
end
|
||||
|
||||
# Here's the concept behind this "mobile content negotiation" hack: In
|
||||
# addition to the main, AJAXy Web UI, Tracks has a lightweight low-feature
|
||||
# 'mobile' version designed to be suitablef or use from a phone or PDA. It
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class ContextsController < ApplicationController
|
|||
|
||||
if @context.save
|
||||
if boolean_param('wants_render')
|
||||
@context_state_changed = (@orgininal_context_hidden != @context.hidden?)
|
||||
@context_state_changed = (@original_context_hidden != @context.hidden?)
|
||||
@new_state = (@context.hidden? ? "hidden" : "active") if @context_state_changed
|
||||
respond_to do |format|
|
||||
format.js
|
||||
|
|
@ -110,6 +110,13 @@ class ContextsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
@context = Context.find(params[:id])
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
# Fairly self-explanatory; deletes the context If the context contains
|
||||
# actions, you'll get a warning dialogue. If you choose to go ahead, any
|
||||
# actions in the context will also be deleted.
|
||||
|
|
@ -124,11 +131,12 @@ class ContextsController < ApplicationController
|
|||
# Methods for changing the sort order of the contexts in the list
|
||||
#
|
||||
def order
|
||||
list = params["list-contexts-hidden"] || params["list-contexts-active"]
|
||||
list.each_with_index do |id, position|
|
||||
current_user.contexts.update(id, :position => position + 1)
|
||||
end
|
||||
context_ids = params["container_context"]
|
||||
@projects = current_user.contexts.update_positions( context_ids )
|
||||
render :nothing => true
|
||||
rescue
|
||||
notify :error, $!
|
||||
redirect_to :action => 'index'
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
@ -218,8 +226,6 @@ class ContextsController < ApplicationController
|
|||
@projects = current_user.projects
|
||||
|
||||
@count = @not_done_todos.size
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
@default_project_tags_map = build_default_project_tags_map(@projects).to_json
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
class IntegrationsController < ApplicationController
|
||||
|
||||
skip_before_filter :login_required, :only => :search_plugin
|
||||
skip_before_filter :login_required, :only => [:search_plugin, :google_gadget]
|
||||
|
||||
def index
|
||||
@page_title = 'TRACKS::Integrations'
|
||||
|
|
@ -32,4 +32,8 @@ class IntegrationsController < ApplicationController
|
|||
render :layout => false
|
||||
end
|
||||
|
||||
def google_gadget
|
||||
render :layout => false, :content_type => Mime::XML
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -47,14 +47,14 @@ class ProjectsController < ApplicationController
|
|||
|
||||
@not_done = @project.not_done_todos_including_hidden
|
||||
@deferred = @project.deferred_todos
|
||||
@pending = @project.pending_todos
|
||||
@done = @project.todos.find_in_state(:all, :completed, :order => "todos.completed_at DESC", :limit => current_user.prefs.show_number_completed, :include => [:context])
|
||||
|
||||
@count = @not_done.size
|
||||
@down_count = @count + @deferred.size
|
||||
@down_count = @count + @deferred.size + @pending.size
|
||||
@next_project = current_user.projects.next_from(@project)
|
||||
@previous_project = current_user.projects.previous_from(@project)
|
||||
@default_project_context_name_map = build_default_project_context_name_map(current_user.projects).to_json
|
||||
@default_project_tags_map = build_default_project_tags_map(current_user.projects).to_json
|
||||
@default_tags = @project.default_tags
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.m &render_project_mobile
|
||||
|
|
@ -92,7 +92,7 @@ class ProjectsController < ApplicationController
|
|||
elsif @project.new_record?
|
||||
render_failure @project.errors.full_messages.join(', ')
|
||||
else
|
||||
head :created, :location => project_url(@project)
|
||||
head :created, :location => project_url(@project), :text => @project.id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -127,6 +127,7 @@ class ProjectsController < ApplicationController
|
|||
@active_projects_count = current_user.projects.active.count
|
||||
@hidden_projects_count = current_user.projects.hidden.count
|
||||
@completed_projects_count = current_user.projects.completed.count
|
||||
init_data_for_sidebar
|
||||
render :template => 'projects/update.js.rjs'
|
||||
return
|
||||
elsif boolean_param('update_status')
|
||||
|
|
@ -173,7 +174,7 @@ class ProjectsController < ApplicationController
|
|||
end
|
||||
|
||||
def order
|
||||
project_ids = params["list-active-projects"] || params["list-hidden-projects"] || params["list-completed-projects"]
|
||||
project_ids = params["container_project"]
|
||||
@projects = current_user.projects.update_positions( project_ids )
|
||||
render :nothing => true
|
||||
rescue
|
||||
|
|
@ -186,6 +187,7 @@ class ProjectsController < ApplicationController
|
|||
@projects = current_user.projects.alphabetize(:state => @state) if @state
|
||||
@contexts = current_user.contexts
|
||||
init_not_done_counts(['project'])
|
||||
init_project_hidden_todo_counts(['project']) if @state == 'hidden'
|
||||
end
|
||||
|
||||
def actionize
|
||||
|
|
@ -193,6 +195,7 @@ class ProjectsController < ApplicationController
|
|||
@projects = current_user.projects.actionize(current_user.id, :state => @state) if @state
|
||||
@contexts = current_user.contexts
|
||||
init_not_done_counts(['project'])
|
||||
init_project_hidden_todo_counts(['project']) if @state == 'hidden'
|
||||
end
|
||||
|
||||
protected
|
||||
|
|
@ -293,4 +296,4 @@ class ProjectsController < ApplicationController
|
|||
project_description
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -251,8 +251,6 @@ class RecurringTodosController < ApplicationController
|
|||
@xth_day = [['first',1],['second',2],['third',3],['fourth',4],['last',5]]
|
||||
@projects = current_user.projects.find(:all, :include => [:default_context])
|
||||
@contexts = current_user.contexts.find(:all)
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
@default_project_tags_map = build_default_project_tags_map(@projects).to_json
|
||||
end
|
||||
|
||||
def get_recurring_todo_from_param
|
||||
|
|
@ -265,4 +263,4 @@ class RecurringTodosController < ApplicationController
|
|||
recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0}
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ class TodosController < ApplicationController
|
|||
prepend_before_filter :login_or_feed_token_required, :only => [:index, :calendar]
|
||||
append_before_filter :init, :except => [ :destroy, :completed,
|
||||
:completed_archive, :check_deferred, :toggle_check, :toggle_star,
|
||||
:edit, :update, :create, :calendar, :auto_complete_for_tag]
|
||||
append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy ]
|
||||
protect_from_forgery :except => [:auto_complete_for_tag]
|
||||
:edit, :update, :create, :calendar, :auto_complete_for_tag, :auto_complete_for_predecessor, :remove_predecessor, :add_predecessor]
|
||||
append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :toggle_star, :show, :update, :destroy, :remove_predecessor]
|
||||
protect_from_forgery :except => [:auto_complete_for_tag, :auto_complete_for_predecessor]
|
||||
|
||||
session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
|
||||
|
||||
|
|
@ -50,9 +50,11 @@ class TodosController < ApplicationController
|
|||
|
||||
def create
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
@tag_name = params['_tag_name']
|
||||
p = TodoCreateParamsHelper.new(params, prefs)
|
||||
p.parse_dates() unless mobile?
|
||||
tag_list = p.tag_list
|
||||
predecessor_list = p.predecessor_list
|
||||
|
||||
@todo = current_user.todos.build(p.attributes)
|
||||
|
||||
|
|
@ -69,13 +71,21 @@ class TodosController < ApplicationController
|
|||
@todo.context_id = context.id
|
||||
end
|
||||
|
||||
@todo.add_predecessor_list(predecessor_list)
|
||||
@todo.update_state_from_project
|
||||
@saved = @todo.save
|
||||
unless (@saved == false) || tag_list.blank?
|
||||
@todo.tag_with(tag_list)
|
||||
@todo.tags.reload
|
||||
end
|
||||
|
||||
|
||||
unless (@aved == false)
|
||||
unless @todo.uncompleted_predecessors.empty? || @todo.state == 'project_hidden'
|
||||
@todo.state = 'pending'
|
||||
end
|
||||
@todo.save
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to :action => "index" }
|
||||
format.m do
|
||||
|
|
@ -94,6 +104,7 @@ class TodosController < ApplicationController
|
|||
@projects = current_user.projects.find(:all) if @new_project_created
|
||||
@initial_context_name = params['default_context_name']
|
||||
@initial_project_name = params['default_project_name']
|
||||
@default_tags = @todo.project.default_tags unless @todo.project.nil?
|
||||
render :action => 'create'
|
||||
end
|
||||
format.xml do
|
||||
|
|
@ -128,6 +139,30 @@ class TodosController < ApplicationController
|
|||
format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
|
||||
end
|
||||
end
|
||||
|
||||
def add_predecessor
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
@predecessor = Todo.find(params['predecessor'])
|
||||
@todo = Todo.find(params['successor'])
|
||||
@original_state = @todo.state
|
||||
# Add predecessor
|
||||
@todo.add_predecessor(@predecessor)
|
||||
@todo.state = 'pending'
|
||||
@saved = @todo.save
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
def remove_predecessor
|
||||
@source_view = params['_source_view'] || 'todo'
|
||||
@predecessor = Todo.find(params['predecessor'])
|
||||
@successor = @todo
|
||||
@removed = @successor.remove_predecessor(@predecessor)
|
||||
respond_to do |format|
|
||||
format.js
|
||||
end
|
||||
end
|
||||
|
||||
# Toggles the 'done' status of the action
|
||||
#
|
||||
|
|
@ -140,6 +175,18 @@ class TodosController < ApplicationController
|
|||
# check if this todo has a related recurring_todo. If so, create next todo
|
||||
@new_recurring_todo = check_for_next_todo(@todo) if @saved
|
||||
|
||||
if @todo.completed?
|
||||
@pending_to_activate = @todo.pending_to_activate
|
||||
@pending_to_activate.each do |t|
|
||||
t.activate!
|
||||
end
|
||||
else
|
||||
@active_to_block = @todo.active_to_block
|
||||
@active_to_block.each do |t|
|
||||
t.block!
|
||||
end
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.js do
|
||||
if @saved
|
||||
|
|
@ -188,7 +235,8 @@ class TodosController < ApplicationController
|
|||
@original_item_project_id = @todo.project_id
|
||||
@original_item_was_deferred = @todo.deferred?
|
||||
@original_item_due = @todo.due
|
||||
@original_item_due_id = get_due_id_for_calendar(@todo.due)
|
||||
@original_item_due_id = get_due_id_for_calendar(@todo.due)
|
||||
@original_item_predecessor_list = @todo.predecessors.map{|t| t.specification}.join(', ')
|
||||
|
||||
if params['todo']['project_id'].blank? && !params['project_name'].nil?
|
||||
if params['project_name'] == 'None'
|
||||
|
|
@ -229,15 +277,38 @@ class TodosController < ApplicationController
|
|||
|
||||
if params['done'] == '1' && !@todo.completed?
|
||||
@todo.complete!
|
||||
@todo.pending_to_activate.each do |t|
|
||||
t.activate!
|
||||
end
|
||||
end
|
||||
# strange. if checkbox is not checked, there is no 'done' in params.
|
||||
# Therefore I've used the negation
|
||||
if !(params['done'] == '1') && @todo.completed?
|
||||
@todo.activate!
|
||||
@todo.active_to_block.each do |t|
|
||||
t.block!
|
||||
end
|
||||
end
|
||||
|
||||
@todo.attributes = params["todo"]
|
||||
|
||||
@todo.add_predecessor_list(params[:predecessor_list])
|
||||
@saved = @todo.save
|
||||
if @saved && params[:predecessor_list]
|
||||
if @original_item_predecessor_list != params[:predecessor_list]
|
||||
# Possible state change with new dependencies
|
||||
if @todo.uncompleted_predecessors.empty?
|
||||
if @todo.state == 'pending'
|
||||
@todo.activate! # Activate pending if no uncompleted predecessors
|
||||
end
|
||||
else
|
||||
if @todo.state == 'active'
|
||||
@todo.block! # Block active if we got uncompleted predecessors
|
||||
end
|
||||
end
|
||||
end
|
||||
@todo.save!
|
||||
end
|
||||
|
||||
@context_changed = @original_item_context_id != @todo.context_id
|
||||
@todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
|
||||
|
|
@ -295,16 +366,32 @@ class TodosController < ApplicationController
|
|||
@context_id = @todo.context_id
|
||||
@project_id = @todo.project_id
|
||||
|
||||
# activate successors if they only depend on this todo
|
||||
activated_successor_count = 0
|
||||
@pending_to_activate = []
|
||||
@todo.pending_successors.each do |successor|
|
||||
successor.uncompleted_predecessors.delete(@todo)
|
||||
if successor.uncompleted_predecessors.empty?
|
||||
successor.activate!
|
||||
@pending_to_activate << successor
|
||||
activated_successor_count += 1
|
||||
end
|
||||
end
|
||||
|
||||
@saved = @todo.destroy
|
||||
|
||||
# check if this todo has a related recurring_todo. If so, create next todo
|
||||
@new_recurring_todo = check_for_next_todo(@todo) if @saved
|
||||
|
||||
|
||||
respond_to do |format|
|
||||
|
||||
format.html do
|
||||
if @saved
|
||||
notify :notice, "Successfully deleted next action", 2.0
|
||||
message = "Successfully deleted next action"
|
||||
if activated_successor_count > 0
|
||||
message += " activated #{pluralize(activated_successor_count, 'pending action')}"
|
||||
end
|
||||
notify :notice, message, 2.0
|
||||
redirect_to :action => 'index'
|
||||
else
|
||||
notify :error, "Failed to delete the action", 2.0
|
||||
|
|
@ -354,11 +441,9 @@ class TodosController < ApplicationController
|
|||
@contexts_to_show = @contexts = current_user.contexts.find(:all, :include => [ :todos ])
|
||||
|
||||
current_user.deferred_todos.find_and_activate_ready
|
||||
@not_done_todos = current_user.deferred_todos
|
||||
@not_done_todos = current_user.deferred_todos + current_user.pending_todos
|
||||
@count = @not_done_todos.size
|
||||
@down_count = @count
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
@default_project_tags_map = build_default_project_tags_map(@projects).to_json
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
|
@ -409,6 +494,9 @@ class TodosController < ApplicationController
|
|||
@deferred = tag_collection.find(:all,
|
||||
:conditions => ['todos.user_id = ? and state = ?', current_user.id, 'deferred'],
|
||||
:order => 'show_from ASC, todos.created_at DESC')
|
||||
@pending = tag_collection.find(:all,
|
||||
:conditions => ['todos.user_id = ? and state = ?', current_user.id, 'pending'],
|
||||
:order => 'show_from ASC, todos.created_at DESC')
|
||||
|
||||
# If you've set no_completed to zero, the completed items box isn't shown on
|
||||
# the tag page
|
||||
|
|
@ -421,16 +509,19 @@ class TodosController < ApplicationController
|
|||
@projects = current_user.projects
|
||||
@contexts = current_user.contexts
|
||||
@contexts_to_show = @contexts.reject {|x| x.hide? }
|
||||
|
||||
# Set defaults for new_action
|
||||
@initial_tag_name = @tag_name
|
||||
unless @not_done_todos.empty?
|
||||
@context = current_user.contexts.find_by_id(@not_done_todos[0].context_id)
|
||||
end
|
||||
|
||||
# Set count badge to number of items with this tag
|
||||
@not_done_todos.empty? ? @count = 0 : @count = @not_done_todos.size
|
||||
@down_count = @count
|
||||
|
||||
respond_to do |format|
|
||||
format.html {
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
@default_project_tags_map = build_default_project_tags_map(@projects).to_json
|
||||
}
|
||||
format.html
|
||||
format.m {
|
||||
cookies[:mobile_url]= {:value => request.request_uri, :secure => SITE_CONFIG['secure_cookies']}
|
||||
render :action => "mobile_tag"
|
||||
|
|
@ -464,8 +555,6 @@ class TodosController < ApplicationController
|
|||
@page_title = "TRACKS::Calendar"
|
||||
|
||||
@projects = current_user.projects.find(:all)
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
@default_project_tags_map = build_default_project_tags_map(@projects).to_json
|
||||
|
||||
due_today_date = Time.zone.now
|
||||
due_this_week_date = Time.zone.now.end_of_week
|
||||
|
|
@ -506,12 +595,52 @@ class TodosController < ApplicationController
|
|||
|
||||
def auto_complete_for_tag
|
||||
@items = Tag.find(:all,
|
||||
:conditions => [ "name LIKE ?", '%' + params['tag_list'] + '%' ],
|
||||
:order => "name ASC",
|
||||
:limit => 10)
|
||||
:conditions => [ "name LIKE ?", '%' + params['tag_list'] + '%' ],
|
||||
:order => "name ASC",
|
||||
:limit => 10)
|
||||
render :inline => "<%= auto_complete_result(@items, :name) %>"
|
||||
end
|
||||
|
||||
def auto_complete_for_predecessor
|
||||
unless params['id'].nil?
|
||||
get_todo_from_params
|
||||
# Begin matching todos in current project
|
||||
@items = current_user.todos.find(:all,
|
||||
:select => 'description, project_id, context_id, created_at',
|
||||
:conditions => [ '(todos.state = ? OR todos.state = ?) AND ' +
|
||||
'NOT (id = ?) AND lower(description) LIKE ? AND project_id = ?',
|
||||
'active', 'pending',
|
||||
@todo.id,
|
||||
'%' + params[:predecessor_list].downcase + '%',
|
||||
@todo.project_id ],
|
||||
:order => 'description ASC',
|
||||
:limit => 10
|
||||
)
|
||||
if @items.empty? # Match todos in other projects
|
||||
@items = current_user.todos.find(:all,
|
||||
:select => 'description, project_id, context_id, created_at',
|
||||
:conditions => [ '(todos.state = ? OR todos.state = ?) AND ' +
|
||||
'NOT (id = ?) AND lower(description) LIKE ?',
|
||||
'active', 'pending',
|
||||
params[:id], '%' + params[:q].downcase + '%' ],
|
||||
:order => 'description ASC',
|
||||
:limit => 10
|
||||
)
|
||||
end
|
||||
else
|
||||
# New todo - TODO: Filter on project
|
||||
@items = current_user.todos.find(:all,
|
||||
:select => 'description, project_id, context_id, created_at',
|
||||
:conditions => [ '(todos.state = ? OR todos.state = ?) AND lower(description) LIKE ?',
|
||||
'active', 'pending',
|
||||
'%' + params[:q].downcase + '%' ],
|
||||
:order => 'description ASC',
|
||||
:limit => 10
|
||||
)
|
||||
end
|
||||
render :inline => "<%= auto_complete_result2(@items) %>"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_todo_from_params
|
||||
|
|
@ -669,6 +798,7 @@ class TodosController < ApplicationController
|
|||
unless @todo.project_id == nil
|
||||
@down_count = current_user.projects.find(@todo.project_id).not_done_todos_including_hidden.count
|
||||
@deferred_count = current_user.projects.find(@todo.project_id).deferred_todos.count
|
||||
@pending_count = current_user.projects.find(@todo.project_id).pending_todos.count
|
||||
end
|
||||
end
|
||||
from.deferred do
|
||||
|
|
@ -718,10 +848,15 @@ class TodosController < ApplicationController
|
|||
end
|
||||
|
||||
def determine_deferred_tag_count(tag)
|
||||
tag_collection = Tag.find_by_name(tag).todos
|
||||
@deferred_tag_count = tag_collection.count(:all,
|
||||
:conditions => ['todos.user_id = ? and state = ?', current_user.id, 'deferred'],
|
||||
:order => 'show_from ASC, todos.created_at DESC')
|
||||
tags = Tag.find_by_name(tag)
|
||||
if tags.nil?
|
||||
# should normally not happen, but is a workaround for #929
|
||||
@deferred_tag_count = 0
|
||||
else
|
||||
@deferred_tag_count = tags.todos.count(:all,
|
||||
:conditions => ['todos.user_id = ? and state = ?', current_user.id, 'deferred'],
|
||||
:order => 'show_from ASC, todos.created_at DESC')
|
||||
end
|
||||
end
|
||||
|
||||
def render_todos_html
|
||||
|
|
@ -745,9 +880,6 @@ class TodosController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
@default_project_context_name_map = build_default_project_context_name_map(@projects).to_json
|
||||
@default_project_tags_map = build_default_project_tags_map(@projects).to_json
|
||||
|
||||
render
|
||||
end
|
||||
end
|
||||
|
|
@ -953,6 +1085,10 @@ class TodosController < ApplicationController
|
|||
def tag_list
|
||||
@params['tag_list']
|
||||
end
|
||||
|
||||
def predecessor_list
|
||||
@params['predecessor_list']
|
||||
end
|
||||
|
||||
def parse_dates()
|
||||
@attributes['show_from'] = @prefs.parse_date(show_from)
|
||||
|
|
|
|||
|
|
@ -3,8 +3,7 @@ class UsersController < ApplicationController
|
|||
skip_before_filter :login_required, :only => [ :new, :create ]
|
||||
prepend_before_filter :login_optional, :only => [ :new, :create ]
|
||||
|
||||
# GET /users
|
||||
# GET /users.xml
|
||||
# GET /users GET /users.xml
|
||||
def index
|
||||
@users = User.find(:all, :order => 'login')
|
||||
respond_to do |format|
|
||||
|
|
@ -12,18 +11,17 @@ class UsersController < ApplicationController
|
|||
@page_title = "TRACKS::Manage Users"
|
||||
@users = User.paginate :page => params[:page], :order => 'login ASC'
|
||||
@total_users = User.count
|
||||
# When we call users/signup from the admin page
|
||||
# we store the URL so that we get returned here when signup is successful
|
||||
# When we call users/signup from the admin page we store the URL so that
|
||||
# we get returned here when signup is successful
|
||||
store_location
|
||||
end
|
||||
format.xml { render :xml => @users.to_xml(:except => [ :password ]) }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /users/somelogin
|
||||
# GET /users/somelogin.xml
|
||||
# GET /users/id GET /users/id.xml
|
||||
def show
|
||||
@user = User.find_by_login(params[:id])
|
||||
@user = User.find_by_id(params[:id])
|
||||
render :xml => @user.to_xml(:except => [ :password ])
|
||||
end
|
||||
|
||||
|
|
@ -46,13 +44,13 @@ class UsersController < ApplicationController
|
|||
render :layout => "login"
|
||||
end
|
||||
|
||||
# Example usage: curl -H 'Accept: application/xml' -H 'Content-Type: application/xml'
|
||||
# Example usage: curl -H 'Accept: application/xml' -H 'Content-Type:
|
||||
# application/xml'
|
||||
# -u admin:up2n0g00d
|
||||
# -d '<request><login>username</login><password>abc123</password></request>'
|
||||
# http://our.tracks.host/users
|
||||
#
|
||||
# POST /users
|
||||
# POST /users.xml
|
||||
# POST /users POST /users.xml
|
||||
def create
|
||||
if params['exception']
|
||||
render_failure "Expected post format is valid xml like so: <request><login>username</login><password>abc123</password></request>."
|
||||
|
|
@ -107,10 +105,9 @@ class UsersController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
# DELETE /users/somelogin
|
||||
# DELETE /users/somelogin.xml
|
||||
# DELETE /users/id DELETE /users/id.xml
|
||||
def destroy
|
||||
@deleted_user = User.find_by_login(params[:id])
|
||||
@deleted_user = User.find_by_id(params[:id])
|
||||
@saved = @deleted_user.destroy
|
||||
@total_users = User.find(:all).size
|
||||
|
||||
|
|
@ -150,9 +147,8 @@ class UsersController < ApplicationController
|
|||
if (params[:open_id_complete] || (params[:user][:auth_type] == 'open_id')) && openid_enabled?
|
||||
authenticate_with_open_id do |result, identity_url|
|
||||
if result.successful?
|
||||
# Success means that the transaction completed without
|
||||
# error. If info is nil, it means that the user cancelled
|
||||
# the verification.
|
||||
# Success means that the transaction completed without error. If info
|
||||
# is nil, it means that the user cancelled the verification.
|
||||
@user.auth_type = 'open_id'
|
||||
@user.open_id_url = identity_url
|
||||
if @user.save
|
||||
|
|
@ -207,5 +203,4 @@ class UsersController < ApplicationController
|
|||
return true
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -166,4 +166,17 @@ module ApplicationHelper
|
|||
return rt+rp+rts
|
||||
end
|
||||
|
||||
def date_format_for_date_picker()
|
||||
standard_format = current_user.prefs.date_format
|
||||
translations = [
|
||||
['%m', 'mm'],
|
||||
['%d', 'dd'],
|
||||
['%Y', 'yy'],
|
||||
['%y', 'y']
|
||||
]
|
||||
translations.inject(standard_format) do |str, translation|
|
||||
str.gsub(*translation)
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,19 +11,15 @@ module RecurringTodosHelper
|
|||
end
|
||||
|
||||
def recurring_todo_remote_delete_icon
|
||||
str = link_to( image_tag_for_delete,
|
||||
link_to( image_tag_for_delete,
|
||||
recurring_todo_path(@recurring_todo), :id => "delete_icon_"+@recurring_todo.id.to_s,
|
||||
:class => "icon delete_icon", :title => "delete the recurring action '#{@recurring_todo.description}'")
|
||||
set_behavior_for_delete_icon
|
||||
str
|
||||
end
|
||||
|
||||
def recurring_todo_remote_star_icon
|
||||
str = link_to( image_tag_for_star(@recurring_todo),
|
||||
link_to( image_tag_for_star(@recurring_todo),
|
||||
toggle_star_recurring_todo_path(@recurring_todo),
|
||||
:class => "icon star_item", :title => "star the action '#{@recurring_todo.description}'")
|
||||
set_behavior_for_star_icon
|
||||
str
|
||||
end
|
||||
|
||||
def recurring_todo_remote_edit_icon
|
||||
|
|
@ -31,7 +27,6 @@ module RecurringTodosHelper
|
|||
str = link_to( image_tag_for_edit(@recurring_todo),
|
||||
edit_recurring_todo_path(@recurring_todo),
|
||||
:class => "icon edit_icon")
|
||||
set_behavior_for_edit_icon
|
||||
else
|
||||
str = '<a class="icon">' + image_tag("blank.png") + "</a> "
|
||||
end
|
||||
|
|
@ -40,7 +35,6 @@ module RecurringTodosHelper
|
|||
|
||||
def recurring_todo_remote_toggle_checkbox
|
||||
str = check_box_tag('item_id', toggle_check_recurring_todo_path(@recurring_todo), @recurring_todo.completed?, :class => 'item-checkbox')
|
||||
set_behavior_for_toggle_checkbox
|
||||
str
|
||||
end
|
||||
|
||||
|
|
@ -53,26 +47,4 @@ module RecurringTodosHelper
|
|||
def image_tag_for_edit(todo)
|
||||
image_tag("blank.png", :title =>"Edit action", :class=>"edit_item", :id=> dom_id(todo, 'edit_icon'))
|
||||
end
|
||||
|
||||
def set_behavior_for_delete_icon
|
||||
parameters = "_source_view=#{@source_view}"
|
||||
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
|
||||
apply_behavior '.item-container a.delete_icon:click', :prevent_default => true do |page|
|
||||
page.confirming "'Are you sure that you want to ' + this.title + '?'" do
|
||||
page << "itemContainer = this.up('.item-container'); itemContainer.startWaiting();"
|
||||
page << remote_to_href(:method => 'delete', :with => "'#{parameters}'", :complete => "itemContainer.stopWaiting();")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def set_behavior_for_edit_icon
|
||||
parameters = "_source_view=#{@source_view}"
|
||||
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
|
||||
apply_behavior '.item-container a.edit_icon:click', :prevent_default => true do |page|
|
||||
page << "Effect.Pulsate(this);"
|
||||
page << remote_to_href(:method => 'get', :with => "'#{parameters}'")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -8,52 +8,41 @@ module TodosHelper
|
|||
end
|
||||
|
||||
def form_remote_tag_edit_todo( &block )
|
||||
form_tag(
|
||||
todo_path(@todo), {
|
||||
form_remote_tag(
|
||||
:url => todo_path(@todo),
|
||||
:loading => "$('#submit_todo_#{@todo.id}').block({message: null})",
|
||||
:html => {
|
||||
:method => :put,
|
||||
:id => dom_id(@todo, 'form'),
|
||||
:class => dom_id(@todo, 'form') + " inline-form edit_todo_form" },
|
||||
&block )
|
||||
apply_behavior 'form.edit_todo_form', make_remote_form(
|
||||
:method => :put,
|
||||
:before => "todoSpinner = this.down('button.positive'); todoSpinner.startWaiting()",
|
||||
:loaded => "todoSpinner.stopWaiting()",
|
||||
:condition => "!(this.down('button.positive').isWaiting())"),
|
||||
:prevent_default => true
|
||||
end
|
||||
|
||||
def set_behavior_for_star_icon
|
||||
apply_behavior '.item-container a.star_item:click',
|
||||
remote_to_href(:method => 'put', :with => "{ _source_view : '#{@source_view}' }"),
|
||||
:prevent_default => true
|
||||
end
|
||||
|
||||
def remote_star_icon
|
||||
str = link_to( image_tag_for_star(@todo),
|
||||
link_to( image_tag_for_star(@todo),
|
||||
toggle_star_todo_path(@todo),
|
||||
:class => "icon star_item", :title => "star the action '#{@todo.description}'")
|
||||
set_behavior_for_star_icon
|
||||
str
|
||||
end
|
||||
|
||||
def remote_edit_menu_item(parameters, todo)
|
||||
return link_to_remote(
|
||||
image_tag("edit_off.png", :mouseover => "edit_on.png", :alt => "", :align => "absmiddle")+" Edit",
|
||||
image_tag("edit_off.png", :mouseover => "edit_on.png", :alt => "Edit", :align => "absmiddle", :id => 'edit_icon_todo_'+todo.id.to_s)+" Edit",
|
||||
:url => {:controller => 'todos', :action => 'edit', :id => todo.id},
|
||||
:method => 'get',
|
||||
:with => "'#{parameters}'",
|
||||
:before => todo_start_waiting_js(todo),
|
||||
:complete => todo_stop_waiting_js)
|
||||
:complete => todo_stop_waiting_js(todo))
|
||||
end
|
||||
|
||||
def remote_delete_menu_item(parameters, todo)
|
||||
return link_to_remote(
|
||||
image_tag("delete_off.png", :mouseover => "delete_on.png", :alt => "", :align => "absmiddle")+" Delete",
|
||||
image_tag("delete_off.png", :mouseover => "delete_on.png", :alt => "Delete", :align => "absmiddle")+" Delete",
|
||||
:url => {:controller => 'todos', :action => 'destroy', :id => todo.id},
|
||||
:method => 'delete',
|
||||
:with => "'#{parameters}'",
|
||||
:before => todo_start_waiting_js(todo),
|
||||
:complete => todo_stop_waiting_js)
|
||||
:complete => todo_stop_waiting_js(todo),
|
||||
:confirm => "Are you sure that you want to delete the action '#{todo.description}'?")
|
||||
end
|
||||
|
||||
def remote_defer_menu_item(days, todo)
|
||||
|
|
@ -64,24 +53,28 @@ module TodosHelper
|
|||
futuredate = (@todo.show_from || @todo.user.date) + days.days
|
||||
if @todo.due && futuredate > @todo.due
|
||||
return link_to_function(
|
||||
image_tag("defer_#{days}_off.png", :mouseover => "defer_#{days}.png", :alt => "", :align => "absmiddle")+" Defer #{pluralize(days, "day")}",
|
||||
image_tag("defer_#{days}_off.png", :mouseover => "defer_#{days}.png", :alt => "Defer #{pluralize(days, "day")}", :align => "absmiddle")+" Defer #{pluralize(days, "day")}",
|
||||
"alert('Defer date is after due date. Please edit and adjust due date before deferring.')"
|
||||
)
|
||||
else
|
||||
return link_to_remote(
|
||||
image_tag("defer_#{days}_off.png", :mouseover => "defer_#{days}.png", :alt => "", :align => "absmiddle")+" Defer #{pluralize(days, "day")}",
|
||||
image_tag("defer_#{days}_off.png", :mouseover => "defer_#{days}.png", :alt => "Defer #{pluralize(days, "day")}", :align => "absmiddle")+" Defer #{pluralize(days, "day")}",
|
||||
:url => url,
|
||||
:before => todo_start_waiting_js(todo),
|
||||
:complete => todo_stop_waiting_js)
|
||||
:complete => todo_stop_waiting_js(todo))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def todo_start_waiting_js(todo)
|
||||
return "$('ul#{dom_id(todo)}').hide(); itemContainer = $('#{dom_id(todo)}'); itemContainer.startWaiting()"
|
||||
return "$('#ul#{dom_id(todo)}').css('visibility', 'hidden'); $('##{dom_id(todo)}').block({message: null})"
|
||||
end
|
||||
|
||||
def todo_stop_waiting_js
|
||||
return "itemContainer.stopWaiting();"
|
||||
def successor_start_waiting_js(successor)
|
||||
return "$('##{dom_id(successor, "successor")}').block({message: null})"
|
||||
end
|
||||
|
||||
def todo_stop_waiting_js(todo)
|
||||
return "$('##{dom_id(todo)}').unblock();enable_rich_interaction();"
|
||||
end
|
||||
|
||||
def image_tag_for_recurring_todo(todo)
|
||||
|
|
@ -91,23 +84,17 @@ module TodosHelper
|
|||
:class => "recurring_icon", :title => recurrence_pattern_as_text(todo.recurring_todo))
|
||||
end
|
||||
|
||||
def set_behavior_for_toggle_checkbox
|
||||
parameters = "_source_view=#{@source_view}"
|
||||
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
|
||||
apply_behavior '.item-container input.item-checkbox:click',
|
||||
remote_function(:url => javascript_variable('this.value'), :method => 'put',
|
||||
:with => "'#{parameters}'")
|
||||
end
|
||||
|
||||
def remote_toggle_checkbox
|
||||
str = check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox')
|
||||
set_behavior_for_toggle_checkbox
|
||||
str
|
||||
check_box_tag('item_id', toggle_check_todo_path(@todo), @todo.completed?, :class => 'item-checkbox',
|
||||
:title => @todo.pending? ? 'Blocked by ' + @todo.uncompleted_predecessors.map(&:description).join(', ') : "", :readonly => @todo.pending?)
|
||||
end
|
||||
|
||||
def date_span
|
||||
if @todo.completed?
|
||||
"<span class=\"grey\">#{format_date( @todo.completed_at )}</span>"
|
||||
elsif @todo.pending?
|
||||
"<a title='Depends on: #{@todo.uncompleted_predecessors.map(&:description).join(', ')}'><span class=\"orange\">Pending</span></a> "
|
||||
elsif @todo.deferred?
|
||||
show_date( @todo.show_from )
|
||||
else
|
||||
|
|
@ -115,6 +102,22 @@ module TodosHelper
|
|||
end
|
||||
end
|
||||
|
||||
def successors_span
|
||||
unless @todo.pending_successors.empty?
|
||||
pending_count = @todo.pending_successors.length
|
||||
title = "Has #{pluralize(pending_count, 'pending action')}: #{@todo.pending_successors.map(&:description).join(', ')}"
|
||||
image_tag( 'successor_off.png', :width=>'10', :height=>'16', :border=>'0', :title => title )
|
||||
end
|
||||
end
|
||||
|
||||
def grip_span
|
||||
unless @todo.completed?
|
||||
image_tag('grip.png', :width => '7', :height => '16', :border => '0',
|
||||
:title => 'Drag onto another action to make it depend on that action',
|
||||
:class => 'grip')
|
||||
end
|
||||
end
|
||||
|
||||
def tag_list_text
|
||||
@todo.tags.collect{|t| t.name}.join(', ')
|
||||
end
|
||||
|
|
@ -135,6 +138,10 @@ module TodosHelper
|
|||
if tag_list.empty? then "" else "<span class=\"tags\">#{tag_list}</span>" end
|
||||
end
|
||||
|
||||
def predecessor_list_text
|
||||
@todo.predecessors.map{|t| t.specification}.join(', ')
|
||||
end
|
||||
|
||||
def deferred_due_date
|
||||
if @todo.deferred? && @todo.due
|
||||
"(action due on #{format_date(@todo.due)})"
|
||||
|
|
@ -213,23 +220,25 @@ module TodosHelper
|
|||
end
|
||||
|
||||
def calendar_setup( input_field )
|
||||
str = "Calendar.setup({ ifFormat:\"#{prefs.date_format}\""
|
||||
str << ",firstDay:#{prefs.week_starts},showOthers:true,range:[2004, 2010]"
|
||||
str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n"
|
||||
javascript_tag str
|
||||
# TODO:jQuery
|
||||
#str = "Calendar.setup({ ifFormat:\"#{prefs.date_format}\""
|
||||
#str << ",firstDay:#{prefs.week_starts},showOthers:true,range:[2004, 2010]"
|
||||
#str << ",step:1,inputField:\"" + input_field + "\",cache:true,align:\"TR\" })\n"
|
||||
#javascript_tag str
|
||||
end
|
||||
|
||||
def item_container_id (todo)
|
||||
if source_view_is :project
|
||||
return "p#{todo.project_id}items" if todo.active?
|
||||
return "tickler" if todo.deferred?
|
||||
if todo.deferred? or todo.pending?
|
||||
return "tickleritems"
|
||||
elsif source_view_is :project
|
||||
return "p#{todo.project_id}items"
|
||||
end
|
||||
return "c#{todo.context_id}"
|
||||
return "c#{todo.context_id}items"
|
||||
end
|
||||
|
||||
def should_show_new_item
|
||||
|
||||
if @todo.project.nil? == false
|
||||
unless @todo.project.nil?
|
||||
# do not show new actions that were added to hidden or completed projects
|
||||
# on home page and context page
|
||||
return false if source_view_is(:todo) && (@todo.project.hidden? || @todo.project.completed?)
|
||||
|
|
@ -240,6 +249,8 @@ module TodosHelper
|
|||
return true if source_view_is(:project) && @todo.project.hidden? && @todo.project_hidden?
|
||||
return true if source_view_is(:project) && @todo.deferred?
|
||||
return true if !source_view_is(:deferred) && @todo.active?
|
||||
return true if source_view_is(:project) && @todo.pending?
|
||||
return true if source_view_is(:tag) && @todo.pending?
|
||||
return false
|
||||
end
|
||||
|
||||
|
|
@ -266,6 +277,20 @@ module TodosHelper
|
|||
array_or_string_for_javascript( current_user.contexts.collect{|c| escape_javascript(c.name) } )
|
||||
end
|
||||
|
||||
def tag_names_for_autocomplete
|
||||
array_or_string_for_javascript( Tag.all.collect{|c| escape_javascript(c.name) } )
|
||||
end
|
||||
|
||||
def default_contexts_for_autocomplete
|
||||
projects = current_user.projects.find(:all, :conditions => ['default_context_id is not null'])
|
||||
Hash[*projects.map{ |p| [p.name, p.default_context.name] }.flatten].to_json
|
||||
end
|
||||
|
||||
def default_tags_for_autocomplete
|
||||
projects = current_user.projects.find(:all, :conditions => ["default_tags != ''"])
|
||||
Hash[*projects.map{ |p| [p.name, p.default_tags] }.flatten].to_json
|
||||
end
|
||||
|
||||
def format_ical_notes(notes)
|
||||
split_notes = notes.split(/\n/)
|
||||
joined_notes = split_notes.join("\\n")
|
||||
|
|
@ -286,4 +311,9 @@ module TodosHelper
|
|||
class_str = todo.starred? ? "starred_todo" : "unstarred_todo"
|
||||
image_tag("blank.png", :title =>"Star action", :class => class_str)
|
||||
end
|
||||
|
||||
def auto_complete_result2(entries, phrase = nil)
|
||||
return entries.map{|e| e.specification()}.join("\n") rescue ''
|
||||
end
|
||||
|
||||
end
|
||||
|
|
|
|||
7
app/models/dependency.rb
Normal file
7
app/models/dependency.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class Dependency < ActiveRecord::Base
|
||||
|
||||
belongs_to :predecessor, :foreign_key => 'predecessor_id', :class_name => 'Todo'
|
||||
belongs_to :successor, :foreign_key => 'successor_id', :class_name => 'Todo'
|
||||
|
||||
end
|
||||
|
||||
|
|
@ -20,6 +20,11 @@ class Project < ActiveRecord::Base
|
|||
:class_name => 'Todo',
|
||||
:conditions => ["todos.state = ? ", "deferred"],
|
||||
:order => "show_from"
|
||||
has_many :pending_todos,
|
||||
:include => [:context,:tags,:project],
|
||||
:class_name => 'Todo',
|
||||
:conditions => ["todos.state = ? ", "pending"],
|
||||
:order => "show_from"
|
||||
|
||||
has_many :notes, :dependent => :delete_all, :order => "created_at DESC"
|
||||
|
||||
|
|
|
|||
|
|
@ -4,12 +4,26 @@ class Todo < ActiveRecord::Base
|
|||
belongs_to :project
|
||||
belongs_to :user
|
||||
belongs_to :recurring_todo
|
||||
|
||||
has_many :predecessor_dependencies, :foreign_key => 'predecessor_id', :class_name => 'Dependency', :dependent => :destroy
|
||||
has_many :successor_dependencies, :foreign_key => 'successor_id', :class_name => 'Dependency', :dependent => :destroy
|
||||
has_many :predecessors, :through => :successor_dependencies
|
||||
has_many :successors, :through => :predecessor_dependencies
|
||||
has_many :uncompleted_predecessors, :through => :successor_dependencies,
|
||||
:source => :predecessor, :conditions => ['NOT (state = ?)', 'completed']
|
||||
has_many :pending_successors, :through => :predecessor_dependencies,
|
||||
:source => :successor, :conditions => ['state = ?', 'pending']
|
||||
|
||||
after_save :save_predecessors
|
||||
|
||||
named_scope :active, :conditions => { :state => 'active' }
|
||||
named_scope :not_completed, :conditions => ['NOT (state = ? )', 'completed']
|
||||
named_scope :are_due, :conditions => ['NOT (todos.due IS NULL)']
|
||||
|
||||
STARRED_TAG_NAME = "starred"
|
||||
RE_TODO = /[^"]+/
|
||||
RE_CONTEXT = /[^"]+/
|
||||
RE_PROJECT = /[^"]+/
|
||||
|
||||
acts_as_state_machine :initial => :active, :column => 'state'
|
||||
|
||||
|
|
@ -19,6 +33,7 @@ class Todo < ActiveRecord::Base
|
|||
state :project_hidden
|
||||
state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil }
|
||||
state :deferred
|
||||
state :pending
|
||||
|
||||
event :defer do
|
||||
transitions :to => :deferred, :from => [:active]
|
||||
|
|
@ -30,6 +45,8 @@ class Todo < ActiveRecord::Base
|
|||
|
||||
event :activate do
|
||||
transitions :to => :active, :from => [:project_hidden, :completed, :deferred]
|
||||
transitions :to => :active, :from => [:pending], :guard => :no_uncompleted_predecessors_or_deferral?
|
||||
transitions :to => :deferred, :from => [:pending], :guard => :no_uncompleted_predecessors?
|
||||
end
|
||||
|
||||
event :hide do
|
||||
|
|
@ -40,6 +57,10 @@ class Todo < ActiveRecord::Base
|
|||
transitions :to => :deferred, :from => [:project_hidden], :guard => Proc.new{|t| !t.show_from.blank? }
|
||||
transitions :to => :active, :from => [:project_hidden]
|
||||
end
|
||||
|
||||
event :block do
|
||||
transitions :to => :pending, :from => [:active, :deferred]
|
||||
end
|
||||
|
||||
attr_protected :user
|
||||
|
||||
|
|
@ -51,15 +72,120 @@ class Todo < ActiveRecord::Base
|
|||
validates_presence_of :show_from, :if => :deferred?
|
||||
validates_presence_of :context
|
||||
|
||||
def initialize(*args)
|
||||
super(*args)
|
||||
@predecessor_array = nil # Used for deferred save of predecessors
|
||||
end
|
||||
|
||||
def no_uncompleted_predecessors_or_deferral?
|
||||
return (show_from.blank? or Time.zone.now > show_from and uncompleted_predecessors.empty?)
|
||||
end
|
||||
|
||||
def no_uncompleted_predecessors?
|
||||
return uncompleted_predecessors.empty?
|
||||
end
|
||||
|
||||
|
||||
# Returns a string with description <context, project>
|
||||
def specification
|
||||
project_name = project.is_a?(NullProject) ? "(none)" : project.name
|
||||
return "\"#{description}\" <\"#{context.title}\"; \"#{project_name}\">"
|
||||
end
|
||||
|
||||
def todo_from_specification(specification)
|
||||
# Split specification into parts: description <context, project>
|
||||
re_parts = /"(#{RE_TODO})"\s<"(#{RE_CONTEXT})";\s"(#{RE_PROJECT})">/
|
||||
parts = specification.scan(re_parts)
|
||||
return nil unless parts.length == 1
|
||||
return nil unless parts[0].length == 3
|
||||
todo_description = parts[0][0]
|
||||
context_name = parts[0][1]
|
||||
todos = Todo.all(
|
||||
:joins => :context,
|
||||
:conditions => {
|
||||
:description => todo_description,
|
||||
:contexts => {:name => context_name}
|
||||
}
|
||||
)
|
||||
return nil if todos.empty?
|
||||
# todos now contains all todos with matching description and context
|
||||
# TODO: Is this possible to do with a single query?
|
||||
todos.each do |todo|
|
||||
project_name = todo.project.is_a?(NullProject) ? "(none)" : todo.project.name
|
||||
return todo if project_name == parts[0][2]
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def validate
|
||||
if !show_from.blank? && show_from < user.date
|
||||
errors.add("show_from", "must be a date in the future")
|
||||
end
|
||||
errors.add(:description, "may not contain \" characters") if /\"/.match(description)
|
||||
unless @predecessor_array.nil? # Only validate predecessors if they changed
|
||||
@predecessor_array.each do |specification|
|
||||
t = todo_from_specification(specification)
|
||||
if t.nil?
|
||||
errors.add("Depends on:", "Could not find action '#{h(specification)}'")
|
||||
else
|
||||
errors.add("Depends on:", "Adding '#{h(specification)}' would create a circular dependency") if is_successor?(t)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def save_predecessors
|
||||
unless @predecessor_array.nil? # Only save predecessors if they changed
|
||||
current_array = predecessors.map{|p| p.specification}
|
||||
remove_array = current_array - @predecessor_array
|
||||
add_array = @predecessor_array - current_array
|
||||
|
||||
# This is probably a bit naive code...
|
||||
remove_array.each do |specification|
|
||||
t = todo_from_specification(specification)
|
||||
self.predecessors.delete(t) unless t.nil?
|
||||
end
|
||||
# ... as is this?
|
||||
add_array.each do |specification|
|
||||
t = todo_from_specification(specification)
|
||||
unless t.nil?
|
||||
self.predecessors << t unless self.predecessors.include?(t)
|
||||
else
|
||||
logger.error "Could not find #{specification}" # Unexpected since validation passed
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def remove_predecessor(predecessor)
|
||||
# remove predecessor and activate myself
|
||||
predecessors.delete(predecessor)
|
||||
self.activate!
|
||||
end
|
||||
|
||||
# Returns true if t is equal to self or a successor of self
|
||||
def is_successor?(t)
|
||||
if self == t
|
||||
return true
|
||||
elsif self.successors.empty?
|
||||
return false
|
||||
else
|
||||
self.successors.each do |item|
|
||||
if item.is_successor?(t)
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
def update_state_from_project
|
||||
if state == 'project_hidden' and !project.hidden?
|
||||
self.state = 'active'
|
||||
if self.uncompleted_predecessors.empty?
|
||||
self.state = 'active'
|
||||
else
|
||||
self.state = 'pending'
|
||||
end
|
||||
elsif state == 'active' and project.hidden?
|
||||
self.state = 'project_hidden'
|
||||
end
|
||||
|
|
@ -92,7 +218,7 @@ class Todo < ActiveRecord::Base
|
|||
def project
|
||||
original_project.nil? ? Project.null_object : original_project
|
||||
end
|
||||
|
||||
|
||||
alias_method :original_set_initial_state, :set_initial_state
|
||||
|
||||
def set_initial_state
|
||||
|
|
@ -138,6 +264,28 @@ class Todo < ActiveRecord::Base
|
|||
def from_recurring_todo?
|
||||
return self.recurring_todo_id != nil
|
||||
end
|
||||
|
||||
def add_predecessor_list(predecessor_list)
|
||||
return unless predecessor_list.kind_of? String
|
||||
# Split into list
|
||||
re_specification = /"#{RE_TODO}"\s<"#{RE_CONTEXT}";\s"#{RE_PROJECT}">/
|
||||
@predecessor_array = predecessor_list.scan(re_specification)
|
||||
end
|
||||
|
||||
def add_predecessor(t)
|
||||
@predecessor_array = predecessors.map{|p| p.specification}
|
||||
@predecessor_array << t.specification
|
||||
end
|
||||
|
||||
# Return todos that should be activated if the current todo is completed
|
||||
def pending_to_activate
|
||||
return successors.find_all {|t| t.uncompleted_predecessors.empty?}
|
||||
end
|
||||
|
||||
# Return todos that should be blocked if the current todo is undone
|
||||
def active_to_block
|
||||
return successors.find_all {|t| t.active? or t.deferred?}
|
||||
end
|
||||
|
||||
# Rich Todo API
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,13 @@ class User < ActiveRecord::Base
|
|||
def find_by_params(params)
|
||||
find(params['id'] || params['context_id']) || nil
|
||||
end
|
||||
def update_positions(context_ids)
|
||||
context_ids.each_with_index do |id, position|
|
||||
context = self.detect { |c| c.id == id.to_i }
|
||||
raise "Context id #{id} not associated with user id #{@user.id}." if context.nil?
|
||||
context.update_attribute(:position, position + 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
has_many :projects,
|
||||
:order => 'projects.position ASC',
|
||||
|
|
@ -91,6 +98,10 @@ class User < ActiveRecord::Base
|
|||
find(:all, :conditions => ['show_from <= ?', Time.zone.now ]).collect { |t| t.activate! }
|
||||
end
|
||||
end
|
||||
has_many :pending_todos,
|
||||
:class_name => 'Todo',
|
||||
:conditions => [ 'state = ?', 'pending' ],
|
||||
:order => 'show_from ASC, todos.created_at DESC'
|
||||
has_many :completed_todos,
|
||||
:class_name => 'Todo',
|
||||
:conditions => ['todos.state = ? AND NOT(todos.completed_at IS NULL)', 'completed'],
|
||||
|
|
|
|||
|
|
@ -3,31 +3,9 @@
|
|||
<h2>
|
||||
<% if collapsible -%>
|
||||
<a href="#" class="container_toggle" id="toggle_c<%= context.id %>"><%= image_tag("collapse.png") %></a>
|
||||
<% apply_behavior '.container_toggle:click', :prevent_default => true do |page|
|
||||
page << " /* only handle the click if a previous click had finished its animation */
|
||||
if (todoItems.lastEffect == null || todoItems.lastEffect.state=='finished') {
|
||||
containerElem = this.up('.container')
|
||||
toggleTarget = containerElem.down('.toggle_target')
|
||||
if (Element.visible(toggleTarget))
|
||||
{
|
||||
todoItems.collapseNextActionListing(this, toggleTarget);
|
||||
todoItems.contextCollapseCookieManager.setCookie(todoItems.buildCookieName(containerElem), true)
|
||||
}
|
||||
else
|
||||
{
|
||||
todoItems.expandNextActionListing(this, toggleTarget);
|
||||
todoItems.contextCollapseCookieManager.clearCookie(todoItems.buildCookieName(containerElem))
|
||||
}
|
||||
}
|
||||
"
|
||||
end
|
||||
-%>
|
||||
<% end -%>
|
||||
<% if source_view_is :context %>
|
||||
<span class="in_place_editor_field" id="context_name_in_place_editor"><%= context.name %></span>
|
||||
<%= in_place_editor 'context_name_in_place_editor', {
|
||||
:url => { :controller => 'contexts', :action => 'update', :id => context.id, :field => 'name', :update_context_name => true, :escape => false},
|
||||
:options=>"{method:'put'}", :script => true } %>
|
||||
<span id="context_name"><%= context.name %></span>
|
||||
<% else %>
|
||||
<%= link_to_context( context ) %>
|
||||
<% end %>
|
||||
|
|
@ -37,11 +15,5 @@
|
|||
<div class="message"><p>Currently there are no incomplete actions in this context</p></div>
|
||||
</div>
|
||||
<%= render :partial => "todos/todo", :collection => @not_done, :locals => { :parent_container_type => "context" } %>
|
||||
<% if @not_done.empty?
|
||||
# fix (hack) for #713
|
||||
set_behavior_for_star_icon
|
||||
set_behavior_for_toggle_checkbox
|
||||
end
|
||||
-%>
|
||||
</div><!-- [end:items] -->
|
||||
</div><!-- [end:c<%= context.id %>] -->
|
||||
</div><!-- [end:c<%= context.id %>] -->
|
||||
|
|
|
|||
|
|
@ -1,35 +1,28 @@
|
|||
<% context = context_form
|
||||
@context = context-%>
|
||||
<div id="<%= dom_id(context, 'edit') %>" class="edit-form" style="display:none;">
|
||||
<% form_tag(context_path(context), {:id => dom_id(context, 'edit_form'), :class => "inline-form "+dom_id(context, 'edit_form')+"-edit-context-form edit-context-form", :method => :put}) do -%>
|
||||
<%= error_messages_for 'context' %>
|
||||
|
||||
<label for="context_name">Context name</label><br/>
|
||||
<%= text_field('context', 'name', :class => 'context-name') %><br/>
|
||||
<% form_remote_tag(:url => context_path(context), :html => {:id => dom_id(context, 'edit_form'), :class => "inline-form "+dom_id(context, 'edit_form')+"-edit-context-form edit-context-form", :method => :put}) do -%>
|
||||
<%= error_messages_for 'context' %>
|
||||
|
||||
<label for="context_hide">Hide from front page?</label>
|
||||
<%= check_box('context', 'hide', :class => 'context-hide') %>
|
||||
<input type="hidden" name="wants_render" value="true" />
|
||||
|
||||
<div class="submit_box">
|
||||
<div class="widgets">
|
||||
<button type="submit" class="positive" id="<%= dom_id(context, 'submit') %>" tabindex="15">
|
||||
<%=image_tag("accept.png", :alt => "") %>
|
||||
Update
|
||||
</button>
|
||||
<a href="javascript:void(0);" onclick="Element.toggle('<%= dom_id(context) %>');Element.toggle('<%= dom_id(context, 'edit') %>');" class="negative">
|
||||
<%=image_tag("cancel.png", :alt => "") %>
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<label for="context_name">Context name</label><br/>
|
||||
<%= text_field('context', 'name', :class => 'context-name') %><br/>
|
||||
|
||||
<label for="context_hide">Hide from front page?</label>
|
||||
<%= check_box('context', 'hide', :class => 'context-hide') %>
|
||||
<input type="hidden" name="wants_render" value="true" />
|
||||
|
||||
<div class="submit_box">
|
||||
<div class="widgets">
|
||||
<button type="submit" class="positive" id="<%= dom_id(context, 'submit') %>" tabindex="15">
|
||||
<%=image_tag("accept.png", :alt => "") %>
|
||||
Update
|
||||
</button>
|
||||
<a href="" onclick="" class="negative">
|
||||
<%=image_tag("cancel.png", :alt => "") %>
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
<br/><br/>
|
||||
|
||||
<% end %>
|
||||
<%= apply_behavior ".edit-context-form", make_remote_form(
|
||||
:before => "this.up('div.edit-form').down('button.positive').startWaiting()",
|
||||
:condition => "!(this.up('div.edit-form').down('button.positive')).isWaiting()"),
|
||||
:external => true
|
||||
@context = nil %>
|
||||
</div>
|
||||
</div>
|
||||
<br/><br/>
|
||||
|
||||
<% end %>
|
||||
|
||||
|
|
|
|||
|
|
@ -19,23 +19,25 @@
|
|||
<% else %>
|
||||
<span class="grey">VISIBLE</span>
|
||||
<% end %>
|
||||
<a class="delete_context_button" href="<%= formatted_context_path(context, :js) %>" title="delete the context '<%= context.name %>'"><%= image_tag( "blank.png", :title => "Delete context", :class=>"delete_item") %></a>
|
||||
<%= apply_behavior "a.delete_context_button:click", { :prevent_default => true, :external => true} do |page, element|
|
||||
page.confirming "'Are you sure that you want to ' + this.title + '?'" do
|
||||
element.up('.context').start_waiting
|
||||
page << remote_to_href(:method => 'delete')
|
||||
end
|
||||
end -%>
|
||||
<a class="edit_context_button" href="#"><%= image_tag( "blank.png", :title => "Edit context", :class=>"edit_item") %></a>
|
||||
<%= apply_behavior 'a.edit_context_button:click', :prevent_default => true do |page, element|
|
||||
element.up('.context').toggle
|
||||
editform = element.up('.list').down('.edit-form')
|
||||
editform.toggle
|
||||
editform.visual_effect(:appear)
|
||||
editform.down('input').focus
|
||||
end
|
||||
-%>
|
||||
<%= link_to_remote(
|
||||
image_tag( "blank.png", :title => "Delete context", :class=>"delete_item"),
|
||||
:url => {:controller => 'contexts', :action => 'destroy', :id => context.id},
|
||||
:method => 'delete',
|
||||
:with => "'_source_view=#{@source_view}'",
|
||||
:before => "$('#{dom_id(context)}').block({message:null});",
|
||||
:complete => "$('#{dom_id(context)}').unblock();",
|
||||
:confirm => "Are you sure that you want to delete the context '#{context.name}'?"
|
||||
) %>
|
||||
<%= link_to_remote(
|
||||
image_tag( "blank.png", :title => "Edit context", :class=>"edit_item"),
|
||||
:url => {:controller => 'contexts', :action => 'edit', :id => context.id},
|
||||
:method => 'get',
|
||||
:with => "'_source_view=#{@source_view}'",
|
||||
:before => "$('#{dom_id(context)}').block({message:null});",
|
||||
:complete => "$('#{dom_id(context)}').unblock();"
|
||||
) %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render :partial => 'contexts/context_form', :object => context %>
|
||||
</div>
|
||||
<div id="<%= dom_id(context, 'edit') %>" class="edit-form" style="display:none;">
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
if @saved
|
||||
container_name = 'list-contexts-' + (@context.hidden? ? 'hidden' : 'active')
|
||||
page.hide 'contexts-empty-nd'
|
||||
page.insert_html :bottom, "list-contexts", :partial => 'context_listing', :locals => { :context_listing => @context }
|
||||
page.sortable "list-contexts", get_listing_sortable_options
|
||||
page.insert_html :bottom, container_name, :partial => 'context_listing', :locals => { :context_listing => @context }
|
||||
page.hide 'status'
|
||||
page['badge_count'].replace_html @down_count
|
||||
page.call "Form.reset", "context-form"
|
||||
page.call "Form.focusFirstElement", "context-form"
|
||||
page << '$("#context-form").clearForm();'
|
||||
page << '$("#context-form input:text:first").focus();'
|
||||
else
|
||||
page.show 'status'
|
||||
page.replace_html 'status', "#{error_messages_for('context')}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
4
app/views/contexts/edit.js.rjs
Normal file
4
app/views/contexts/edit.js.rjs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
page[dom_id(@context, 'edit')].replace_html :partial => 'context_form', :locals => { :context_form => @context }
|
||||
page[@context].hide
|
||||
page[dom_id(@context, 'edit')].show
|
||||
page[dom_id(@context, 'edit_form')].find('input.context-name').focus
|
||||
|
|
@ -8,12 +8,6 @@
|
|||
|
||||
<div id="toggle_context_new" class="hide_form">
|
||||
<a title="Hide new context form" accesskey="n">« Hide form</a>
|
||||
<% apply_behavior '#toggle_context_new a:click', :prevent_default => true do |page|
|
||||
page << "TracksForm.toggle('toggle_context_new', 'context_new', 'context-form',
|
||||
'« Hide form', 'Hide new context form',
|
||||
'Create a new context »', 'Add a context');"
|
||||
end
|
||||
%>
|
||||
</div>
|
||||
|
||||
<div id="context_new" class="context_new" style="display:block">
|
||||
|
|
@ -21,9 +15,8 @@
|
|||
:url => contexts_path,
|
||||
:method => :post,
|
||||
:html=> { :id => 'context-form', :name => 'context', :class => 'inline-form'},
|
||||
:before => "$('context_new_submit').startWaiting()",
|
||||
:complete => "$('context_new_submit').stopWaiting()",
|
||||
:condition => "!$('context_new_submit').isWaiting()") do -%>
|
||||
:before => "$('#context_new_submit').block({message: null})",
|
||||
:complete => "$('#context_new_submit').unblock()") do -%>
|
||||
|
||||
<div id="status"><%= error_messages_for('context') %></div>
|
||||
|
||||
|
|
@ -49,8 +42,3 @@
|
|||
sortable_element 'list-contexts-active', get_listing_sortable_options
|
||||
sortable_element 'list-contexts-hidden', get_listing_sortable_options
|
||||
-%>
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#context_new_container","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,9 @@
|
|||
status_message = 'Context saved'
|
||||
page.notify :notice, status_message, 5.0
|
||||
if @context_state_changed
|
||||
page << "jQuery('##{dom_id(@context, 'edit')}').hide();"
|
||||
page.remove dom_id(@context, 'container')
|
||||
page.insert_html :bottom, "list-contexts-#{@new_state}", :partial => 'context_listing', :object => @context
|
||||
else
|
||||
page.replace_html dom_id(@context, 'container'), :partial => 'context_listing', :object => @context
|
||||
end
|
||||
page.sortable "list-contexts-active", get_listing_sortable_options
|
||||
page.sortable "list-contexts-hidden", get_listing_sortable_options
|
||||
|
||||
page.hide "busy"
|
||||
page.visual_effect :highlight, dom_id(@context), :duration => 3
|
||||
|
|
|
|||
|
|
@ -34,9 +34,3 @@
|
|||
</table>
|
||||
</div><!-- End of feeds -->
|
||||
</div>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#feedlegend","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -15,9 +15,3 @@
|
|||
</p>
|
||||
</div><!-- End of feeds -->
|
||||
</div><!-- End of display_box -->
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#feedlegend","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -125,11 +125,3 @@
|
|||
<div id="input_box">
|
||||
<%= render :file => "sidebar/sidebar.html.erb" %>
|
||||
</div><!-- End of input box -->
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#feedicons-project","normal");
|
||||
Nifty("div#feedicons-context","normal");
|
||||
Nifty("div#feedlegend","normal");
|
||||
}
|
||||
</script>
|
||||
|
|
|
|||
5
app/views/integrations/google_gadget.erb
Normal file
5
app/views/integrations/google_gadget.erb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<Module>
|
||||
<ModulePrefs title="Tracks" directory_title="Tracks" description="Gadget to add Tracks to Gmail as a gadget" author="Tracks" author_email="butshesagirl@rousette.org.uk" author_affiliation="Tracks" author_location="UK" title_url="http://www.getontracks.org/" screenshot="http://www.getontracks.org/images/uploads/tracks_home_thumb.png" thumbnail="http://www.getontracks.org/images/uploads/tracks_tickler.png" category="communication" category2="tools" height="300">
|
||||
</ModulePrefs>
|
||||
<Content type="url" href="<%= home_url %>mobile"/>
|
||||
</Module>
|
||||
|
|
@ -1,27 +1,23 @@
|
|||
<% has_contexts = !current_user.contexts.empty? -%>
|
||||
<h1>Integrations</h1>
|
||||
<p>Tracks can be integrated with a number of other tools... whatever it takes to help you get things done! This page has information on setting up some of these. Not all of these are applicable to all platforms, and some require more technical knowledge than others. See also <%= link_to "developer documentation for Tracks' REST API", url_for(:action => 'rest_api') %>.</p>
|
||||
<p>Contents:
|
||||
<ol>
|
||||
<p>Tracks can be integrated with a number of other tools...
|
||||
whatever it takes to help you get things done!
|
||||
This page has information on setting up some of these.
|
||||
Not all of these are applicable to all platforms, and some require more
|
||||
technical knowledge than others.
|
||||
See also <%= link_to "developer documentation for Tracks' REST API", url_for(:action => 'rest_api') %>.</p>
|
||||
<br/><p>Contents:</p>
|
||||
<ul>
|
||||
<li><a href="#applescript1-section">Add an Action with Applescript</a></li>
|
||||
<li><a href="#applescript2-section">Add an Action with Applescript based on the currently selected Email in Mail.app</a></li>
|
||||
<li><a href="#quicksilver-applescript-section">Add Actions with Quicksilver and Applescript</a></li>
|
||||
<li><a href="#email-cron-section">Automatically Email Yourself Upcoming Actions</a></li>
|
||||
</ol><br />
|
||||
</p>
|
||||
<p>Do you have one of your own to add? <a href="http://www.rousette.org.uk/projects/forums/viewforum/10/" title="Tracks | Tips and Tricks">Tell us about it in our Tips and Tricks forum
|
||||
</a> and we may include it on this page in a future versions of Tracks.</p>
|
||||
|
||||
<a name="message_gateway"> </a>
|
||||
<h2>Integrated email/SMS receiver</h2>
|
||||
<p>
|
||||
If Tracks is running on the same server as your mail server, you can use the integrated mail handler built into tracks. Steps to set it up:
|
||||
<ul>
|
||||
<li>Go to <%= link_to "Preferences", preferences_url %> and set your "From email" and "default email context" for todos sent in via email (which could come from an SMS message)</li>
|
||||
<li>In sendmail/qmail/postfix/whatever, set up an email address alias to pipe messages to <pre style="font-size:1.5em">/PATH/TO/RUBY/ruby /PATH/TO/TRACKS/script/runner -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
|
||||
<li>Send an email to your newly configured address!</li>
|
||||
</ul>
|
||||
You can also use the Rich Todo API to send in tasks like "do laundry @ Home" or "Call Bill > project X". The subject of the message will fill description, context, and project, while the body will populate the tasks's note.
|
||||
<li><a href="#message_gateway">Integrate Tracks with an email server to be able to send an action through email to Tracks</a></li>
|
||||
<li><a href="#google_gadget">Add Tracks as a Google Gmail gadget</a></li>
|
||||
</ul><br/>
|
||||
<p>Do you have one of your own to add?
|
||||
<a href="http://www.getontracks.org/forums/viewforum/10/" title="Tracks | Tips and Tricks">Tell us about
|
||||
it in our Tips and Tricks forum</a> and we may include it on this page in a future versions of Tracks.
|
||||
</p>
|
||||
|
||||
<a name="applescript1-section"> </a>
|
||||
|
|
@ -29,22 +25,22 @@ You can also use the Rich Todo API to send in tasks like "do laundry @ Home" or
|
|||
<p>This is a simple script that pops up a dialog box asking for a description, and then sends that to Tracks with a hard-coded context.</p>
|
||||
|
||||
<% if has_contexts -%>
|
||||
<ol>
|
||||
<li>Choose the context you want to add actions to: <select name="applescript1-contexts" id="applescript1-contexts"><%= options_from_collection_for_select(current_user.contexts, "id", "name", current_user.contexts.first.id) %></select>
|
||||
<%= observe_field "applescript1-contexts", :update => "applescript1",
|
||||
:with => 'context_id',
|
||||
:url => { :controller => "integrations", :action => "get_applescript1" },
|
||||
:before => "$('applescript1').startWaiting()",
|
||||
:complete => "$('applescript1').stopWaiting()"
|
||||
%>
|
||||
</li>
|
||||
<li>Copy the Applescript below to the clipboard.<br />
|
||||
|
||||
<textarea id="applescript1" name="applescript1" rows="15"><%= render :partial => 'applescript1', :locals => { :context => current_user.contexts.first } %></textarea>
|
||||
</li>
|
||||
<li>Open Script Editor and paste the script into a new document.</li>
|
||||
<li>Compile and save the script. Run it as necessary.</li>
|
||||
</ol>
|
||||
<ol>
|
||||
<li>Choose the context you want to add actions to: <select name="applescript1-contexts" id="applescript1-contexts"><%= options_from_collection_for_select(current_user.contexts, "id", "name", current_user.contexts.first.id) %></select>
|
||||
<%= observe_field "applescript1-contexts", :update => "applescript1",
|
||||
:with => 'context_id',
|
||||
:url => { :controller => "integrations", :action => "get_applescript1" },
|
||||
:before => "$('applescript1').startWaiting()",
|
||||
:complete => "$('applescript1').stopWaiting()"
|
||||
%>
|
||||
</li>
|
||||
<li>Copy the Applescript below to the clipboard.<br />
|
||||
|
||||
<textarea id="applescript1" name="applescript1" rows="15"><%= render :partial => 'applescript1', :locals => { :context => current_user.contexts.first } %></textarea>
|
||||
</li>
|
||||
<li>Open Script Editor and paste the script into a new document.</li>
|
||||
<li>Compile and save the script. Run it as necessary.</li>
|
||||
</ol>
|
||||
<% else %>
|
||||
<br/><p id="no_context_msg"><i>You do not have any context yet. The script will be available after you add your first context</i></p>
|
||||
<% end %>
|
||||
|
|
@ -54,23 +50,23 @@ You can also use the Rich Todo API to send in tasks like "do laundry @ Home" or
|
|||
<p>This script takes the sender and subject of the selected email(s) in Mail and creates a new action for each one, with the description, "Email [sender] about [subject]". The description gets truncated to 100 characters (the validation limit for the field) if it is longer than that. It also has Growl notifications if you have Growl installed.</p>
|
||||
|
||||
<% if has_contexts -%>
|
||||
<ol>
|
||||
<li>Choose the context you want to add actions to: <select name="applescript2-contexts" id="applescript2-contexts"><%= options_from_collection_for_select(current_user.contexts, "id", "name", current_user.contexts.first.id) %></select>
|
||||
<%= observe_field "applescript2-contexts", :update => "applescript2",
|
||||
:with => 'context_id',
|
||||
:url => { :controller => "integrations", :action => "get_applescript2" },
|
||||
:before => "$('applescript2').startWaiting()",
|
||||
:complete => "$('applescript2').stopWaiting()"
|
||||
%>
|
||||
</li>
|
||||
<li>Copy the Applescript below to the clipboard.<br />
|
||||
|
||||
<textarea id="applescript2" name="applescript2" rows="15"><%= render :partial => 'applescript2', :locals => { :context => current_user.contexts.first } %></textarea>
|
||||
</li>
|
||||
<li>Open Script Editor and paste the script into a new document.</li>
|
||||
<li>Compile and save the script to the ~/Library/Scriipts/Mail Scripts directory.</li>
|
||||
<li>For more information on using AppleScript with Mail.app, see <a href="http://www.apple.com/applescript/mail/" title="Scriptable Applications: Mail">this overview</a>.
|
||||
</ol>
|
||||
<ol>
|
||||
<li>Choose the context you want to add actions to: <select name="applescript2-contexts" id="applescript2-contexts"><%= options_from_collection_for_select(current_user.contexts, "id", "name", current_user.contexts.first.id) %></select>
|
||||
<%= observe_field "applescript2-contexts", :update => "applescript2",
|
||||
:with => 'context_id',
|
||||
:url => { :controller => "integrations", :action => "get_applescript2" },
|
||||
:before => "$('applescript2').startWaiting()",
|
||||
:complete => "$('applescript2').stopWaiting()"
|
||||
%>
|
||||
</li>
|
||||
<li>Copy the Applescript below to the clipboard.<br />
|
||||
|
||||
<textarea id="applescript2" name="applescript2" rows="15"><%= render :partial => 'applescript2', :locals => { :context => current_user.contexts.first } %></textarea>
|
||||
</li>
|
||||
<li>Open Script Editor and paste the script into a new document.</li>
|
||||
<li>Compile and save the script to the ~/Library/Scriipts/Mail Scripts directory.</li>
|
||||
<li>For more information on using AppleScript with Mail.app, see <a href="http://www.apple.com/applescript/mail/" title="Scriptable Applications: Mail">this overview</a>.
|
||||
</ol>
|
||||
<% else %>
|
||||
<br/><p><i>You do not have any context yet. The script will be available after you add your first context</i></p>
|
||||
<% end %>
|
||||
|
|
@ -81,28 +77,28 @@ You can also use the Rich Todo API to send in tasks like "do laundry @ Home" or
|
|||
<p>This integration will allow you to add actions to Tracks via <a href="http://quicksilver.blacktree.com/">Quicksilver</a>.</p>
|
||||
|
||||
<% if has_contexts -%>
|
||||
<ol>
|
||||
<li>Choose the context you want to add actions to: <select name="quicksilver-contexts" id="quicksilver-contexts"><%= options_from_collection_for_select(current_user.contexts, "id", "name", current_user.contexts.first.id) %></select>
|
||||
<%= observe_field "quicksilver-contexts", :update => "quicksilver",
|
||||
:with => 'context_id',
|
||||
:url => { :controller => "integrations", :action => "get_quicksilver_applescript" },
|
||||
:before => "$('quicksilver').startWaiting()",
|
||||
:complete => "$('quicksilver').stopWaiting()"
|
||||
%>
|
||||
</li>
|
||||
<li>Copy the Applescript below to the clipboard.<br />
|
||||
|
||||
<textarea id="quicksilver" name="quicksilver" rows="15"><%= render :partial => 'quicksilver_applescript', :locals => { :context => current_user.contexts.first } %></textarea>
|
||||
</li>
|
||||
<li>Open Script Editor and paste the script into a new document.</li>
|
||||
<li>Compile and save the script as "Add to Tracks.scpt" in ~/Library/Application Support/Quicksilver/Actions/ (you may need to create the Actions directory)</li>
|
||||
<li>Restart Quicksilver</li>
|
||||
<li>Activate Quicksilver (Ctrl+Space by default)</li>
|
||||
<li>Press "." to put quicksilver into text mode</li>
|
||||
<li>Type the description of the next action you want to add</li>
|
||||
<li>Press tab to switch to the action pane.</li>
|
||||
<li>By typing or scrolling, choose the "Add to Tracks" action.</li>
|
||||
</ol>
|
||||
<ol>
|
||||
<li>Choose the context you want to add actions to: <select name="quicksilver-contexts" id="quicksilver-contexts"><%= options_from_collection_for_select(current_user.contexts, "id", "name", current_user.contexts.first.id) %></select>
|
||||
<%= observe_field "quicksilver-contexts", :update => "quicksilver",
|
||||
:with => 'context_id',
|
||||
:url => { :controller => "integrations", :action => "get_quicksilver_applescript" },
|
||||
:before => "$('quicksilver').startWaiting()",
|
||||
:complete => "$('quicksilver').stopWaiting()"
|
||||
%>
|
||||
</li>
|
||||
<li>Copy the Applescript below to the clipboard.<br />
|
||||
|
||||
<textarea id="quicksilver" name="quicksilver" rows="15"><%= render :partial => 'quicksilver_applescript', :locals => { :context => current_user.contexts.first } %></textarea>
|
||||
</li>
|
||||
<li>Open Script Editor and paste the script into a new document.</li>
|
||||
<li>Compile and save the script as "Add to Tracks.scpt" in ~/Library/Application Support/Quicksilver/Actions/ (you may need to create the Actions directory)</li>
|
||||
<li>Restart Quicksilver</li>
|
||||
<li>Activate Quicksilver (Ctrl+Space by default)</li>
|
||||
<li>Press "." to put quicksilver into text mode</li>
|
||||
<li>Type the description of the next action you want to add</li>
|
||||
<li>Press tab to switch to the action pane.</li>
|
||||
<li>By typing or scrolling, choose the "Add to Tracks" action.</li>
|
||||
</ol>
|
||||
<% else %>
|
||||
<br/><p><i>You do not have any context yet. The script will be available after you add your first context</i></p>
|
||||
<% end %>
|
||||
|
|
@ -115,3 +111,38 @@ You can also use the Rich Todo API to send in tasks like "do laundry @ Home" or
|
|||
<textarea id="cron" name="cron">0 5 * * * /usr/bin/curl -0 "<%= home_url %>todos.txt?due=6&token=<%= current_user.token %>" | /usr/bin/mail -e -s 'Tracks actions due in the next 7 days' youremail@yourdomain.com</textarea>
|
||||
|
||||
<p>You can of course use other text <%= link_to 'feeds provided by Tracks', feeds_path %> -- why not email a list of next actions in a particular project to a group of colleagues who are working on the project?</p>
|
||||
|
||||
<a name="message_gateway"> </a>
|
||||
<h2>Integrated email/SMS receiver</h2>
|
||||
<p>
|
||||
If Tracks is running on the same server as your mail server, you can use the integrated mail handler built into tracks. Steps to set it up:
|
||||
<ul>
|
||||
<li>Go to <%= link_to "Preferences", preferences_url %> and set your "From email" and "default email context" for todos sent in via email (which could come from an SMS message)</li>
|
||||
<li>In sendmail/qmail/postfix/whatever, set up an email address alias to pipe messages to <pre >/PATH/TO/RUBY/ruby /PATH/TO/TRACKS/script/runner -e production 'MessageGateway.receive(STDIN.read)'</pre></li>
|
||||
<li>Send an email to your newly configured address!</li>
|
||||
</ul>
|
||||
<p>You can also use the Rich Todo API to send in tasks like "do laundry @ Home"
|
||||
or "Call Bill > project X". The subject of the message will fill description,
|
||||
context, and project, while the body will populate the tasks's note.
|
||||
</p>
|
||||
|
||||
<a name="google_gadget"> </a>
|
||||
<h2>Add Tracks as a Google Gmail gadget</h2>
|
||||
<p>
|
||||
You can now manage your projects/actions inside Gmail using Tracks Gmail Gadget.
|
||||
Add Tracks Gmail gadget to the sidebar of Gmail and track your next actions
|
||||
or add new action without explicitly open new browser tab for Tracks. Steps to set it up:
|
||||
</p>
|
||||
<ul>
|
||||
<li>Sign in to Gmail and click Settings in the top right of your Gmail page. In Gmail setting page, click Labs tab</li>
|
||||
<li>Enable the "Add any gadget by URL" feature. You will find it at bottom of the list. Select Enable radio button and click Save Changes button.</li>
|
||||
<li>Now you can see Gadgets tab added to Gmail Settings. Go to the Gadgets tab</li>
|
||||
<li>Paste following link to the Add a gadget by its URL: and then click Add button:<br/>
|
||||
<pre><%= integrations_url + "/google_gadget" %></pre></li>
|
||||
</ul>
|
||||
|
||||
<Module>
|
||||
<ModulePrefs title="GTDify" directory_title="GTDify" description="Official gadget for GTDify service." author="GTDify" author_email="support@gtdify.com" author_affiliation="GTDify" author_location="NJ, USA" title_url="http://www.gtdify.com/" screenshot="http://www.gtdify.com/modules/gmail/ss.png" thumbnail="http://www.gtdify.com/modules/gmail/tn.png" category="communication" category2="tools" height="300">
|
||||
</ModulePrefs>
|
||||
<Content type="url" href="http://my.gtdify.com/mobile/"/>
|
||||
</Module>
|
||||
|
|
@ -3,7 +3,8 @@
|
|||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<%= stylesheet_link_tag "scaffold" %>
|
||||
<%= javascript_include_tag :defaults %>
|
||||
<%= javascript_include_tag 'jquery' %>
|
||||
<%= javascript_include_tag 'jquery.cookie' %>
|
||||
|
||||
<title><%= @page_title -%></title>
|
||||
|
||||
|
|
|
|||
|
|
@ -6,22 +6,35 @@
|
|||
<meta http-equiv="Refresh" content="<%= @prefs["refresh"].to_i*60 %>;url=<%= request.request_uri %>">
|
||||
<% end -%>
|
||||
<% bundle :name => "tracks_css" do %>
|
||||
<%= stylesheet_link_tag *%w[ standard superfish calendar-system niftyCorners] %>
|
||||
<%= stylesheet_link_tag *%w[ standard superfish niftyCorners jquery-ui jquery.autocomplete] %>
|
||||
<% end %>
|
||||
<%= stylesheet_link_tag "print", :media => "print" %>
|
||||
<% bundle :name => "jquery" do %>
|
||||
<%= javascript_include_tag "jquery-1.2.6.min" %>
|
||||
<%= javascript_include_tag 'jquery' %>
|
||||
<%= javascript_include_tag 'jquery-ui' %>
|
||||
<%= javascript_include_tag 'jquery.cookie' %>
|
||||
<%= javascript_include_tag 'jquery.blockUI' %>
|
||||
<%= javascript_include_tag 'jquery.jeditable' %>
|
||||
<%= javascript_include_tag 'jquery.autocomplete' %>
|
||||
<% end %>
|
||||
<% bundle :name => "tracks_js" do %>
|
||||
<%= javascript_include_tag *%w[
|
||||
hoverIntent superfish prototype
|
||||
effects dragdrop controls application
|
||||
calendar calendar-en calendar-setup
|
||||
accesskey-hints todo-items niftycube
|
||||
protoload flashobject lowpro
|
||||
] %>
|
||||
hoverIntent superfish application
|
||||
accesskey-hints niftycube flashobject ] %>
|
||||
<% end %>
|
||||
<%= javascript_include_tag :unobtrusive %>
|
||||
<%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? %>
|
||||
<%= javascript_tag "var SOURCE_VIEW = '#{@source_view}';" %>
|
||||
<%= javascript_tag "var TAG_NAME = '#{@tag_name}';" if @tag_name %>
|
||||
<script type="text/javascript">
|
||||
<% if defined? context_names_for_autocomplete -%>
|
||||
var contextNames = <%= context_names_for_autocomplete %>;
|
||||
var projectNames = <%= project_names_for_autocomplete %>;
|
||||
var defaultContexts = <%= default_contexts_for_autocomplete %>;
|
||||
var defaultTags = <%= default_tags_for_autocomplete %>;
|
||||
var tagNames = <%= tag_names_for_autocomplete %>;
|
||||
var dateFormat = '<%= date_format_for_date_picker %>';
|
||||
<% end -%>
|
||||
</script>
|
||||
<link rel="shortcut icon" href="<%= url_for(:controller => 'favicon.ico') %>" />
|
||||
<%= auto_discovery_link_tag(:rss, {:controller => "todos", :action => "index", :format => 'rss', :token => "#{current_user.token}"}, {:title => "RSS feed of next actions"}) %>
|
||||
<link rel="search" type="application/opensearchdescription+xml" title="Tracks" href="<%= search_plugin_path %>" />
|
||||
|
|
@ -54,14 +67,16 @@
|
|||
<li><%= navigation_link( "Contexts", contexts_path, {:accesskey=>"c", :title=>"Contexts"} ) %></li>
|
||||
<li><%= navigation_link( "Notes", notes_path, {:accesskey => "o", :title => "Show all notes"} ) %></li>
|
||||
<li><%= navigation_link( "Repeating todos", {:controller => "recurring_todos", :action => "index"}, :title => "Manage recurring actions" ) %></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">View</a>
|
||||
<ul>
|
||||
<li><%= navigation_link( "Calendar", calendar_path, :title => "Calendar of due actions" ) %></li>
|
||||
<li><%= navigation_link( "Done", done_path, {:accesskey=>"d", :title=>"Completed"} ) %></li>
|
||||
<li><%= navigation_link( "Feeds", {:controller => "feedlist", :action => "index"}, :title => "See a list of available feeds" ) %></li>
|
||||
<li><%= navigation_link( "Statistics", {:controller => "stats", :action => "index"}, :title => "See your statistics" ) %></li>
|
||||
</ul>
|
||||
<ul>
|
||||
<li><%= navigation_link( "Calendar", calendar_path, :title => "Calendar of due actions" ) %></li>
|
||||
<li><%= navigation_link( "Done", done_path, {:accesskey=>"d", :title=>"Completed"} ) %></li>
|
||||
<li><%= navigation_link( "Feeds", {:controller => "feedlist", :action => "index"}, :title => "See a list of available feeds" ) %></li>
|
||||
<li><%= navigation_link( "Statistics", {:controller => "stats", :action => "index"}, :title => "See your statistics" ) %></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">Admin</a>
|
||||
<ul>
|
||||
<li><%= navigation_link( "Preferences", preferences_path, {:accesskey => "u", :title => "Show my preferences"} ) %></li>
|
||||
|
|
@ -69,19 +84,21 @@
|
|||
<% if current_user.is_admin? -%>
|
||||
<li><%= navigation_link("Manage users", users_path, {:accesskey => "a", :title => "Add or delete users"} ) %></li>
|
||||
<% end -%>
|
||||
</ul></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="#">?</a>
|
||||
<ul>
|
||||
<li><%= link_to 'Integrate Tracks', integrations_path %></li>
|
||||
<li><%= link_to 'REST API Docs', url_for(:controller => 'integrations', :action => 'rest_api') %></li>
|
||||
</ul></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><%= navigation_link(image_tag("system-search.png", :size => "16X16", :border => 0), {:controller => "search", :action => "index"}, :title => "Search All Items" ) %></li>
|
||||
</ul>
|
||||
</div>
|
||||
<%= render_flash %>
|
||||
</div>
|
||||
|
||||
<div id="content">
|
||||
<div id="content" class="<%= @controller.controller_name %>">
|
||||
<% unless @controller_name == 'feed' or session['noexpiry'] == "on" -%>
|
||||
<%= periodically_call_remote( :url => {:controller => "login", :action => "check_expiry"},
|
||||
:frequency => (5*60)) %>
|
||||
|
|
@ -93,36 +110,5 @@
|
|||
</div>
|
||||
|
||||
<%= render :partial => "shared/footer" %>
|
||||
|
||||
<script type="text/javascript">
|
||||
jQuery(document).ready(function() { /* main menu */
|
||||
jQuery('ul.sf-menu').superfish({
|
||||
delay: 250,
|
||||
animation: {opacity:'show',height:'show'},
|
||||
autoArrows: false,
|
||||
dropShadows: false,
|
||||
speed: 'fast'
|
||||
});
|
||||
jQuery('ul.sf-item-menu').superfish({ /* context menu */
|
||||
delay: 100,
|
||||
animation: {opacity:'show',height:'show'},
|
||||
autoArrows: false,
|
||||
dropShadows: false,
|
||||
speed: 'fast',
|
||||
onBeforeShow: function() { /* highlight todo */
|
||||
$(this.parent().parent().parent()).addClass("sf-item-selected");
|
||||
},
|
||||
onHide: function() { /* remove hightlight from todo */
|
||||
$(this.parent().parent().parent()).removeClass("sf-item-selected");
|
||||
}
|
||||
});
|
||||
/* for toggle notes link in mininav */
|
||||
jQuery("#toggle-notes-nav").click(function () { jQuery(".todo_notes").toggle(); });
|
||||
/* show the notes of a todo */
|
||||
TodoBehavior.enableToggleNotes();
|
||||
Nifty("div#todo_new_action_container","normal");
|
||||
if ($('flash').visible()) { new Effect.Fade("flash",{duration:5.0}); }
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
|
||||
<script type="text/javascript">
|
||||
function showPreferredAuth() {
|
||||
var preferredAuth = new CookieManager().getCookie('preferred_auth');
|
||||
var preferredAuth = $.cookie('preferred_auth');
|
||||
var databaseEnabled = <%= show_database_form ? 'true' : 'false' %>;
|
||||
var openidEnabled = <%= show_openid_form ? 'true' : 'false' %>;
|
||||
if (preferredAuth && preferredAuth == 'openid' && openidEnabled) {
|
||||
|
|
@ -74,5 +74,28 @@ function showPreferredAuth() {
|
|||
Login.showOpenid();
|
||||
}
|
||||
}
|
||||
Event.observe(window, 'load', showPreferredAuth);
|
||||
</script>
|
||||
$(document).ready(showPreferredAuth);
|
||||
|
||||
var Login = {
|
||||
showOpenid: function() {
|
||||
$('#database_auth_form').hide();
|
||||
$('#openid_auth_form').show();
|
||||
$('#alternate_auth_openid').hide();
|
||||
$('#alternate_auth_database').show();
|
||||
$('#openid_url').focus();
|
||||
$('#openid_url').select();
|
||||
$.cookie('preferred_auth', 'openid');
|
||||
},
|
||||
|
||||
showDatabase: function(container) {
|
||||
$('#openid_auth_form').hide();
|
||||
$('#database_auth_form').show();
|
||||
$('#alternate_auth_database').hide();
|
||||
$('#alternate_auth_openid').show();
|
||||
$('#user_login').focus();
|
||||
$('#user_login').select();
|
||||
$.cookie('preferred_auth', 'database');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@
|
|||
:id => "delete_note_"+note.id.to_s),
|
||||
{:update => dom_id(note),
|
||||
:loading => visual_effect(:fade, dom_id(note, 'container')),
|
||||
:complete => "Element.remove('#{dom_id(note, 'container')}');",
|
||||
:complete => "$('#{dom_id(note, 'container')}').remove();",
|
||||
:url => note_path(note),
|
||||
:method => :delete,
|
||||
:confirm => "Are you sure that you want to delete the note \'#{note.id.to_s}\'?" },
|
||||
{ :class => 'delete_note' }) -%>
|
||||
<%= link_to_function(image_tag( "blank.png", :title => "Edit item", :class=>"edit_item"),
|
||||
"Element.toggle('#{dom_id(note)}'); Element.toggle('#{dom_id(note, 'edit')}'); Effect.Appear('#{dom_id(note, 'edit')}'); Form.focusFirstElement('#{dom_id(note, 'edit_form')}');" ) + " | " %>
|
||||
"$('##{dom_id(note)}').toggle(); $('##{dom_id(note, 'edit')}').show(); $('##{dom_id(note, 'edit_form')} textarea').focus();" ) + " | " %>
|
||||
<%= link_to("In: " + note.project.name, project_path(note.project), :class=>"footer_link" ) %> |
|
||||
Created: <%= format_date(note.created_at) %>
|
||||
<% if note.updated_at? -%>
|
||||
|
|
@ -37,4 +37,4 @@
|
|||
<% end -%>
|
||||
</div>
|
||||
</div>
|
||||
<% note = nil -%>
|
||||
<% note = nil -%>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
<div class="page_name_auto_complete" id="default_context_list" style="display:none;z-index:9999"></div>
|
||||
<script type="text/javascript">
|
||||
defaultContextAutoCompleter = new Autocompleter.Local('project_default_context_name', 'default_context_list', <%= context_names_for_autocomplete %>, {choices:100,autoSelect:false});
|
||||
Event.observe($('project_default_context_name'), "focus", defaultContextAutoCompleter.activate.bind(defaultContextAutoCompleter));
|
||||
Event.observe($('project_default_context_name'), "click", defaultContextAutoCompleter.activate.bind(defaultContextAutoCompleter));
|
||||
</script>
|
||||
|
|
@ -1,10 +1,5 @@
|
|||
<div class="container">
|
||||
<h2>
|
||||
<% if collapsible -%>
|
||||
<a href="#" class="container_toggle" id="toggle_p<%= project.id %>"><%= image_tag("collapse.png") %></a>
|
||||
<% end -%>
|
||||
<%= project.name -%>
|
||||
</h2>
|
||||
<h2 id="project_name"><% if collapsible %><a href="#" class="container_toggle" id="toggle_p<%= project.id %>"><%= image_tag("collapse.png") %></a><% end %><%= project.name -%></h2>
|
||||
<div id="<%= dom_id(project, "container")%>">
|
||||
<%= render :partial => "projects/project_settings", :locals => { :project => project, :collapsible => collapsible } %>
|
||||
</div>
|
||||
|
|
@ -17,11 +12,5 @@
|
|||
<div class="message"><p>Currently there are no incomplete actions in this project</p></div>
|
||||
</div>
|
||||
<%= render :partial => "todos/todo", :collection => @not_done, :locals => { :parent_container_type => "project" } %>
|
||||
<% if @not_done.empty?
|
||||
# fix (hack) for #713
|
||||
set_behavior_for_star_icon
|
||||
set_behavior_for_toggle_checkbox
|
||||
end
|
||||
-%>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
<%
|
||||
project = project_form
|
||||
%>
|
||||
<% form_tag project_path(project), { :id => dom_id(project, 'edit_form'), :class => "inline-form "+dom_id(project, 'edit_form')+"-edit-project-form", :method => :put } do -%>
|
||||
|
||||
<% form_remote_tag(:url => project_path(project), :html => { :id => dom_id(project, 'edit_form'), :class => "inline-form "+dom_id(project, 'edit_form')+"-edit-project-form", :method => :put }) do -%>
|
||||
|
||||
<%= source_view_tag( @source_view ) -%>
|
||||
|
||||
|
|
@ -18,7 +19,6 @@ project = project_form
|
|||
|
||||
<label for="project[default_context_name]">Default Context</label><br/>
|
||||
<%= text_field_tag("project[default_context_name]", project.default_context.name, {:tabindex=>1,:size=> 25}) %>
|
||||
<%= render :partial => 'default_context_autocomplete' %>
|
||||
<br/>
|
||||
|
||||
<label for="project[default_tags]">Default Tags</label><br/>
|
||||
|
|
@ -32,7 +32,7 @@ project = project_form
|
|||
<%=image_tag("accept.png", :alt => "") %>
|
||||
Update
|
||||
</button>
|
||||
<a href="javascript:void(0);" id="<%= dom_id(project, 'cancel') %>" onclick="Element.toggle('<%= dom_id(project) %>');Element.toggle('<%= dom_id(project, 'edit') %>');" class="negative">
|
||||
<a href="#" id="<%= dom_id(project, 'cancel') %>" class="negative">
|
||||
<%=image_tag("cancel.png", :alt => "") %>
|
||||
Cancel
|
||||
</a>
|
||||
|
|
@ -42,7 +42,3 @@ project = project_form
|
|||
|
||||
<% end -%>
|
||||
|
||||
<%= apply_behavior "."+dom_id(project, 'edit_form')+"-edit-project-form", make_remote_form(
|
||||
:before => "$('"+dom_id(project, 'submit')+"').startWaiting();",
|
||||
:condition => "!$('"+dom_id(project, 'submit')+"').isWaiting()",
|
||||
:external => false) %>
|
||||
|
|
@ -19,20 +19,15 @@ suppress_edit_button ||= false
|
|||
title="delete the project '<%= project.name %>'"><%= image_tag( "blank.png",
|
||||
:title => "Delete project",
|
||||
:class=>"delete_item") %></a>
|
||||
<%= apply_behavior "a.delete_project_button:click", { :prevent_default => true, :external => true } do |page, element|
|
||||
page.confirming "'Are you sure that you want to ' + this.title + '?'" do
|
||||
element.up('.project').start_waiting
|
||||
page << remote_to_href(:method => 'delete')
|
||||
end
|
||||
end -%>
|
||||
|
||||
<% unless suppress_edit_button -%>
|
||||
<%= link_to_remote(
|
||||
image_tag( "blank.png", :title => "Edit project", :class=>"edit_item"),
|
||||
:url => {:controller => 'projects', :action => 'edit', :id => project.id},
|
||||
:method => 'get',
|
||||
:with => "'_source_view=#{@source_view}'",
|
||||
:before => "$('#{dom_id(project)}').startWaiting();",
|
||||
:complete => "$('#{dom_id(project)}').stopWaiting();"
|
||||
:before => "$('#{dom_id(project)}').block({message:null});",
|
||||
:complete => "$('#{dom_id(project)}').unblock();enable_rich_interaction();"
|
||||
) %>
|
||||
|
||||
<% end -%>
|
||||
|
|
@ -42,8 +37,3 @@ suppress_edit_button ||= false
|
|||
<div id="<%= dom_id(project, 'edit') %>" class="edit-form" style="display:none;">
|
||||
</div>
|
||||
</div>
|
||||
<% if controller.action_name == 'create' %>
|
||||
<script>
|
||||
new Effect.Appear('<%= dom_id(project) %>');
|
||||
</script>
|
||||
<% end %>
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@
|
|||
:url => {:controller => 'projects', :action => 'edit', :id => project.id},
|
||||
:method => 'get',
|
||||
:with => "'_source_view=#{@source_view}'",
|
||||
:before => "$('#{dom_id(project)}').startWaiting();",
|
||||
:complete => "$('#{dom_id(project)}').stopWaiting();"
|
||||
:before => "$('#{dom_id(project)}').block({message: null});",
|
||||
:complete => "$('#{dom_id(project)}').unblock();enable_rich_interaction();"
|
||||
) %>
|
||||
</div>
|
||||
<% unless project.description.blank? -%>
|
||||
|
|
@ -28,4 +28,4 @@
|
|||
<% end -%>
|
||||
</div>
|
||||
<div id="<%= dom_id(project, 'edit') %>" class="edit-form" style="display:none;">
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,28 +4,13 @@
|
|||
<div class="alpha_sort">
|
||||
<%= link_to("Alphabetically", alphabetize_projects_path(:state => state),
|
||||
:class => "alphabetize_link", :title => "Sort these projects alphabetically") %>
|
||||
<% apply_behavior '.alphabetize_link:click', :prevent_default => true do |page, element|
|
||||
page.confirming 'Are you sure that you want to sort these projects alphabetically? This will replace the existing sort order.' do
|
||||
page << "alphaSort = this.up('.alpha_sort');
|
||||
alphaSort.startWaiting();"
|
||||
page << remote_to_href(:complete => "alphaSort.stopWaiting()")
|
||||
end
|
||||
end
|
||||
%></div><span class="sort_separator"> | </span><div class="tasks_sort">
|
||||
</div><span class="sort_separator"> | </span><div class="tasks_sort">
|
||||
<%= link_to("By number of tasks", actionize_projects_path(:state => state),
|
||||
:class => "actionize_link", :title => "Sort these projects by number of tasks") %>
|
||||
<% apply_behavior '.actionize_link:click', :prevent_default => true do |page, element|
|
||||
page.confirming 'Are you sure that you want to sort these projects by the number of tasks? This will replace the existing sort order.' do
|
||||
page << "tasksSort = this.up('.tasks_sort');
|
||||
tasksSort.startWaiting();"
|
||||
page << remote_to_href(:complete => "tasksSort.stopWaiting()")
|
||||
end
|
||||
end
|
||||
%></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="list-<%= state %>-projects" class="project-list">
|
||||
<%= render :partial => 'project_listing', :collection => project_state_group %>
|
||||
</div>
|
||||
<%= sortable_element "list-#{state}-projects", get_listing_sortable_options("list-#{state}-projects") %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ elsif @saved
|
|||
page.replace_html "active-projects-count", @active_projects_count
|
||||
page.insert_html :bottom, "list-active-projects", :partial => 'project_listing', :locals => { :project_listing => @project }
|
||||
page.sortable "list-active-projects", get_listing_sortable_options('list-active-projects')
|
||||
page.call "Form.reset", "project-form"
|
||||
page.call "Form.focusFirstElement", "project-form"
|
||||
page << "$('#project-form').clearForm();"
|
||||
page << "$('#project-form input:text:first').focus();"
|
||||
else
|
||||
page.show 'status'
|
||||
page.replace_html 'status', "#{error_messages_for('project')}"
|
||||
end
|
||||
page.hide "busy"
|
||||
page.hide "busy"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
page[dom_id(@project, 'edit')].replace_html :partial => 'project_form', :locals => { :project_form => @project }
|
||||
page[@project].hide
|
||||
page[dom_id(@project, 'edit')].show
|
||||
page[dom_id(@project, 'edit_form')].down('input.project-name').focus
|
||||
page[dom_id(@project, 'edit_form')].find('input.project-name').focus
|
||||
|
|
|
|||
|
|
@ -14,20 +14,13 @@
|
|||
|
||||
<div id="toggle_project_new" class="hide_form">
|
||||
<a title="Hide new project form" accesskey="n">« Hide form</a>
|
||||
<% apply_behavior '#toggle_project_new a:click', :prevent_default => true do |page|
|
||||
page << "TracksForm.toggle('toggle_project_new', 'project_new', 'project-form',
|
||||
'« Hide form', 'Hide new project form',
|
||||
'Create a new project »', 'Add a project');"
|
||||
end
|
||||
%>
|
||||
</div>
|
||||
|
||||
<div id="project_new" class="project_new" style="display:block">
|
||||
<% form_remote_tag(:url => projects_path, :method => :post,
|
||||
:html=> { :id=>'project-form', :name=>'project', :class => 'inline-form'},
|
||||
:before => "$('project_new_project_submit').startWaiting()",
|
||||
:complete => "$('project_new_project_submit').stopWaiting()",
|
||||
:condition => "!$('project_new_project_submit').isWaiting()") do -%>
|
||||
:before => "$('#project_new_project_submit').block({message:null})",
|
||||
:complete => "$('#project_new_project_submit').unblock()") do -%>
|
||||
|
||||
<div id="status"><%= error_messages_for('project') %></div>
|
||||
|
||||
|
|
@ -40,7 +33,6 @@
|
|||
<% unless @contexts.empty? -%>
|
||||
<label for="default_context_name">Default Context (optional):</label><br />
|
||||
<%= text_field_tag("project[default_context_name]", @project.default_context.name, :tabindex => 3) %>
|
||||
<%= render :partial => 'default_context_autocomplete' %>
|
||||
<br />
|
||||
<% end -%>
|
||||
|
||||
|
|
|
|||
|
|
@ -4,14 +4,14 @@
|
|||
</div>
|
||||
|
||||
<%= render :partial => "projects/project", :locals => { :project => @project, :collapsible => false } %>
|
||||
<%= render :partial => "todos/deferred", :locals => { :deferred => @deferred, :collapsible => false, :append_descriptor => "in this project", :parent_container_type => 'project' } %>
|
||||
<%= render :partial => "todos/deferred", :locals => { :deferred => @deferred, :collapsible => false, :append_descriptor => "in this project", :parent_container_type => 'project', :pending => @pending } %>
|
||||
<% unless @max_completed==0 -%>
|
||||
<%= render :partial => "todos/completed", :locals => { :done => @done, :collapsible => false, :suppress_project => true, :append_descriptor => "in this project" } %>
|
||||
<% end -%>
|
||||
|
||||
<div class="container">
|
||||
<div id="notes">
|
||||
<div class="add_note_link"><%= link_to_function( "Add a note", "Element.toggle('new-note'); Form.focusFirstElement('form-new-note');", :id=>"add_note_href") %></div>
|
||||
<div class="add_note_link"><%= link_to 'Add a note', '#' %> </div>
|
||||
<h2>Notes</h2>
|
||||
<div id="empty-n" style="display:<%= @project.notes.empty? ? 'block' : 'none'%>;">
|
||||
<%= render :partial => "shared/empty",
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
:method => :post,
|
||||
:update => "notes",
|
||||
:position => "bottom",
|
||||
:complete => "new Effect.Highlight('notes');$('empty-n').hide();Form.reset('form-new-note');",
|
||||
:complete => "$('#notes').effect('highlight', 1000);$('#empty-n').hide();$('#new-note form').clearForm();",
|
||||
:html => {:id=>'form-new-note', :class => 'inline-form'} do %>
|
||||
<%= hidden_field( "new_note", "project_id", "value" => "#{@project.id}" ) %>
|
||||
<%= text_area( "new_note", "body", "cols" => 50, "rows" => 3, "tabindex" => 1 ) %>
|
||||
|
|
@ -39,4 +39,4 @@
|
|||
<div id="input_box">
|
||||
<%= render :partial => "shared/add_new_item_form" %>
|
||||
<%= render :file => "sidebar/sidebar.html.erb" %>
|
||||
</div><!-- End of input box -->
|
||||
</div><!-- End of input box -->
|
||||
|
|
|
|||
|
|
@ -19,4 +19,14 @@ else
|
|||
page[dom_id(@project, 'edit')].hide
|
||||
page.replace_html dom_id(@project, 'container'), :partial => 'project_settings', :locals => { :project => @project }
|
||||
page[dom_id(@project)].show
|
||||
|
||||
page['todo_context_name'].value = @project.default_context.name if @project.default_context
|
||||
page['#todo_project_name'].value = @project.name
|
||||
page['tag_list'].value = @project.default_tags if @project.default_tags
|
||||
page << "$('input[name=default_context_name]').val('#{@project.default_context.name}');" if @project.default_context
|
||||
page << "defaultContexts = #{default_contexts_for_autocomplete};"
|
||||
page << "defaultTags = #{default_tags_for_autocomplete};"
|
||||
end
|
||||
|
||||
page.replace_html "sidebar", :file => 'sidebar/sidebar.html.erb'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
page['project_name_in_place_editor'].replace_html @project.name
|
||||
|
||||
page['default_project_name_id'].value = @project.name
|
||||
page['todo_project_name'].value = @project.name
|
||||
|
||||
# renew project auto complete array
|
||||
page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}; projectAutoCompleter.changed = true"
|
||||
page << "var projectNames = #{project_names_for_autocomplete};"
|
||||
|
||||
status_message = "Name of project was changed"
|
||||
page.notify :notice, status_message, 5.0
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@
|
|||
<% form_remote_tag(
|
||||
:url => recurring_todo_path(@recurring_todo), :method => :put,
|
||||
:html=> { :id=>'recurring-todo-form-edit-action', :name=>'recurring_todo', :class => 'inline-form' },
|
||||
:before => "$('recurring_todo_edit_action_submit').startWaiting()",
|
||||
:complete => "$('recurring_todo_edit_action_submit').stopWaiting();",
|
||||
:condition => "!$('recurring_todo_edit_action_submit').isWaiting()") do
|
||||
:before => "$('#recurring_todo_edit_action_submit').block({message: null})",
|
||||
:complete => "$('#recurring_todo_edit_action_submit').unblock();$('#recurring-todo-form-edit-action').clearForm();") do
|
||||
|
||||
-%>
|
||||
<div id="edit_status"><%= error_messages_for("item", :object_name => 'action') %></div>
|
||||
|
||||
|
|
@ -20,59 +20,13 @@
|
|||
<label for="edit_recurring_todo_project_name">Project</label>
|
||||
<input id="edit_recurring_todo_project_name" name="project_name" autocomplete="off" tabindex="3" size="30" type="text" value="<%= @recurring_todo.project.nil? ? 'None' : @recurring_todo.project.name.gsub(/"/,""") %>" />
|
||||
<div class="page_name_auto_complete" id="edit_project_list" style="display:none"></div>
|
||||
<script type="text/javascript">
|
||||
projectAutoCompleter = new Autocompleter.Local('edit_recurring_todo_project_name', 'edit_project_list', <%= project_names_for_autocomplete %>, {choices:100,autoSelect:false});
|
||||
function selectDefaultContext() {
|
||||
todoContextNameElement = $('edit_recurring_todo_context_name');
|
||||
defaultContextName = todoContextNameElement.projectDefaultContextsMap[this.value];
|
||||
if (defaultContextName && !todoContextNameElement.editedByTracksUser) {
|
||||
todoContextNameElement.value = defaultContextName;
|
||||
}
|
||||
}
|
||||
function selectDefaultTags() {
|
||||
todoTagListElement = $('edit_recurring_todo_tag_list');
|
||||
defaultTagList = todoTagListElement.projectDefaultTagsMap[this.value];
|
||||
if (defaultTagList && !todoTagListElement.editedByTracksUser) {
|
||||
todoTagListElement.value = defaultTagList;
|
||||
}
|
||||
}
|
||||
Event.observe($('edit_recurring_todo_project_name'), "focus", projectAutoCompleter.activate.bind(projectAutoCompleter));
|
||||
Event.observe($('edit_recurring_todo_project_name'), "click", projectAutoCompleter.activate.bind(projectAutoCompleter));
|
||||
Event.observe($('edit_recurring_todo_project_name'), "blur", selectDefaultContext.bind($('edit_recurring_todo_project_name')));
|
||||
Event.observe($('edit_recurring_todo_project_name'), "blur", selectDefaultTags.bind($('edit_recurring_todo_project_name')));
|
||||
</script>
|
||||
|
||||
|
||||
<label for="edit_recurring_todo_context_name">Context</label>
|
||||
<input id="edit_recurring_todo_context_name" name="context_name" autocomplete="off" tabindex="4" size="30" type="text" value="<%= @recurring_todo.context.name %>" />
|
||||
<div class="page_name_auto_complete" id="edit_context_list" style="display:none"></div>
|
||||
<script type="text/javascript">
|
||||
var contextAutoCompleter;
|
||||
function initializeNamesForAutoComplete(contextNamesForAutoComplete) {
|
||||
if (contextNamesForAutoComplete.length == 0 || contextNamesForAutoComplete[0].length == 0) {
|
||||
return;
|
||||
}
|
||||
contextAutoCompleter = new Autocompleter.Local('edit_recurring_todo_context_name', 'edit_context_list', contextNamesForAutoComplete, {choices:100,autoSelect:false});
|
||||
Event.observe($('edit_recurring_todo_context_name'), "focus", function(){ $('edit_recurring_todo_context_name').editedByTracksUser = true; });
|
||||
Event.observe($('edit_recurring_todo_context_name'), "focus", contextAutoCompleter.activate.bind(contextAutoCompleter));
|
||||
Event.observe($('edit_recurring_todo_context_name'), "click", contextAutoCompleter.activate.bind(contextAutoCompleter));
|
||||
}
|
||||
function updateContextNamesForAutoComplete(contextNamesForAutoComplete) {
|
||||
if (contextAutoCompleter) { // i.e. if we're already initialized
|
||||
contextAutoCompleter.options.array = contextNamesForAutoComplete
|
||||
} else {
|
||||
initializeNamesForAutoComplete(contextNamesForAutoComplete)
|
||||
}
|
||||
}
|
||||
initializeNamesForAutoComplete(<%= context_names_for_autocomplete %>);
|
||||
$('edit_recurring_todo_context_name').projectDefaultContextsMap = eval('(' + <%= @default_project_context_name_map %> + ')');
|
||||
</script>
|
||||
|
||||
<label for="edit_recurring_todo_tag_list">Tags (separate with commas)</label>
|
||||
<%= text_field_tag "edit_recurring_todo_tag_list", @recurring_todo.tag_list, :size => 30, :tabindex => 5 -%>
|
||||
<script type="text/javascript">
|
||||
$('edit_recurring_todo_tag_list').projectDefaultTagsMap = eval('(' + <%= @default_project_tags_map %> + ')');
|
||||
Event.observe($('edit_recurring_todo_tag_list'), "focus", function(){ $('edit_recurring_todo_tag_list').editedByTracksUser = true; });
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<div id="recurring_edit_period_id">
|
||||
|
|
@ -87,13 +41,13 @@
|
|||
<div id="recurring_timespan">
|
||||
<br/>
|
||||
<label for="recurring_todo[start_from]">Starts on </label><%=
|
||||
text_field_tag("recurring_todo_edit_start_from", format_date(@recurring_todo.start_from), "size" => 12, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 6, "autocomplete" => "off") %><br/>
|
||||
text_field_tag("recurring_todo_edit_start_from", format_date(@recurring_todo.start_from), "size" => 12, "class" => "Date", "tabindex" => 6, "autocomplete" => "off") %><br/>
|
||||
<br/>
|
||||
<label for="recurring_todo[ends_on]">Ends on:</label><br/>
|
||||
<%= radio_button_tag('recurring_todo[ends_on]', 'no_end_date', @recurring_todo.ends_on == 'no_end_date')%> No end date<br/>
|
||||
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_number_of_times', @recurring_todo.ends_on == 'ends_on_number_of_times')%> Ends after <%= text_field_tag("recurring_todo[number_of_occurences]", @recurring_todo.number_of_occurences, "size" => 3, "tabindex" => 7) %> times<br/>
|
||||
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_end_date', @recurring_todo.ends_on == 'ends_on_end_date')%> Ends on <%=
|
||||
text_field_tag('recurring_todo_edit_end_date', format_date(@recurring_todo.end_date), "size" => 12, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 8, "autocomplete" => "off") %><br/>
|
||||
text_field_tag('recurring_todo_edit_end_date', format_date(@recurring_todo.end_date), "size" => 12, "class" => "Date", "tabindex" => 8, "autocomplete" => "off") %><br/>
|
||||
</div></div>
|
||||
<div id="recurring_edit_daily" style="display:<%= @recurring_todo.recurring_period == 'daily' ? 'block' : 'none' %> ">
|
||||
<label>Settings for daily recurring actions</label><br/>
|
||||
|
|
@ -149,13 +103,11 @@
|
|||
<%=image_tag("accept.png", :alt => "") %>
|
||||
Update
|
||||
</button>
|
||||
<button type="button" class="positive" id="recurring_todo_edit_action_cancel" tabindex="15" onclick="TracksForm.toggle_overlay();">
|
||||
<button type="button" class="positive" id="recurring_todo_edit_action_cancel" tabindex="15">
|
||||
<%=image_tag("cancel.png", :alt => "") %>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= calendar_setup( "recurring_todo_edit_start_from" ) %>
|
||||
<%= calendar_setup( "recurring_todo_edit_end_date" ) %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
<% form_remote_tag(
|
||||
:url => recurring_todos_path, :method => :post,
|
||||
:html=> { :id=>'recurring-todo-form-new-action', :name=>'recurring_todo', :class => 'inline-form' },
|
||||
:before => "$('recurring_todo_new_action_submit').startWaiting()",
|
||||
:complete => "$('recurring_todo_new_action_submit').stopWaiting();",
|
||||
:condition => "!$('recurring_todo_new_action_submit').isWaiting()") do
|
||||
:before => "$('#recurring_todo_new_action_submit').block({message: null})",
|
||||
:complete => "$('#recurring_todo_new_action_submit').unblock();$('#recurring-todo-form-new-action').clearForm();") do
|
||||
-%>
|
||||
<div id="new_status"><%= error_messages_for("item", :object_name => 'action') %></div>
|
||||
|
||||
|
|
@ -17,59 +16,12 @@
|
|||
<label for="recurring_todo_project_name">Project</label>
|
||||
<input id="recurring_todo_project_name" name="project_name" autocomplete="off" tabindex="3" size="30" type="text" value="" />
|
||||
<div class="page_name_auto_complete" id="project_list" style="display:none"></div>
|
||||
<script type="text/javascript">
|
||||
projectAutoCompleter = new Autocompleter.Local('recurring_todo_project_name', 'project_list', <%= project_names_for_autocomplete %>, {choices:100,autoSelect:false});
|
||||
function selectDefaultContext() {
|
||||
todoContextNameElement = $('recurring_todo_context_name');
|
||||
defaultContextName = todoContextNameElement.projectDefaultContextsMap[this.value];
|
||||
if (defaultContextName && !todoContextNameElement.editedByTracksUser) {
|
||||
todoContextNameElement.value = defaultContextName;
|
||||
}
|
||||
}
|
||||
Event.observe($('recurring_todo_project_name'), "focus", projectAutoCompleter.activate.bind(projectAutoCompleter));
|
||||
Event.observe($('recurring_todo_project_name'), "click", projectAutoCompleter.activate.bind(projectAutoCompleter));
|
||||
Event.observe($('recurring_todo_project_name'), "blur", selectDefaultContext.bind($('recurring_todo_project_name')));
|
||||
</script>
|
||||
|
||||
<label for="recurring_todo_context_name">Context</label>
|
||||
<input id="recurring_todo_context_name" name="context_name" autocomplete="off" tabindex="4" size="30" type="text" value="" />
|
||||
<div class="page_name_auto_complete" id="context_list" style="display:none"></div>
|
||||
<script type="text/javascript">
|
||||
var contextAutoCompleter;
|
||||
function initializeNamesForAutoComplete(contextNamesForAutoComplete) {
|
||||
if (contextNamesForAutoComplete.length == 0 || contextNamesForAutoComplete[0].length == 0) {
|
||||
return;
|
||||
}
|
||||
contextAutoCompleter = new Autocompleter.Local('recurring_todo_context_name', 'context_list', contextNamesForAutoComplete, {choices:100,autoSelect:false});
|
||||
Event.observe($('recurring_todo_context_name'), "focus", function(){ $('recurring_todo_context_name').editedByTracksUser = true; });
|
||||
Event.observe($('recurring_todo_context_name'), "focus", contextAutoCompleter.activate.bind(contextAutoCompleter));
|
||||
Event.observe($('recurring_todo_context_name'), "click", contextAutoCompleter.activate.bind(contextAutoCompleter));
|
||||
}
|
||||
function updateContextNamesForAutoComplete(contextNamesForAutoComplete) {
|
||||
if (contextAutoCompleter) { // i.e. if we're already initialized
|
||||
contextAutoCompleter.options.array = contextNamesForAutoComplete
|
||||
} else {
|
||||
initializeNamesForAutoComplete(contextNamesForAutoComplete)
|
||||
}
|
||||
}
|
||||
initializeNamesForAutoComplete(<%= context_names_for_autocomplete %>);
|
||||
$('recurring_todo_context_name').projectDefaultContextsMap = eval('(' + <%= @default_project_context_name_map %> + ')');
|
||||
</script>
|
||||
|
||||
<label for="tag_list">Tags (separate with commas)</label>
|
||||
<%= text_field_tag "tag_list", nil, :size => 30, :tabindex => 5 -%>
|
||||
<script type="text/javascript">
|
||||
$('tag_list').projectDefaultTagsMap = eval('(' + <%= @default_project_tags_map %> + ')');
|
||||
function selectDefaultTags() {
|
||||
todoTagListElement = $('tag_list');
|
||||
defaultTagList = todoTagListElement.projectDefaultTagsMap[this.value];
|
||||
if (defaultTagList && !todoTagListElement.editedByTracksUser) {
|
||||
todoTagListElement.value = defaultTagList;
|
||||
}
|
||||
}
|
||||
Event.observe($('recurring_todo_project_name'), "blur", selectDefaultTags.bind($('recurring_todo_project_name')));
|
||||
Event.observe($('tag_list'), "focus", function(){ $('tag_list').editedByTracksUser = true; });
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
<div id="recurring_period_id">
|
||||
|
|
@ -79,18 +31,16 @@
|
|||
<%= radio_button_tag('recurring_todo[recurring_period]', 'weekly')%> Weekly<br/>
|
||||
<%= radio_button_tag('recurring_todo[recurring_period]', 'monthly')%> Monthly<br/>
|
||||
<%= radio_button_tag('recurring_todo[recurring_period]', 'yearly')%> Yearly<br/>
|
||||
<% apply_behaviour "#recurring_period:click",
|
||||
"TracksForm.hide_all_recurring(); $('recurring_'+TracksForm.get_period()).show();" %>
|
||||
</div>
|
||||
<div id="recurring_timespan">
|
||||
<br/>
|
||||
<label for="recurring_todo[start_from]">Starts on </label><%=
|
||||
text_field(:recurring_todo, :start_from, "value" => format_date(current_user.time), "size" => 12, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 6, "autocomplete" => "off") %><br/>
|
||||
text_field(:recurring_todo, :start_from, "value" => format_date(current_user.time), "size" => 12, "class" => "Date", "tabindex" => 6, "autocomplete" => "off") %><br/>
|
||||
<br/>
|
||||
<label for="recurring_todo[ends_on]">Ends on:</label><br/>
|
||||
<%= radio_button_tag('recurring_todo[ends_on]', 'no_end_date', true)%> No end date<br/>
|
||||
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_number_of_times')%> Ends after <%= text_field( :recurring_todo, :number_of_occurences, "size" => 3, "tabindex" => 7) %> times<br/>
|
||||
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_end_date')%> Ends on <%= text_field(:recurring_todo, :end_date, "size" => 12, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 8, "autocomplete" => "off", "value" => "") %><br/>
|
||||
<%= radio_button_tag('recurring_todo[ends_on]', 'ends_on_end_date')%> Ends on <%= text_field(:recurring_todo, :end_date, "size" => 12, "class" => "Date", "tabindex" => 8, "autocomplete" => "off", "value" => "") %><br/>
|
||||
</div></div>
|
||||
<div id="recurring_daily" style="display:block">
|
||||
<label>Settings for daily recurring actions</label><br/>
|
||||
|
|
@ -147,13 +97,11 @@
|
|||
<%=image_tag("accept.png", :alt => "") %>
|
||||
Create
|
||||
</button>
|
||||
<button type="button" class="positive" id="recurring_todo_new_action_cancel" tabindex="15" onclick="Form.reset('recurring-todo-form-new-action');Form.focusFirstElement('recurring-todo-form-new-action');TracksForm.hide_all_recurring(); $('recurring_daily').show();TracksForm.toggle_overlay();">
|
||||
<button type="button" class="positive" id="recurring_todo_new_action_cancel" tabindex="15">
|
||||
<%=image_tag("cancel.png", :alt => "") %>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= calendar_setup( "recurring_todo_start_from" ) %>
|
||||
<%= calendar_setup( "recurring_todo_end_date" ) %>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4,9 +4,8 @@ page.replace_html 'new_status', "#{error_messages_for('recurring_todo')}"
|
|||
page.notify :notice, @message, 5.0
|
||||
if @recurring_saved
|
||||
# reset form
|
||||
page << "TracksForm.hide_all_recurring(); $('recurring_daily').show();"
|
||||
page << "Form.reset('recurring-todo-form-new-action');"
|
||||
page << "Form.focusFirstElement('recurring-todo-form-new-action');"
|
||||
page << "TracksForm.hide_all_recurring(); $('#recurring_daily').show();"
|
||||
page << "$('#recurring_todo_new_action_submit').unblock();$('#recurring-todo-form-new-action').clearForm();"
|
||||
# hide overlayed edit form
|
||||
page << "TracksForm.toggle_overlay();"
|
||||
# insert new recurring todo
|
||||
|
|
|
|||
|
|
@ -33,16 +33,4 @@
|
|||
<div id="edit-recurring-todo" class="edit-form" style="display:none">
|
||||
<div class='placeholder'>This should not be visible</div>
|
||||
</div>
|
||||
</div><%
|
||||
|
||||
# need to add behaviour for edit form here. Behaviour defined in partials are
|
||||
# not generated for
|
||||
apply_behaviour "#recurring_edit_period:click",
|
||||
"TracksForm.hide_all_edit_recurring(); $('recurring_edit_'+TracksForm.get_edit_period()).show();"
|
||||
-%>
|
||||
|
||||
<script type="text/javascript">
|
||||
window.onload=function(){
|
||||
Nifty("div#recurring_new_container","normal");
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
|
|
|||
3
app/views/recurring_todos/toggle_star.js.erb
Normal file
3
app/views/recurring_todos/toggle_star.js.erb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<% if @saved -%>
|
||||
$('div#recurring_todo_<%= @recurring_todo.id %> a.star_item img').toggleClass('starred_todo').toggleClass('unstarred_todo');
|
||||
<% end -%>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
if @saved
|
||||
page[@recurring_todo].down('a.star_item').down('img').toggleClassName('starred_todo').toggleClassName('unstarred_todo')
|
||||
end
|
||||
|
|
@ -4,6 +4,3 @@
|
|||
<%= submit_tag "Search" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
Form.focusFirstElement('search-form')
|
||||
</script>
|
||||
|
|
@ -9,21 +9,15 @@
|
|||
|
||||
<div id="toggle_action_new" class="hide_form">
|
||||
<a title="Hide new action form" accesskey="n" href="#">« Hide form</a>
|
||||
<% apply_behavior '#toggle_action_new a:click', :prevent_default => true do |page|
|
||||
page << "TracksForm.toggle('toggle_action_new', 'todo_new_action', 'todo-form-new-action',
|
||||
'« Hide form', 'Hide next action form',
|
||||
'Add a next action »', 'Add a next action');"
|
||||
end
|
||||
-%>
|
||||
</div>
|
||||
|
||||
<div id="todo_new_action" style="display:block">
|
||||
<% form_remote_tag(
|
||||
:url => todos_path, :method => :post,
|
||||
:html=> { :id=>'todo-form-new-action', :name=>'todo', :class => 'inline-form' },
|
||||
:before => "$('todo_new_action_submit').startWaiting()",
|
||||
:complete => "$('todo_new_action_submit').stopWaiting()",
|
||||
:condition => "!$('todo_new_action_submit').isWaiting() && askIfNewContextProvided()") do -%>
|
||||
:before => "$('#todo_new_action_submit').block({message:null})",
|
||||
:complete => "$('#todo_new_action_submit').unblock()",
|
||||
:condition => "askIfNewContextProvided()") do -%>
|
||||
|
||||
<div id="status"><%= error_messages_for("item", :object_name => 'action') %></div>
|
||||
|
||||
|
|
@ -44,24 +38,23 @@
|
|||
<div class="page_name_auto_complete" id="context_list" style="display:none"></div>
|
||||
|
||||
<label for="tag_list">Tags (separate with commas)</label>
|
||||
<%= text_field_tag "tag_list", nil, :size => 30, :tabindex => 5 %>
|
||||
<%= text_field_tag "tag_list", @default_tags, :size => 30, :tabindex => 5 %>
|
||||
<%= content_tag("div", "", :id => "tag_list_auto_complete", :class => "auto_complete") %>
|
||||
<%= auto_complete_field 'tag_list', {
|
||||
:url => {:controller => 'todos', :action => 'auto_complete_for_tag'},
|
||||
:tokens => [',']
|
||||
} %>
|
||||
|
||||
<div class="due_input">
|
||||
<label for="todo_due">Due</label>
|
||||
<%= text_field("todo", "due", "size" => 12, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 6, "autocomplete" => "off") %>
|
||||
<%= text_field("todo", "due", "size" => 12, "class" => "Date", "tabindex" => 6, "autocomplete" => "off") %>
|
||||
</div>
|
||||
|
||||
<div class="show_from_input">
|
||||
<label for="todo_show_from">Show from</label>
|
||||
<%= text_field("todo", "show_from", "size" => 12, "class" => "Date", "onfocus" => "Calendar.setup", "tabindex" => 7, "autocomplete" => "off") %>
|
||||
<%= text_field("todo", "show_from", "size" => 12, "class" => "Date", "tabindex" => 7, "autocomplete" => "off") %>
|
||||
</div>
|
||||
|
||||
<label for="predecessor_list">Depends on</label>
|
||||
<%= text_field_tag "predecessor_list", nil, :size => 30, :tabindex => 8 %>
|
||||
<%= source_view_tag( @source_view ) %>
|
||||
<%= hidden_field_tag :_tag_name, @tag_name.underscore.gsub(/\s+/,'_') if source_view_is :tag %>
|
||||
|
||||
<div class="submit_box">
|
||||
<div class="widgets">
|
||||
|
|
@ -71,67 +64,5 @@
|
|||
</div>
|
||||
</div>
|
||||
<% end -%>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
var contextNames = <%= context_names_for_autocomplete %>;
|
||||
var projectNames = <%= project_names_for_autocomplete %>;
|
||||
|
||||
function askIfNewContextProvided() {
|
||||
var givenContextName = $('todo_context_name').value;
|
||||
if (givenContextName.length == 0) return true; // do nothing and depend on rails validation error
|
||||
for (var i = 0; i < contextNames.length; ++i) {
|
||||
if (contextNames[i] == givenContextName) return true;
|
||||
}
|
||||
return confirm('New context "' + givenContextName + '" will be also created. Are you sure?');
|
||||
}
|
||||
|
||||
var projectAutoCompleter = new Autocompleter.Local('todo_project_name', 'project_list', projectNames, {choices:100,autoSelect:false});
|
||||
|
||||
function selectDefaultContext() {
|
||||
todoContextNameElement = $('todo_context_name');
|
||||
defaultContextName = todoContextNameElement.projectDefaultContextsMap[this.value];
|
||||
if (defaultContextName && !todoContextNameElement.editedByTracksUser) {
|
||||
todoContextNameElement.value = defaultContextName;
|
||||
}
|
||||
}
|
||||
function selectDefaultTags() {
|
||||
todoTagListElement = $('tag_list');
|
||||
defaultTags = todoTagListElement.projectDefaultTagsMap[this.value];
|
||||
if (defaultTags && !todoTagListElement.editedByTracksUser) {
|
||||
todoTagListElement.value = defaultTags;
|
||||
}
|
||||
}
|
||||
Event.observe($('todo_project_name'), "focus", projectAutoCompleter.activate.bind(projectAutoCompleter));
|
||||
Event.observe($('todo_project_name'), "click", projectAutoCompleter.activate.bind(projectAutoCompleter));
|
||||
Event.observe($('todo_project_name'), "blur", selectDefaultContext.bind($('todo_project_name')));
|
||||
Event.observe($('todo_project_name'), "blur", selectDefaultTags.bind($('todo_project_name')));
|
||||
|
||||
var contextAutoCompleter;
|
||||
function initializeNamesForAutoComplete(contextNamesForAutoComplete) {
|
||||
if (contextNamesForAutoComplete.length == 0 || contextNamesForAutoComplete[0].length == 0) {
|
||||
return;
|
||||
}
|
||||
contextAutoCompleter = new Autocompleter.Local('todo_context_name', 'context_list', contextNamesForAutoComplete, {choices:100,autoSelect:false});
|
||||
Event.observe($('todo_context_name'), "focus", function(){ $('todo_context_name').editedByTracksUser = true; });
|
||||
Event.observe($('tag_list'), "focus", function(){ $('tag_list').editedByTracksUser = true; });
|
||||
Event.observe($('todo_context_name'), "focus", contextAutoCompleter.activate.bind(contextAutoCompleter));
|
||||
Event.observe($('todo_context_name'), "click", contextAutoCompleter.activate.bind(contextAutoCompleter));
|
||||
}
|
||||
|
||||
function updateContextNamesForAutoComplete(contextNamesForAutoComplete) {
|
||||
if (contextAutoCompleter) { // i.e. if we're already initialized
|
||||
contextAutoCompleter.options.array = contextNamesForAutoComplete
|
||||
} else {
|
||||
initializeNamesForAutoComplete(contextNamesForAutoComplete)
|
||||
}
|
||||
}
|
||||
initializeNamesForAutoComplete(contextNames);
|
||||
$('todo_context_name').projectDefaultContextsMap = eval('(' + <%= @default_project_context_name_map %> + ')');
|
||||
$('tag_list').projectDefaultTagsMap = eval('(' + <%= @default_project_tags_map %> + ')');
|
||||
</script>
|
||||
|
||||
<%= calendar_setup( "todo_due" ) %>
|
||||
<%= calendar_setup( "todo_show_from" ) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div id="footer">
|
||||
<p>Send feedback on <%= TRACKS_VERSION %>: <a href="http://www.assembla.com/spaces/tracks-tickets/tickets">Bugs</a> | <a href="http://www.getontracks.org/forums/">Forum</a> | <a href="http://www.getontracks.org/wiki/">Wiki</a> | <a href="mailto:butshesagirl@rousette.org.uk?subject=Tracks feedback">Email</a> | <a href="http://www.getontracks.org/">Website</a> | <a href="http://www.getontracks.org/development/">Contribute</a></p>
|
||||
<p>Send feedback on <%= TRACKS_VERSION %>: <a href="http://www.assembla.com/spaces/tracks-tickets/tickets">Bugs</a> | <a href="http://www.getontracks.org/forums/">Forum</a> | <a href="http://www.getontracks.org/wiki/">Wiki</a> | <a href="mailto:butshesagirl@rousette.org.uk?subject=Tracks feedback">Email</a> | <a href="http://www.getontracks.org/">Website</a> | <a href="http://getontracks.org/tracks/contribute">Contribute</a></p>
|
||||
</div>
|
||||
|
|
@ -1 +1 @@
|
|||
<div class="footer"><p>Mobile Tracks <%= TRACKS_VERSION %>: <a href="mailto:butshesagirl@rousette.org.uk?subject=Tracks feedback">Email</a> | <a href="http://www.rousette.org.uk/projects/">Website</a> | <a href="http://www.rousette.org.uk/projects/tracks/contribute">Contribute</a></p></div>
|
||||
<div class="footer"><p>Mobile Tracks <%= TRACKS_VERSION %>: <a href="mailto:butshesagirl@rousette.org.uk?subject=Tracks feedback">Email</a> | <a href="http://www.getontracks.org/">Website</a> | <a href="http://getontracks.org/tracks/contribute">Contribute</a></p></div>
|
||||
|
|
@ -3,15 +3,16 @@
|
|||
<% if collapsible %>
|
||||
<a href="#" class="container_toggle" id="toggle_deferred"><%= image_tag("collapse.png") %></a>
|
||||
<% end %>
|
||||
Deferred actions <%= append_descriptor ? append_descriptor : '' %>
|
||||
Deferred/pending actions <%= append_descriptor ? append_descriptor : '' %>
|
||||
</h2>
|
||||
|
||||
<div id="tickleritems" class="items toggle_target">
|
||||
<div id="tickler-empty-nd" style="display:<%= deferred.empty? ? 'block' : 'none'%>;">
|
||||
<div class="message"><p>Currently there are no deferred actions</p></div>
|
||||
<div id="tickler-empty-nd" style="display:<%= deferred.empty? && pending.empty? ? 'block' : 'none'%>;">
|
||||
<div class="message"><p>Currently there are no deferred or pending actions</p></div>
|
||||
</div>
|
||||
|
||||
<%= render :partial => "todos/todo", :collection => deferred, :locals => { :parent_container_type => parent_container_type } %>
|
||||
<%= render :partial => "todos/todo", :collection => pending, :locals => { :parent_container_type => parent_container_type } %>
|
||||
|
||||
</div><!-- [end:items] -->
|
||||
</div><!-- [end:tickler] -->
|
||||
</div><!-- [end:tickler] -->
|
||||
|
|
|
|||
|
|
@ -14,42 +14,12 @@
|
|||
<label for="<%= dom_id(@todo, 'project_name') %>">Project</label>
|
||||
<input id="<%= dom_id(@todo, 'project_name') %>" name="project_name" autocomplete="off" tabindex="10" size="30" type="text" value="<%= @todo.project.nil? ? 'None' : @todo.project.name.gsub(/"/,""") %>" />
|
||||
<div class="page_name_auto_complete" id="<%= dom_id(@todo, 'project_list') %>" style="display:none"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
<%= dom_id(@todo, 'project_autocompleter') %> = new Autocompleter.Local('<%= dom_id(@todo, 'project_name') %>', '<%= dom_id(@todo, 'project_list') %>', projectNames, {choices:100,autoSelect:false});
|
||||
function selectDefaultContext() {
|
||||
todoContextNameElement = $('<%= dom_id(@todo, 'context_name') %>');
|
||||
defaultContextName = $('todo_context_name').projectDefaultContextsMap[this.value];
|
||||
if (defaultContextName && !todoContextNameElement.editedByTracksUser) {
|
||||
todoContextNameElement.value = defaultContextName;
|
||||
}
|
||||
}
|
||||
function selectDefaultTags() {
|
||||
todoTagListElement = $('<%= dom_id(@todo, 'tag_list') %>');
|
||||
defaultTagList = $('tag_list').projectDefaultTagsMap[this.value];
|
||||
if (defaultTagList && !todoTagListElement.editedByTracksUser) {
|
||||
todoTagListElement.value = defaultTagList;
|
||||
}
|
||||
}
|
||||
Event.observe($('<%= dom_id(@todo, 'project_name') %>'), "focus", <%= dom_id(@todo, 'project_autocompleter') %>.activate.bind(<%= dom_id(@todo, 'project_autocompleter') %>));
|
||||
Event.observe($('<%= dom_id(@todo, 'project_name') %>'), "click", <%= dom_id(@todo, 'project_autocompleter') %>.activate.bind(<%= dom_id(@todo, 'project_autocompleter') %>));
|
||||
Event.observe($('<%= dom_id(@todo, 'project_name') %>'), "blur", selectDefaultContext.bind($('<%= dom_id(@todo, 'project_name') %>')));
|
||||
Event.observe($('<%= dom_id(@todo, 'project_name') %>'), "blur", selectDefaultTags.bind($('<%= dom_id(@todo, 'project_name') %>')));
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<div class="context_input">
|
||||
<label for="<%= dom_id(@todo, 'context_name') %>">Context</label>
|
||||
<input id="<%= dom_id(@todo, 'context_name') %>" name="context_name" autocomplete="off" tabindex="11" size="30" type="text" value="<%= @todo.context.name %>" />
|
||||
<div class="page_name_auto_complete" id="<%= dom_id(@todo, 'context_list') %>" style="display:none"></div>
|
||||
|
||||
<script type="text/javascript">
|
||||
<%= dom_id(@todo, 'context_autocompleter') %> = new Autocompleter.Local('<%= dom_id(@todo, 'context_name') %>', '<%= dom_id(@todo, 'context_list') %>', contextNames, {choices:100,autoSelect:false});
|
||||
Event.observe($('<%= dom_id(@todo, 'context_name') %>'), "focus", function(){ $('<%= dom_id(@todo, 'context_name') %>').editedByTracksUser = true; });
|
||||
Event.observe($('<%= dom_id(@todo, 'tag_list') %>'), "focus", function(){ $('<%= dom_id(@todo, 'tag_list') %>').editedByTracksUser = true; });
|
||||
Event.observe($('<%= dom_id(@todo, 'context_name') %>'), "focus", <%= dom_id(@todo, 'context_autocompleter') %>.activate.bind(<%= dom_id(@todo, 'context_autocompleter') %>));
|
||||
Event.observe($('<%= dom_id(@todo, 'context_name') %>'), "click", <%= dom_id(@todo, 'context_autocompleter') %>.activate.bind(<%= dom_id(@todo, 'context_autocompleter') %>));
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<label class="tag_list_label" for="<%= dom_id(@todo, 'tag_list') %>">Tags (separate with commas)</label>
|
||||
|
|
@ -76,30 +46,27 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
<label class="predecessor_list_label" for="<%= dom_id(@todo, 'predecessor_list') %>">Depends on (separate with commas)</label>
|
||||
<%= text_field_tag 'predecessor_list', predecessor_list_text, :id => dom_id(@todo, 'predecessor_list'), :size => 30, :tabindex => 15 %>
|
||||
<%= content_tag("div", "", :id => dom_id(@todo, 'predecessor_list')+"_auto_complete", :class => "auto_complete") %>
|
||||
<%= auto_complete_field dom_id(@todo, 'predecessor_list'), {
|
||||
:url => {:controller => 'todos', :action => 'auto_complete_for_predecessor', :id => @todo.id},
|
||||
:tokens => [',']
|
||||
} %>
|
||||
|
||||
<% if controller.controller_name == "project" || @todo.deferred? -%>
|
||||
<input type="hidden" name="on_project_page" value="true" />
|
||||
<% end -%>
|
||||
|
||||
<div class="submit_box">
|
||||
<div class="widgets">
|
||||
<button type="submit" class="positive" id="<%= dom_id(@todo, 'submit') %>" tabindex="15">
|
||||
<button type="submit" class="positive" id="<%= dom_id(@todo, 'submit') %>" tabindex="16">
|
||||
<%=image_tag("accept.png", :alt => "") %>
|
||||
Update
|
||||
</button>
|
||||
<a href="javascript:void(0);" onclick="Element.toggle('<%= dom_id(@todo, 'line') %>');Element.toggle('<%= dom_id(@todo, 'edit') %>');" class="negative">
|
||||
<a href="#" class="negative">
|
||||
<%=image_tag("cancel.png", :alt => "") %>
|
||||
Cancel
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%= calendar_setup( dom_id(@todo, 'due') ) %>
|
||||
<%= calendar_setup( dom_id(@todo, 'show_from') ) %>
|
||||
|
||||
<script>
|
||||
jQuery(function(){
|
||||
jQuery(".date_clear").click(function() {
|
||||
/* add behavior to clear the date both buttons for show_from and due */
|
||||
jQuery(this).prev().val('');
|
||||
})})
|
||||
</script>
|
||||
26
app/views/todos/_successor.html.erb
Normal file
26
app/views/todos/_successor.html.erb
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<%
|
||||
suppress_context ||= false
|
||||
suppress_project ||= false
|
||||
suppress_dependencies ||= false
|
||||
parameters = "_source_view=#{@source_view}"
|
||||
parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
|
||||
@z_index_counter = @z_index_counter - 1 # for IE z-index bug
|
||||
%>
|
||||
<div id="<%= dom_id(successor, 'successor') %>" class="item-container">
|
||||
<div id="<%= dom_id(successor, 'successor_line') %>">
|
||||
<div class="description<%= staleness_class( successor ) %>" style="margin-left: 20px">
|
||||
<span class="todo.descr"><%= h sanitize(successor.description) %></span>
|
||||
|
||||
<%= link_to_remote(
|
||||
image_tag("blank.png", :title => "Remove dependency (does not delete the action)", :align => "absmiddle", :class => "delete_item"),
|
||||
{:url => {:controller => 'todos', :action => 'remove_predecessor', :id => successor.id},
|
||||
:method => 'delete',
|
||||
:with => "'#{parameters}&predecessor=#{predecessor.id}'",
|
||||
:before => successor_start_waiting_js(successor)},
|
||||
{:style => "background: transparent;"}) %>
|
||||
|
||||
<%= render(:partial => "todos/toggle_successors", :locals => { :item => successor, :suppress_button => true }) unless successor.pending_successors.empty? %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -8,27 +8,35 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
|
|||
@z_index_counter = @z_index_counter - 1 # for IE z-index bug
|
||||
%>
|
||||
<div id="<%= dom_id(todo) %>" class="item-container">
|
||||
<div id="<%= dom_id(todo, 'line') %>">
|
||||
<div id="<%= dom_id(todo, 'line') %>" class="item-show">
|
||||
<%= remote_star_icon %>
|
||||
<%= remote_toggle_checkbox unless source_view_is :deferred %>
|
||||
<ul class="sf-menu sf-item-menu"><li style="z-index:<%=@z_index_counter%>"><%= image_tag "downarrow.png", :alt=> "" %><ul id="ul<%= dom_id(todo) %>">
|
||||
<% unless suppress_edit_button %>
|
||||
<li><%= remote_edit_menu_item(parameters, todo) %></li>
|
||||
<% end %>
|
||||
<li><%= remote_delete_menu_item(parameters, todo) %></li>
|
||||
<% unless todo.completed? || todo.deferred? %>
|
||||
<li><%= remote_defer_menu_item(1, todo) %></li>
|
||||
<li><%= remote_defer_menu_item(7, todo) %></li>
|
||||
<% end %>
|
||||
</ul></ul>
|
||||
<ul class="sf-menu sf-item-menu">
|
||||
<li style="z-index:<%=@z_index_counter%>"><%= image_tag "downarrow.png", :alt=> "" %>
|
||||
<ul id="ul<%= dom_id(todo) %>">
|
||||
<% unless suppress_edit_button %>
|
||||
<li><%= remote_edit_menu_item(parameters, todo) %></li>
|
||||
<% end %>
|
||||
<li><%= remote_delete_menu_item(parameters, todo) %></li>
|
||||
<% unless todo.completed? || todo.deferred? %>
|
||||
<li><%= remote_defer_menu_item(1, todo) %></li>
|
||||
<li><%= remote_defer_menu_item(7, todo) %></li>
|
||||
<% end %>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="description<%= staleness_class( todo ) %>">
|
||||
<%= grip_span %>
|
||||
<%= date_span -%>
|
||||
<span class="todo.descr"><%= sanitize(todo.description) %></span>
|
||||
<span class="todo.descr"><%= h sanitize(todo.description) %></span>
|
||||
<% #= successors_span %>
|
||||
<%= image_tag_for_recurring_todo(todo) if @todo.from_recurring_todo? %>
|
||||
<%= tag_list %>
|
||||
<%= deferred_due_date %>
|
||||
<%= project_and_context_links( parent_container_type, :suppress_context => suppress_context, :suppress_project => suppress_project ) %>
|
||||
<%= render(:partial => "todos/toggle_notes", :locals => { :item => todo }) if todo.notes? %>
|
||||
<%= render(:partial => "todos/toggle_successors", :locals => { :item => todo }) unless todo.pending_successors.empty? %>
|
||||
</div>
|
||||
</div>
|
||||
<div id="<%= dom_id(todo, 'edit') %>" class="edit-form" style="display:none">
|
||||
|
|
@ -37,9 +45,5 @@ parameters += "&_tag_name=#{@tag_name}" if @source_view == 'tag'
|
|||
<div class="placeholder"> </div>
|
||||
<% end -%>
|
||||
</div>
|
||||
<<<<<<< HEAD:app/views/todos/_todo.html.erb
|
||||
</div>
|
||||
=======
|
||||
</div>
|
||||
<%= apply_behaviour ".date_clear:click","var selector_x = this.getAttribute('id').replace('_x', ''); $(selector_x).value='';" %>
|
||||
>>>>>>> Don't run double sanitation on a string.:app/views/todos/_todo.html.erb
|
||||
|
|
|
|||
15
app/views/todos/_toggle_successors.html.erb
Normal file
15
app/views/todos/_toggle_successors.html.erb
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<%
|
||||
suppress_button ||= false
|
||||
%>
|
||||
<%= link_to(image_tag( 'blank.png', :width=>'16', :height=>'16', :border=>'0' ), "#", {:class => 'show_successors', :title => 'Show successors'}) unless suppress_button %>
|
||||
|
||||
<div class="todo_successors" id="<%= dom_id(item, 'successors') %>" style=<%= suppress_button ? "display:display" : "display:none" %> >
|
||||
<%= render :partial => "todos/successor",
|
||||
:collection => item.pending_successors,
|
||||
:locals => { :todo => item,
|
||||
:parent_container_type => parent_container_type,
|
||||
:suppress_dependencies => true,
|
||||
:predecessor => item }
|
||||
%>
|
||||
</div>
|
||||
|
||||
27
app/views/todos/add_predecessor.js.rjs
Normal file
27
app/views/todos/add_predecessor.js.rjs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
if @saved
|
||||
# show update message
|
||||
status_message = "Added #{@predecessor.description} as dependency."
|
||||
unless @original_state == 'pending'
|
||||
status_message += " #{@todo.description} set to pending"
|
||||
end
|
||||
# remove successor from page
|
||||
page[@todo].remove
|
||||
# regenerate predecessor to add arrow
|
||||
page[@predecessor].replace_html :partial => 'todos/todo', :locals => { :todo => @predecessor, :parent_container_type => parent_container_type }
|
||||
|
||||
# show in tickler box in project view
|
||||
if source_view_is_one_of :project, :tag
|
||||
page['tickler-empty-nd'].hide
|
||||
page.replace "tickler", :partial => 'todos/deferred', :locals => { :deferred => @todo.project.deferred_todos,
|
||||
:collapsible => false,
|
||||
:append_descriptor => "in this project",
|
||||
:parent_container_type => 'project',
|
||||
:pending => @todo.project.pending_todos }
|
||||
end
|
||||
|
||||
page << 'enable_rich_interaction();'
|
||||
page.notify :notice, status_message, 5.0
|
||||
else
|
||||
page.replace_html "status", content_tag("div", content_tag("h2", "Unable to add dependency"), "id" => "errorExplanation", "class" => "errorExplanation")
|
||||
end
|
||||
|
||||
|
|
@ -59,17 +59,6 @@
|
|||
var projectNames = <%= project_names_for_autocomplete %>;
|
||||
$('todo_context_name').projectDefaultContextsMap = eval('(' + <%= @default_project_context_name_map %> + ')');
|
||||
</script>
|
||||
<!--
|
||||
<input class="hide_tickler" id="hide_tickler" type="checkbox" tabindex="5" name="hide_tickler" checked="true"/>
|
||||
<label for="hide_tickler"> Show actions in tickler</label>
|
||||
<br/><br/>
|
||||
-->
|
||||
<p><%= link_to('<span class="feed">iCal</span>', {:format => 'ics', :token => current_user.token}, :title => "iCal feed" ) %>
|
||||
- Get this calendar in iCal format</p>
|
||||
</div>
|
||||
|
||||
<%
|
||||
apply_behavior 'input.hide_tickler:click', :prevent_default => true do |page|
|
||||
page << "alert('hiding action in tickler from calendar is not yet implemented');"
|
||||
end
|
||||
%>
|
||||
|
|
@ -11,6 +11,7 @@ X-WR-CALNAME:Tracks
|
|||
due_date = Time.zone.now
|
||||
overdue_text = "Overdue: "
|
||||
end
|
||||
modified = todo.updated_at || todo.created_at
|
||||
%>BEGIN:VEVENT
|
||||
DTSTART;VALUE=DATE:<%= due_date.strftime("%Y%m%d") %>
|
||||
DTEND;VALUE=DATE:<%= (due_date+1.day).strftime("%Y%m%d") %>
|
||||
|
|
@ -20,7 +21,7 @@ CLASS:PUBLIC
|
|||
CATEGORIES:Tracks
|
||||
CREATED:<%= todo.created_at.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
DESCRIPTION:<%= format_ical_notes(todo.notes) %>
|
||||
LAST-MODIFIED:<%= todo.updated_at.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
LAST-MODIFIED:<%= modified.strftime("%Y%m%dT%H%M%SZ") %>
|
||||
LOCATION:
|
||||
SEQUENCE:0
|
||||
STATUS:CONFIRMED
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@ if @saved
|
|||
page.hide 'status'
|
||||
status_message = 'Added new next action'
|
||||
status_message += ' to tickler' if @todo.deferred?
|
||||
status_message += ' in pending state' if @todo.pending?
|
||||
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, "$('#todo-form-new-action').clearForm();$('#todo-form-new-action input:text:first').focus();"
|
||||
page['todo_context_name'].value = @initial_context_name
|
||||
page['todo_project_name'].value = @initial_project_name
|
||||
page << "updateContextNamesForAutoComplete(#{context_names_for_autocomplete})" if @new_context_created
|
||||
page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}" if @new_project_created
|
||||
page['tag_list'].value = @default_tags
|
||||
#page << "updateContextNamesForAutoComplete(#{context_names_for_autocomplete})" if @new_context_created
|
||||
#page << "projectAutoCompleter.options.array = #{project_names_for_autocomplete}" if @new_project_created
|
||||
if should_show_new_item()
|
||||
if @new_context_created
|
||||
page.insert_html :top, 'display_box', :partial => 'contexts/context', :locals => { :context => @todo.context, :collapsible => true }
|
||||
|
|
@ -20,10 +22,19 @@ if @saved
|
|||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil?
|
||||
end
|
||||
# make sure the behavior of the new/updated todo is enabled
|
||||
page << "TodoBehavior.enableToggleNotes()"
|
||||
page['tickler-empty-nd'].hide if source_view_is :deferred
|
||||
if (source_view_is :project and @todo.pending?) or (source_view_is :deferred)
|
||||
page['tickler-empty-nd'].hide # For some reason this does not work: page['tickler-empty-nd'].hide if (@todo.pending? or (source_view_is :deferred))
|
||||
end
|
||||
end
|
||||
# Update predecessors (if they exist and are visible)
|
||||
@todo.uncompleted_predecessors.each do |p|
|
||||
page << "if ($(\'#{item_container_id(p)}\')) {"
|
||||
page[p].replace_html :partial => 'todos/todo',
|
||||
:locals => { :todo => p, :parent_container_type => parent_container_type }
|
||||
page << "}"
|
||||
end
|
||||
# make sure the behavior of the new/updated todo is enabled
|
||||
page << "enable_rich_interaction();"
|
||||
else
|
||||
page.show 'status'
|
||||
page.replace_html 'status', "#{error_messages_for('todo', :object_name => 'action')}"
|
||||
|
|
|
|||
|
|
@ -4,23 +4,33 @@ if @saved
|
|||
page['badge_count'].replace_html @down_count
|
||||
|
||||
# remove context if empty
|
||||
page.visual_effect :fade, item_container_id(@todo), :duration => 0.4 if source_view_is_one_of(:todo, :deferred) && @remaining_in_context == 0
|
||||
page.visual_effect(:fade, "c#{@todo.context_id}", :duration => 0.4) if (@remaining_in_context == 0)
|
||||
|
||||
# show message if there are no actions
|
||||
page[empty_container_msg_div_id].show if !empty_container_msg_div_id.nil? && @down_count == 0
|
||||
page['tickler-empty-nd'].show if source_view_is(:deferred) && @down_count == 0
|
||||
|
||||
# show new todo if the completed todo was recurring
|
||||
unless @new_recurring_todo.nil? || @new_recurring_todo.deferred?
|
||||
page.call "todoItems.ensureVisibleWithEffectAppear", item_container_id(@new_recurring_todo)
|
||||
page.insert_html :bottom, item_container_id(@new_recurring_todo), :partial => 'todos/todo', :locals => { :todo => @new_recurring_todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@new_recurring_todo, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
page.notify :notice, "Action was deleted. Because this action is recurring, a new action was added", 6.0
|
||||
else
|
||||
if @todo.recurring_todo.todos.active.count == 0
|
||||
page.notify :notice, "There is no next action after the recurring action you just deleted. The recurrence is completed", 6.0 unless @recurring_todo.nil?
|
||||
if @todo.from_recurring_todo?
|
||||
unless @new_recurring_todo.nil? || @new_recurring_todo.deferred?
|
||||
page.call "todoItems.ensureVisibleWithEffectAppear", item_container_id(@new_recurring_todo)
|
||||
page.insert_html :bottom, item_container_id(@new_recurring_todo), :partial => 'todos/todo', :locals => { :todo => @new_recurring_todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@new_recurring_todo, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
page.notify :notice, "Action was deleted. Because this action is recurring, a new action was added", 6.0
|
||||
else
|
||||
if @todo.recurring_todo.todos.active.count == 0
|
||||
page.notify :notice, "There is no next action after the recurring action you just deleted. The recurrence is completed", 6.0 if @new_recurring_todo.nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Activate pending todos that are successors of the deleted
|
||||
@pending_to_activate.each do |t|
|
||||
logger.debug "#300: Removing #{t.description} from pending block and adding it to active"
|
||||
page[t].remove if source_view_is(:project) or source_view_is(:tag)
|
||||
page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(t, 'line'), {'startcolor' => "'#99ff99'", :duration => 2}
|
||||
end
|
||||
else
|
||||
page.notify :error, "There was an error deleting the item #{@todo.description}", 8.0
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
page[dom_id(@todo, 'form')].down('.placeholder').replace_html :partial => 'todos/edit_form'
|
||||
page[dom_id(@todo, 'line')].hide
|
||||
page[dom_id(@todo, 'form')].find('.placeholder').show().replace_html :partial => 'todos/edit_form'
|
||||
page[dom_id(@todo, 'edit')].show
|
||||
page[dom_id(@todo, 'form')].down('input#todo_description').focus
|
||||
page[dom_id(@todo, 'line')].hide
|
||||
page[dom_id(@todo, 'form')].find('input#todo_description').show().focus
|
||||
|
|
|
|||
25
app/views/todos/remove_predecessor.js.rjs
Normal file
25
app/views/todos/remove_predecessor.js.rjs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
if @removed
|
||||
status_message = "Removed #{@successor.description} as dependency from #{@predecessor.description}."
|
||||
page.notify :notice, status_message, 5.0
|
||||
|
||||
# replace old predecessor with one without the successor
|
||||
page.replace dom_id(@predecessor), :partial => 'todos/todo', :locals => {
|
||||
:todo => @predecessor, :parent_container_type => parent_container_type }
|
||||
|
||||
# update display if pending->active
|
||||
if @successor.active?
|
||||
page[@successor].remove unless source_view_is_one_of(:todo, :context)
|
||||
page.insert_html :bottom, item_container_id(@successor), :partial => 'todos/todo', :locals => {
|
||||
:todo => @successor, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@successor, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
end
|
||||
|
||||
# update display if pending->deferred
|
||||
if @successor.deferred?
|
||||
page.replace dom_id(@successor), :partial => 'todos/todo', :locals => {
|
||||
:todo => @successor, :parent_container_type => parent_container_type }
|
||||
end
|
||||
page << "enable_rich_interaction();"
|
||||
else
|
||||
page.notify :error, "There was an error removing the dependency", 8.0
|
||||
end
|
||||
|
|
@ -8,7 +8,13 @@
|
|||
:locals => { :collapsible => true } %>
|
||||
|
||||
<% unless @deferred.nil? -%>
|
||||
<%= render :partial => "todos/deferred", :locals => { :deferred => @deferred, :collapsible => true, :append_descriptor => "tagged with ‘#{@tag_name}’", :parent_container_type => 'tag' } %>
|
||||
<%= render :partial => "todos/deferred", :locals => {
|
||||
:deferred => @deferred,
|
||||
:pending => @pending,
|
||||
:collapsible => true,
|
||||
:append_descriptor => "tagged with ‘#{@tag_name}’",
|
||||
:parent_container_type => 'tag'
|
||||
} %>
|
||||
<% end -%>
|
||||
|
||||
<% unless @hidden_todos.nil? -%>
|
||||
|
|
|
|||
|
|
@ -8,13 +8,20 @@ if @saved
|
|||
page.insert_html :top, "completed_containeritems", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => "completed" }
|
||||
page.visual_effect :highlight, dom_id(@todo, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
page[empty_container_msg_div_id].show if @down_count == 0 && !empty_container_msg_div_id.nil?
|
||||
page.show 'tickler-empty-nd' if source_view_is(:project) && @deferred_count == 0
|
||||
page.show 'tickler-empty-nd' if source_view_is(:project) && @deferred_count == 0 && @pending_count == 0
|
||||
page.hide 'empty-d' # If we've checked something as done, completed items can't be empty
|
||||
end
|
||||
# Activate pending todos that are successors of the completed
|
||||
@pending_to_activate.each do |t|
|
||||
logger.debug "#300: Removing #{t.description} from pending block and adding it to active"
|
||||
page[t].remove if source_view_is(:project) or source_view_is(:tag)
|
||||
page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(t, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
end
|
||||
|
||||
# remove container if empty
|
||||
if @remaining_in_context == 0 && source_view_is(:todo)
|
||||
page.visual_effect :fade, item_container_id(@todo), :duration => 0.4
|
||||
page.visual_effect :fade, "c"+@todo.context.id.to_s, :duration => 0.4
|
||||
end
|
||||
|
||||
if @original_item_was_deferred && source_view_is(:tag)
|
||||
|
|
@ -42,7 +49,17 @@ if @saved
|
|||
page.insert_html :bottom, item_container_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo, 'line'), {'startcolor' => "'#99ff99'"}
|
||||
page.show "empty-d" if @completed_count == 0
|
||||
page.show "c"+@todo.context.id.to_s
|
||||
page[empty_container_msg_div_id].hide unless empty_container_msg_div_id.nil? # If we've checked something as undone, incomplete items can't be empty
|
||||
# If active todos are successors of the reactivated todo they will be blocked
|
||||
@active_to_block.each do |t|
|
||||
logger.debug "#300: Block #{t.description} that are a successor of #{@todo.description}"
|
||||
page[t].remove # Remove it from active
|
||||
if source_view_is(:project) or source_view_is(:tag) # Insert it in deferred/pending block if existing
|
||||
logger.debug "Insert #{t.description} in #{item_container_id(t)} block"
|
||||
page.insert_html :bottom, item_container_id(t), :partial => 'todos/todo', :locals => { :todo => t, :parent_container_type => parent_container_type }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
page.hide "status"
|
||||
|
|
@ -53,5 +70,3 @@ if @saved
|
|||
else
|
||||
page.replace_html "status", content_tag("div", content_tag("h2", "#{pluralize(@todo.errors.count, "error")} prohibited this action from being saved") + content_tag("p", "There were problems with the following fields:") + content_tag("ul", @todo.errors.each_full { |msg| content_tag("li", msg) }), "id" => "errorExplanation", "class" => "errorExplanation")
|
||||
end
|
||||
# make sure the behavior of the new/updated todo is enabled
|
||||
page << "TodoBehavior.enableToggleNotes()"
|
||||
3
app/views/todos/toggle_star.js.erb
Normal file
3
app/views/todos/toggle_star.js.erb
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<% if @saved -%>
|
||||
$('div#line_todo_<%= @todo.id %> a.star_item img').toggleClass('starred_todo').toggleClass('unstarred_todo');
|
||||
<% end -%>
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
if @saved
|
||||
page[@todo].down('a.star_item').down('img').toggleClassName('starred_todo').toggleClassName('unstarred_todo')
|
||||
end
|
||||
|
|
@ -6,15 +6,8 @@ if @saved
|
|||
status_message = 'Added new context / ' + status_message if @new_context_created
|
||||
page.notify :notice, status_message, 5.0
|
||||
|
||||
# update auto completer arrays for edit form in right column, only for pages
|
||||
# with that form
|
||||
unless source_view_is_one_of(:calendar)
|
||||
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
|
||||
end
|
||||
|
||||
if source_view_is_one_of(:todo, :context, :tag)
|
||||
if @context_changed || @todo.deferred?
|
||||
if @context_changed || @todo.deferred? || @todo.pending?
|
||||
page[@todo].remove
|
||||
|
||||
if (@remaining_in_context == 0)
|
||||
|
|
@ -27,8 +20,8 @@ if @saved
|
|||
end
|
||||
else
|
||||
source_view do |from|
|
||||
from.todo { page.visual_effect :fade, item_container_id(@todo), :duration => 0.4 }
|
||||
from.tag { page.visual_effect :fade, item_container_id(@todo), :duration => 0.4 }
|
||||
from.todo { page.visual_effect :fade, "c#{@todo.context.id}", :duration => 0.4 }
|
||||
from.tag { page.visual_effect :fade, "c#{@todo.context.id}", :duration => 0.4 }
|
||||
from.context { page.show "c#{@original_item_context_id}empty-nd" }
|
||||
end
|
||||
end
|
||||
|
|
@ -89,11 +82,19 @@ if @saved
|
|||
elsif @todo_was_activated_from_deferred_state
|
||||
page[@todo].remove
|
||||
page['tickler-empty-nd'].show if (@deferred_count == 0)
|
||||
page.insert_html :bottom, "p#{@todo.project_id}", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.insert_html :bottom, "p#{@todo.project_id}items", :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
page["p#{@todo.project_id}empty-nd"].hide
|
||||
page.replace_html "badge_count", @down_count
|
||||
else
|
||||
page.replace dom_id(@todo), :partial => 'todos/todo', :locals => { :todo => @todo, :parent_container_type => parent_container_type }
|
||||
page.replace_html "p#{@todo.project_id}items", :partial => 'todos/todo', :collection => @todo.project.not_done_todos,
|
||||
:locals => { :parent_container_type => parent_container_type }
|
||||
page.replace "tickler", :partial => 'todos/deferred', :locals => { :deferred => @todo.project.deferred_todos,
|
||||
:collapsible => false,
|
||||
:append_descriptor => "in this project",
|
||||
:parent_container_type => 'project',
|
||||
:pending => @todo.project.pending_todos }
|
||||
page['tickler-empty-nd'].show if (@deferred_count == 0 and @pending_count == 0)
|
||||
page.visual_effect :highlight, dom_id(@todo), :duration => 3
|
||||
end
|
||||
elsif source_view_is :deferred
|
||||
|
|
@ -141,9 +142,14 @@ if @saved
|
|||
else
|
||||
logger.error "unexpected source_view '#{params[:_source_view]}'"
|
||||
end
|
||||
# make sure the behavior of the new/updated todo is enabled
|
||||
page << "TodoBehavior.enableToggleNotes()"
|
||||
# Update predecessors (if they exist and are visible)
|
||||
@todo.uncompleted_predecessors.each do |p|
|
||||
page << "if ($(\'#{item_container_id(p)}\')) {"
|
||||
page[p].replace_html :partial => 'todos/todo',
|
||||
:locals => { :todo => p, :parent_container_type => parent_container_type }
|
||||
page << "}"
|
||||
end
|
||||
else
|
||||
page.show 'error_status'
|
||||
page.replace_html 'error_status', "#{error_messages_for('todo')}"
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -2,34 +2,38 @@
|
|||
|
||||
<p>You have a total of <span id="user_count"><%= @total_users %></span> users</p>
|
||||
|
||||
<table class="users_table">
|
||||
<table class="users_table">
|
||||
<tr>
|
||||
<th>Login</th>
|
||||
<th>Full name</th>
|
||||
<th>Authorization type</th>
|
||||
<th>Open ID URL</th>
|
||||
<th>Total actions</th>
|
||||
<th>Total contexts</th>
|
||||
<th>Total projects</th>
|
||||
<th>Total notes</th>
|
||||
<th> </th>
|
||||
<th>Login</th>
|
||||
<th>Full name</th>
|
||||
<th>Authorization type</th>
|
||||
<th>Open ID URL</th>
|
||||
<th>Total actions</th>
|
||||
<th>Total contexts</th>
|
||||
<th>Total projects</th>
|
||||
<th>Total notes</th>
|
||||
<th> </th>
|
||||
</tr>
|
||||
<% for user in @users %>
|
||||
<tr <%= "class=\"highlight\"" if user.is_admin? %> id="user-<%= user.id %>">
|
||||
<td><%=h user.login %></td>
|
||||
<td><%=h user.last_name? ? user.display_name : '-' %></td>
|
||||
<td><%= h user.auth_type %></td>
|
||||
<td><%= h user.open_id_url || '-' %></td>
|
||||
<td><%= h user.todos.size %></td>
|
||||
<td><%= h user.contexts.size %></td>
|
||||
<td><%= h user.projects.size %></td>
|
||||
<td><%= h user.notes.size %></td>
|
||||
<td><%= !user.is_admin? ? link_to_remote( image_tag("blank.png", :title =>"Destroy user", :class=>"delete_item"), {:url => user_path(user), :method => :delete, :confirm => "Warning: this will delete user \'#{user.login}\', all their actions, contexts, project and notes. Are you sure that you want to continue?" }, { :class => "icon" } ) : " " %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<p>
|
||||
<%= will_paginate @users %>
|
||||
</p>
|
||||
|
||||
<p><%= link_to 'Signup new user', signup_path %></p>
|
||||
<tr <%= "class=\"highlight\"" if user.is_admin? %> id="user-<%= user.id %>">
|
||||
<td><%=h user.login %></td>
|
||||
<td><%=h user.last_name? ? user.display_name : '-' %></td>
|
||||
<td><%= h user.auth_type %></td>
|
||||
<td><%= h user.open_id_url || '-' %></td>
|
||||
<td><%= h user.todos.size %></td>
|
||||
<td><%= h user.contexts.size %></td>
|
||||
<td><%= h user.projects.size %></td>
|
||||
<td><%= h user.notes.size %></td>
|
||||
<td><%= !user.is_admin? ? link_to_remote(
|
||||
image_tag("blank.png", :title =>"Destroy user", :class=>"delete_item"),
|
||||
{ :url => user_path(user.id), :method => :delete,
|
||||
:confirm => "Warning: this will delete user \'#{user.login}\', all their actions, contexts, project and notes. Are you sure that you want to continue?" },
|
||||
{ :class => "icon" } ) : " " %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</table>
|
||||
<p>
|
||||
<%= will_paginate @users %>
|
||||
</p>
|
||||
|
||||
<p><%= link_to 'Signup new user', signup_path %></p>
|
||||
|
|
@ -58,6 +58,11 @@ Rails::Initializer.run do |config|
|
|||
# (enables use of different database adapters for development and test environments)
|
||||
config.active_record.schema_format = :ruby
|
||||
|
||||
# allow other protocols in urls for sanitzer. Add to your liking, for example
|
||||
# config.action_view.sanitized_allowed_protocols = 'onenote', 'blah', 'proto'
|
||||
# to enable "link":onenote://... or "link":blah://... hyperlinks
|
||||
config.action_view.sanitized_allowed_protocols = 'onenote'
|
||||
|
||||
# See Rails::Configuration for more options
|
||||
end
|
||||
|
||||
|
|
@ -78,7 +83,6 @@ require 'tracks/todo_list'
|
|||
require 'tracks/config'
|
||||
require 'tagging_extensions' # Needed for tagging-specific extensions
|
||||
require 'digest/sha1' #Needed to support 'rake db:fixtures:load' on some ruby installs: http://dev.rousette.org.uk/ticket/557
|
||||
require 'prototype_helper_extensions'
|
||||
|
||||
if ( SITE_CONFIG['authentication_schemes'].include? 'ldap')
|
||||
require 'net/ldap' #requires ruby-net-ldap gem be installed
|
||||
|
|
|
|||
25
config/initializers/core_ext.rb
Normal file
25
config/initializers/core_ext.rb
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# See test_url_with_slash_in_query_string_are_parsed_correctly in test/functional/todos_controller_test.rb
|
||||
# and http://blog.swivel.com/code/2009/06/rails-auto_link-and-certain-query-strings.html
|
||||
module ActionView::Helpers::TextHelper
|
||||
remove_const :AUTO_LINK_RE
|
||||
AUTO_LINK_RE = %r{
|
||||
( # leading text
|
||||
<\w+.*?>| # leading HTML tag, or
|
||||
[^=!:'"/]| # leading punctuation, or
|
||||
^ # beginning of line
|
||||
)
|
||||
(
|
||||
(?:https?://)| # protocol spec, or
|
||||
(?:www\.) # www.*
|
||||
)
|
||||
(
|
||||
[-\w]+ # subdomain or domain
|
||||
(?:\.[-\w]+)* # remaining subdomains or domain
|
||||
(?::\d+)? # port
|
||||
(?:/(?:[~\w\+@%=\(\)-]|(?:[,.;:'][^\s$]))*)* # path
|
||||
(?:\?[\w\+@%&=.;:/-]+)? # query string
|
||||
(?:\#[\w\-]*)? # trailing anchor
|
||||
)
|
||||
([[:punct:]]|<|$|) # trailing text
|
||||
}x
|
||||
end
|
||||
|
|
@ -1,6 +1,4 @@
|
|||
ActionController::Routing::Routes.draw do |map|
|
||||
UJS::routes
|
||||
|
||||
map.with_options :controller => 'login' do |login|
|
||||
login.login 'login', :action => 'login'
|
||||
login.formatted_login 'login.:format', :action => 'login'
|
||||
|
|
@ -65,8 +63,9 @@ ActionController::Routing::Routes.draw do |map|
|
|||
|
||||
map.preferences 'preferences', :controller => 'preferences', :action => 'index'
|
||||
map.integrations 'integrations', :controller => 'integrations', :action => 'index'
|
||||
map.stats 'stats', :controller => 'stats', :action => 'index'
|
||||
map.search_plugin '/integrations/search_plugin.xml', :controller => 'integrations', :action => 'search_plugin', :format => 'xml'
|
||||
map.google_gadget '/integrations/google_gadget.xml', :controller => 'integrations', :action => 'google_gadget', :format => 'xml'
|
||||
map.stats 'stats', :controller => 'stats', :action => 'index'
|
||||
|
||||
map.resources :recurring_todos,
|
||||
:member => {:toggle_check => :put, :toggle_star => :put}
|
||||
|
|
|
|||
13
db/migrate/20090516000646_add_todo_dependencies.rb
Normal file
13
db/migrate/20090516000646_add_todo_dependencies.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
class AddTodoDependencies < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table :dependencies do |t|
|
||||
t.integer :successor_id, :null => false
|
||||
t.integer :predecessor_id, :null => false
|
||||
t.string :relationship_type
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :dependencies
|
||||
end
|
||||
end
|
||||
Binary file not shown.
Binary file not shown.
BIN
db/tracks-17-test.db
Normal file
BIN
db/tracks-17-test.db
Normal file
Binary file not shown.
7
features/step_definitions/generic_steps.rb
Normal file
7
features/step_definitions/generic_steps.rb
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
Then "the badge should show (.*)" do |number|
|
||||
badge = -1
|
||||
response.should have_xpath("//span[@id='badge_count']") do |node|
|
||||
badge = node.first.content.to_i
|
||||
end
|
||||
badge.should == number.to_i
|
||||
end
|
||||
4
features/step_definitions/project_steps.rb
Normal file
4
features/step_definitions/project_steps.rb
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
When /^I visit the "([^\"]*)" project$/ do |project_name|
|
||||
project = Project.find_by_name(project_name)
|
||||
visit project_path(project)
|
||||
end
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
Given /^the following user records?$/ do |table|
|
||||
User.delete_all
|
||||
table.hashes.each do |hash|
|
||||
user = Factory(:user, hash)
|
||||
user.create_preference
|
||||
|
|
@ -12,4 +13,4 @@ end
|
|||
Then "I should be an admin" do
|
||||
# just check on the presence of the menu item for managing users
|
||||
Then "I should see \"Manage users\""
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1,27 +0,0 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
module PrototypeHelper
|
||||
|
||||
def remote_to_href(options = {})
|
||||
remote_function(options.merge(:url => javascript_variable('this.href'))) + "\n"
|
||||
end
|
||||
|
||||
class JavaScriptGenerator #:nodoc:
|
||||
module GeneratorMethods
|
||||
|
||||
# Executes the content of the block if the user confirms the javascript confirmation. Example:
|
||||
#
|
||||
# page.confirming("Are you sure?") do
|
||||
# page.visual_effect :hide, 'information'
|
||||
# end
|
||||
def confirming(message)
|
||||
message = "'#{message}'" unless message =~ /^['"]/
|
||||
self << "if (confirm(#{message})) {"
|
||||
yield
|
||||
self << "}"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
BIN
public/images/grip.png
Normal file
BIN
public/images/grip.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 192 B |
BIN
public/images/successor_off.png
Normal file
BIN
public/images/successor_off.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.8 KiB |
BIN
public/images/successor_on.png
Normal file
BIN
public/images/successor_on.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -19,7 +19,7 @@ var accessKeyHintsAdder = {
|
|||
var elemTypes = new Array('a','area','button','input','label','legend','textarea');
|
||||
for(var i = 0; i < elemTypes.length; i++)
|
||||
{
|
||||
this.addHint(document.getElementsByTagName(elemTypes[i]));
|
||||
accessKeyHintsAdder.addHint(document.getElementsByTagName(elemTypes[i]));
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -66,4 +66,4 @@ var accessKeyHintsAdder = {
|
|||
|
||||
}
|
||||
|
||||
Event.observe(window, "load", accessKeyHintsAdder.run.bindAsEventListener(accessKeyHintsAdder));
|
||||
$(accessKeyHintsAdder.run);
|
||||
|
|
|
|||
|
|
@ -1,84 +1,29 @@
|
|||
var Login = {
|
||||
showOpenid: function() {
|
||||
if ($('database_auth_form')) $('database_auth_form').hide();
|
||||
if ($('openid_auth_form')) $('openid_auth_form').show();
|
||||
if ($('alternate_auth_openid')) $('alternate_auth_openid').hide();
|
||||
if ($('alternate_auth_database')) $('alternate_auth_database').show();
|
||||
if ($('openid_url')) $('openid_url').focus();
|
||||
if ($('openid_url')) $('openid_url').select();
|
||||
new CookieManager().setCookie('preferred_auth', 'openid');
|
||||
},
|
||||
|
||||
showDatabase: function(container) {
|
||||
if ($('openid_auth_form')) $('openid_auth_form').hide();
|
||||
if ($('database_auth_form')) $('database_auth_form').show();
|
||||
if ($('alternate_auth_database')) $('alternate_auth_database').hide();
|
||||
if ($('alternate_auth_openid')) $('alternate_auth_openid').show();
|
||||
if ($('user_login')) $('user_login').focus();
|
||||
if ($('user_login')) $('user_login').select();
|
||||
new CookieManager().setCookie('preferred_auth', 'database');
|
||||
}
|
||||
}
|
||||
|
||||
var TracksForm = {
|
||||
toggle: function(toggleDivId, formContainerId, formId, hideLinkText, hideLinkTitle, showLinkText, showLinkTitle) {
|
||||
$(formContainerId).toggle();
|
||||
toggleDiv = $(toggleDivId);
|
||||
toggleLink = toggleDiv.down('a');
|
||||
if (toggleDiv.hasClassName('hide_form')) {
|
||||
toggleLink.update(showLinkText).setAttribute('title', showLinkTitle);
|
||||
toggle: function(toggleDivId, formContainerId, formId, hideLinkText,
|
||||
hideLinkTitle, showLinkText, showLinkTitle) {
|
||||
$('#'+formContainerId).toggle();
|
||||
toggleDiv = $('#'+toggleDivId);
|
||||
toggleLink = toggleDiv.find('a');
|
||||
if (toggleDiv.hasClass('hide_form')) {
|
||||
toggleLink.text(showLinkText).attr('title', showLinkTitle);
|
||||
}
|
||||
else {
|
||||
toggleLink.update(hideLinkText).setAttribute('title', hideLinkTitle);
|
||||
Form.focusFirstElement(formId);
|
||||
toggleLink.text(hideLinkText).attr('title', hideLinkTitle);
|
||||
$('#'+formId+' input:text:first').focus();
|
||||
}
|
||||
toggleDiv.toggleClassName('hide_form');
|
||||
toggleDiv.toggleClass('hide_form');
|
||||
},
|
||||
get_period: function() {
|
||||
if ($('recurring_todo_recurring_period_daily').checked) {
|
||||
return 'daily';
|
||||
}
|
||||
else if ($('recurring_todo_recurring_period_weekly').checked) {
|
||||
return 'weekly';
|
||||
}
|
||||
else if ($('recurring_todo_recurring_period_monthly').checked) {
|
||||
return 'monthly';
|
||||
}
|
||||
else if ($('recurring_todo_recurring_period_yearly').checked) {
|
||||
return 'yearly';
|
||||
}
|
||||
else {
|
||||
return 'no period'
|
||||
}
|
||||
},
|
||||
get_edit_period: function() {
|
||||
if ($('recurring_edit_todo_recurring_period_daily').checked) {
|
||||
return 'daily';
|
||||
}
|
||||
else if ($('recurring_edit_todo_recurring_period_weekly').checked) {
|
||||
return 'weekly';
|
||||
}
|
||||
else if ($('recurring_edit_todo_recurring_period_monthly').checked) {
|
||||
return 'monthly';
|
||||
}
|
||||
else if ($('recurring_edit_todo_recurring_period_yearly').checked) {
|
||||
return 'yearly';
|
||||
}
|
||||
else {
|
||||
return 'no period'
|
||||
}
|
||||
},
|
||||
hide_all_recurring: function () {
|
||||
$('recurring_daily').hide();
|
||||
$('recurring_weekly').hide();
|
||||
$('recurring_monthly').hide();
|
||||
$('recurring_yearly').hide();
|
||||
$('#recurring_daily').hide();
|
||||
$('#recurring_weekly').hide();
|
||||
$('#recurring_monthly').hide();
|
||||
$('#recurring_yearly').hide();
|
||||
},
|
||||
hide_all_edit_recurring: function () {
|
||||
$('recurring_edit_daily').hide();
|
||||
$('recurring_edit_weekly').hide();
|
||||
$('recurring_edit_monthly').hide();
|
||||
$('recurring_edit_yearly').hide();
|
||||
$('#recurring_edit_daily').hide();
|
||||
$('#recurring_edit_weekly').hide();
|
||||
$('#recurring_edit_monthly').hide();
|
||||
$('#recurring_edit_yearly').hide();
|
||||
},
|
||||
toggle_overlay: function () {
|
||||
el = document.getElementById("overlay");
|
||||
|
|
@ -86,148 +31,424 @@ var TracksForm = {
|
|||
}
|
||||
}
|
||||
|
||||
var TodoBehavior = {
|
||||
enableToggleNotes: function() {
|
||||
jQuery(".show_notes").unbind('click').bind('click', function () {
|
||||
jQuery(this).next().toggle("fast"); return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
// uncomment the next four lines for easier debugging with FireBug
|
||||
// Ajax.Responders.register({
|
||||
// onException: function(source, exception) {
|
||||
// console.error(exception);
|
||||
// }
|
||||
// });
|
||||
$.fn.clearForm = function() {
|
||||
return this.each(function() {
|
||||
var type = this.type, tag = this.tagName.toLowerCase();
|
||||
if (tag == 'form')
|
||||
return $(':input',this).clearForm();
|
||||
if (type == 'text' || type == 'password' || tag == 'textarea')
|
||||
this.value = '';
|
||||
else if (type == 'checkbox' || type == 'radio')
|
||||
this.checked = false;
|
||||
else if (tag == 'select')
|
||||
this.selectedIndex = -1;
|
||||
});
|
||||
};
|
||||
|
||||
/* fade flashes automatically */
|
||||
Event.observe(window, 'load', function() {
|
||||
$A(document.getElementsByClassName('alert')).each(function(o) {
|
||||
o.opacity = 100.0
|
||||
Effect.Fade(o, {
|
||||
duration: 8.0
|
||||
})
|
||||
});
|
||||
/****************************************
|
||||
* Unobtrusive jQuery written by Eric Allen
|
||||
****************************************/
|
||||
|
||||
/* Set up authenticity token proplery */
|
||||
$(document).ajaxSend(function(event, request, settings) {
|
||||
if ( settings.type == 'POST' ) {
|
||||
if(typeof(AUTH_TOKEN) != 'undefined'){
|
||||
settings.data = (settings.data ? settings.data + "&" : "")
|
||||
+ "authenticity_token=" + encodeURIComponent( AUTH_TOKEN ) + "&"
|
||||
+ "_source_view=" + encodeURIComponent( SOURCE_VIEW );
|
||||
} else {
|
||||
settings.data = (settings.data ? settings.data + "&" : "")
|
||||
+ "_source_view=" + encodeURIComponent( SOURCE_VIEW );
|
||||
}
|
||||
request.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
|
||||
}
|
||||
request.setRequestHeader("Accept", "text/javascript");
|
||||
});
|
||||
|
||||
/**
|
||||
* Provides a simple interface for creating, retrieving and clearing cookies.
|
||||
* Adapted from Jonathan Buchanan's code at http://insin.woaf.net/code/javascript/cookiemanager.html
|
||||
*/
|
||||
CookieManager = Class.create();
|
||||
CookieManager.prototype =
|
||||
{
|
||||
BROWSER_IS_IE:
|
||||
(document.all
|
||||
&& window.ActiveXObject
|
||||
&& navigator.userAgent.toLowerCase().indexOf("msie") > -1
|
||||
&& navigator.userAgent.toLowerCase().indexOf("opera") == -1),
|
||||
|
||||
/**
|
||||
* I hate navigator string based browser detection too, but when Opera alone
|
||||
* chokes on cookies containing double quotes...
|
||||
*/
|
||||
BROWSER_IS_OPERA:
|
||||
(navigator.userAgent.toLowerCase().indexOf("opera") != -1),
|
||||
|
||||
initialize: function(options)
|
||||
{
|
||||
this.options = Object.extend({
|
||||
shelfLife: 365,
|
||||
userData: false
|
||||
}, options || {});
|
||||
|
||||
this.cookieShelfLife = this.options.shelfLife;
|
||||
this.userDataForIE = this.options.userData;
|
||||
|
||||
// Internet Explorer has a cookie handling bug - if the *combined size*
|
||||
// of all cookies stored for a given domain is greater than 4096 bytes,
|
||||
// document.cookie will return an empty string. Until this is fixed, we
|
||||
// can fall back on IE's proprietary userData behaviour if necessary.
|
||||
if (this.BROWSER_IS_IE && this.userDataForIE)
|
||||
{
|
||||
this.IE_CACHE_NAME = "storage";
|
||||
if ($(this.IE_CACHE_NAME) == null)
|
||||
{
|
||||
var div = document.createElement("DIV");
|
||||
div.id = this.IE_CACHE_NAME;
|
||||
document.body.appendChild(div);
|
||||
}
|
||||
this.store = $(this.IE_CACHE_NAME);
|
||||
this.store.style.behavior = "url('#default#userData')";
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns the value of a cookie with the given name, or <code>null</code>
|
||||
* if no such cookie exists.
|
||||
*/
|
||||
getCookie: function(aCookieName)
|
||||
{
|
||||
var result = null;
|
||||
if (this.BROWSER_IS_IE && this.userDataForIE)
|
||||
{
|
||||
this.store.load(this.IE_CACHE_NAME);
|
||||
result = this.store.getAttribute(aCookieName);
|
||||
}
|
||||
else
|
||||
{
|
||||
for (var i = 0; i < document.cookie.split('; ').length; i++)
|
||||
{
|
||||
var crumb = document.cookie.split('; ')[i].split('=');
|
||||
if (crumb[0] == aCookieName && crumb[1] != null)
|
||||
{
|
||||
result = crumb[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.BROWSER_IS_OPERA && result != null)
|
||||
{
|
||||
result = result.replace(/%22/g, '"');
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
/**
|
||||
* Sets a cookie with the given name and value.
|
||||
*/
|
||||
setCookie: function(aCookieName, aCookieValue)
|
||||
{
|
||||
if (this.BROWSER_IS_IE && this.userDataForIE)
|
||||
{
|
||||
this.store.setAttribute(aCookieName, aCookieValue);
|
||||
this.store.save(this.IE_CACHE_NAME);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (this.BROWSER_IS_OPERA)
|
||||
{
|
||||
aCookieValue = aCookieValue.replace(/"/g, "%22");
|
||||
}
|
||||
var date = new Date();
|
||||
date.setTime(date.getTime() + (this.cookieShelfLife * 24*60*60*1000));
|
||||
var expires = '; expires=' + date.toGMTString();
|
||||
document.cookie = aCookieName + '=' + aCookieValue + expires + '; path=/';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Clears the cookie with the given name.
|
||||
*/
|
||||
clearCookie: function(aCookieName)
|
||||
{
|
||||
if (this.BROWSER_IS_IE && this.userDataForIE)
|
||||
{
|
||||
this.store.load(this.IE_CACHE_NAME);
|
||||
this.store.removeAttribute(aCookieName);
|
||||
this.store.save(this.IE_CACHE_NAME);
|
||||
}
|
||||
else
|
||||
{
|
||||
document.cookie =
|
||||
aCookieName + '=;expires=Thu, 01-Jan-1970 00:00:01 GMT; path=/';
|
||||
}
|
||||
todoItems = {
|
||||
// public
|
||||
ensureVisibleWithEffectAppear: function(elemId){
|
||||
$('#'+elemId).fadeIn(400);
|
||||
},
|
||||
expandNextActionListing: function(itemsElem, skipAnimation) {
|
||||
itemsElem = $(itemsElem);
|
||||
if(skipAnimation == true) {
|
||||
itemsElem.show();
|
||||
}
|
||||
else {
|
||||
itemsElem.show('blind', 400);
|
||||
}
|
||||
todoItems.showContainer(itemsElem.parentNode);
|
||||
},
|
||||
collapseNextActionListing: function(itemsElem, skipAnimation) {
|
||||
itemsElem = $(itemsElem);
|
||||
if(skipAnimation == true) {
|
||||
itemsElem.hide();
|
||||
}
|
||||
else {
|
||||
itemsElem.hide('blind', 400);
|
||||
}
|
||||
todoItems.hideContainer(itemsElem.parentNode);
|
||||
},
|
||||
ensureContainerHeight: function(itemsElem) {
|
||||
$(itemsElem).css({height: '', overflow: ''});
|
||||
},
|
||||
expandNextActionListingByContext: function(itemsElemId, skipAnimation){
|
||||
todoItems.expandNextActionListing($('#'+itemsElemId).get(), skipAnimation);
|
||||
},
|
||||
|
||||
// private
|
||||
buildCookieName: function(containerElem) {
|
||||
tracks_login = $.cookie('tracks_login');
|
||||
return 'tracks_'+tracks_login+'_context_' + containerElem.id + '_collapsed';
|
||||
},
|
||||
showContainer: function(containerElem) {
|
||||
imgSrc = $(containerElem).find('.container_toggle img').attr('src');
|
||||
$(containerElem).find('.container_toggle img').attr('src', imgSrc.replace('expand', 'collapse'));
|
||||
},
|
||||
hideContainer: function (containerElem) {
|
||||
imgSrc = $(containerElem).find('.container_toggle img').attr('src');
|
||||
$(containerElem).find('.container_toggle img').attr('src', imgSrc.replace('collapse', 'expand'));
|
||||
}
|
||||
}
|
||||
|
||||
function setup_container_toggles(){
|
||||
// bind handlers
|
||||
$('.container_toggle').click(function(evt){
|
||||
toggle_target = $(this.parentNode.parentNode).find('.toggle_target');
|
||||
if(toggle_target.is(':visible')){
|
||||
// hide it
|
||||
imgSrc = $(this).find('img').attr('src');
|
||||
$(this).find('img').attr('src', imgSrc.replace('collapse', 'expand'));
|
||||
$.cookie(todoItems.buildCookieName(this.parentNode.parentNode), true);
|
||||
} else {
|
||||
// show it
|
||||
imgSrc = $(this).find('img').attr('src');
|
||||
$(this).find('img').attr('src', imgSrc.replace('expand', 'collapse'));
|
||||
$.cookie(todoItems.buildCookieName(this.parentNode.parentNode), null);
|
||||
}
|
||||
toggle_target.toggle('blind');
|
||||
});
|
||||
// set to cookied state
|
||||
$('.container.context').each(function(){
|
||||
if($.cookie(todoItems.buildCookieName(this))){
|
||||
imgSrc = $(this).find('.container_toggle img').attr('src');
|
||||
$(this).find('.container_toggle img').attr('src', imgSrc.replace('collapse', 'expand'));
|
||||
$(this).find('.toggle_target').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function askIfNewContextProvided() {
|
||||
var givenContextName = $('#todo_context_name').val();
|
||||
if (givenContextName.length == 0) return true; // do nothing and depend on rails validation error
|
||||
for (var i = 0; i < contextNames.length; ++i) {
|
||||
if (contextNames[i] == givenContextName) return true;
|
||||
}
|
||||
return confirm('New context "' + givenContextName + '" will be also created. Are you sure?');
|
||||
}
|
||||
|
||||
function update_order(event, ui){
|
||||
container = $(ui.item).parent();
|
||||
row = $(ui.item).children('.sortable_row');
|
||||
|
||||
url = '';
|
||||
if(row.hasClass('context'))
|
||||
url = '/contexts/order';
|
||||
else if(row.hasClass('project'))
|
||||
url = '/projects/order';
|
||||
else {
|
||||
console.log("Bad sortable list");
|
||||
return;
|
||||
}
|
||||
$.post(url,
|
||||
container.sortable("serialize"),
|
||||
function(){row.effect('highlight', {}, 1000)},
|
||||
'script');
|
||||
}
|
||||
|
||||
/* Unobtrusive jQuery behavior */
|
||||
|
||||
function project_defaults(){
|
||||
if(defaultContexts[$(this).val()] !== undefined) {
|
||||
context_name = $(this).parents('form').find('input[name=context_name]');
|
||||
if(context_name.attr('edited') === undefined){
|
||||
context_name.val(defaultContexts[$(this).val()]);
|
||||
}
|
||||
}
|
||||
if(defaultTags[$(this).val()] !== undefined) {
|
||||
tag_list = $(this).parents('form').find('input[name=tag_list]');
|
||||
if(tag_list.attr('edited') === undefined){
|
||||
tag_list.val(defaultTags[$(this).val()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function enable_rich_interaction(){
|
||||
$('input.Date').datepicker({'dateFormat': dateFormat});
|
||||
/* Autocomplete */
|
||||
$('input[name=context_name]').autocomplete(contextNames, {matchContains: true});
|
||||
$('input[name=project[default_context_name]]').autocomplete(contextNames, {matchContains: true});
|
||||
$('input[name=project_name]').autocomplete(projectNames, {matchContains: true});
|
||||
$('input[name=tag_list]').autocomplete(tagNames, {multiple: true,multipleSeparator:',',matchContains:true});
|
||||
$('input[name=predecessor_list]').autocomplete('/todos/auto_complete_for_predecessor',
|
||||
{multiple: true,multipleSeparator:','});
|
||||
|
||||
/* have to bind on keypress because of limitataions of live() */
|
||||
$('input[name=project_name]').live('keypress', function(){
|
||||
$(this).bind('blur', project_defaults);
|
||||
});
|
||||
|
||||
$('input[name=context_name]').live('keypress', function(){
|
||||
$(this).attr('edited', 'true');
|
||||
});
|
||||
$('input[name=tag_list]').live('keypress', function(){
|
||||
$(this).attr('edited', 'true');
|
||||
});
|
||||
|
||||
/* Drag & Drop for successor/predecessor */
|
||||
function drop_todo(evt, ui) {
|
||||
dragged_todo = ui.draggable[0].id.split('_')[2];
|
||||
dropped_todo = this.id.split('_')[2];
|
||||
ui.draggable.hide();
|
||||
$(this).block({message: null});
|
||||
$.post('/todos/add_predecessor',
|
||||
{successor: dragged_todo, predecessor: dropped_todo},
|
||||
null, 'script');
|
||||
}
|
||||
|
||||
$('.item-show').draggable({handle: '.grip', revert: 'invalid'});
|
||||
$('.item-show').droppable({
|
||||
drop: drop_todo,
|
||||
hoverClass: 'hover'
|
||||
});
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
$('#search-form #search').focus();
|
||||
|
||||
/* Nifty corners */
|
||||
Nifty("div#recurring_new_container","normal");
|
||||
Nifty("div#context_new_container","normal");
|
||||
Nifty("div#feedlegend","normal");
|
||||
Nifty("div#feedicons-project","normal");
|
||||
Nifty("div#feedicons-context","normal");
|
||||
Nifty("div#todo_new_action_container","normal");
|
||||
|
||||
/* Moved from standard.html.erb layout */
|
||||
$('ul.sf-menu').superfish({
|
||||
delay: 250,
|
||||
animation: {opacity:'show',height:'show'},
|
||||
autoArrows: false,
|
||||
dropShadows: false,
|
||||
speed: 'fast'
|
||||
});
|
||||
|
||||
$('ul.sf-item-menu').superfish({ /* context menu */
|
||||
delay: 100,
|
||||
animation: {opacity:'show',height:'show'},
|
||||
autoArrows: false,
|
||||
dropShadows: false,
|
||||
speed: 'fast',
|
||||
onBeforeShow: function() { /* highlight todo */
|
||||
$(this.parent().parent().parent()).addClass("sf-item-selected");
|
||||
},
|
||||
onHide: function() { /* remove hightlight from todo */
|
||||
$(this.parent().parent().parent()).removeClass("sf-item-selected");
|
||||
}
|
||||
});
|
||||
|
||||
/* for toggle notes link in mininav */
|
||||
$("#toggle-notes-nav").click(function () { $(".todo_notes").toggle(); });
|
||||
|
||||
/* show the notes of a todo */
|
||||
$(".show_notes").live('click', function () {
|
||||
$(this).next().toggle("fast"); return false;
|
||||
});
|
||||
|
||||
$(".show_successors").live('click', function () {
|
||||
$(this).next().toggle("fast"); return false;
|
||||
});
|
||||
|
||||
/* fade flashes and alerts in automatically */
|
||||
$(".alert").fadeOut(8000);
|
||||
|
||||
/* set behavior for star icon */
|
||||
$(".item-container a.star_item").live('click', function (ev){
|
||||
$.post(this.href, {_method: 'put'}, null, 'script');
|
||||
return false;
|
||||
});
|
||||
|
||||
/* set behavior for toggle checkboxes */
|
||||
$(".item-container input.item-checkbox").live('click', function(ev){
|
||||
params = {_method: 'put'};
|
||||
if(typeof(TAG_NAME) !== 'undefined')
|
||||
params._tag_name = TAG_NAME;
|
||||
$.post(this.value, params, null, 'script');
|
||||
});
|
||||
|
||||
setup_container_toggles();
|
||||
|
||||
$('#toggle_action_new').click(function(){
|
||||
TracksForm.toggle('toggle_action_new', 'todo_new_action', 'todo-form-new-action',
|
||||
'« Hide form', 'Hide next action form',
|
||||
'Add a next action »', 'Add a next action');
|
||||
});
|
||||
|
||||
$('.edit-form a.negative').live('click', function(){
|
||||
$(this).parents('.container').find('.item-show').show();
|
||||
$(this).parents('.container').find('.project').show();
|
||||
$(this).parents('.edit-form').hide();
|
||||
});
|
||||
|
||||
/* add behavior to clear the date both buttons for show_from and due */
|
||||
$(".date_clear").live('click', function() {
|
||||
$(this).prev().val('');
|
||||
});
|
||||
|
||||
/* recurring todo behavior */
|
||||
|
||||
/* behavior for delete icon */
|
||||
$('.item-container a.delete_icon').live('click', function(evt){
|
||||
evt.preventDefault();
|
||||
params = {};
|
||||
if(typeof(TAG_NAME) !== 'undefined'){
|
||||
params._tag_name = TAG_NAME;
|
||||
}
|
||||
if(confirm("Are you sure that you want to "+this.title+"?")){
|
||||
itemContainer = $(this).parents(".item-container");
|
||||
itemContainer.block({message: null});
|
||||
params._method = 'delete';
|
||||
$.post(this.href, params, function(){
|
||||
itemContainer.unblock();
|
||||
}, 'script');
|
||||
}
|
||||
});
|
||||
|
||||
/* behavior for edit icon */
|
||||
$('.item-container a.edit_icon').live('click', function(evt){
|
||||
evt.preventDefault();
|
||||
params = {};
|
||||
if(typeof(TAG_NAME) !== 'undefined'){
|
||||
params._tag_name = TAG_NAME;
|
||||
}
|
||||
itemContainer = $(this).parents(".item-container");
|
||||
$(this).effect('pulsate', {times: 1}, 800);
|
||||
$.get(this.href, params, function(){
|
||||
}, 'script');
|
||||
});
|
||||
|
||||
$("#recurring_todo_new_action_cancel").click(function(){
|
||||
$('#recurring-todo-form-new-action').clearForm();
|
||||
$('#recurring-todo-form-new-action input:text:first').focus();
|
||||
TracksForm.hide_all_recurring();
|
||||
$('#recurring_daily').show();
|
||||
TracksForm.toggle_overlay();
|
||||
});
|
||||
|
||||
$("#recurring_todo_edit_action_cancel").live('click', function(){
|
||||
$('#recurring-todo-form-edit-action').clearForm();
|
||||
$('#recurring-todo-form-edit-action input:text:first').focus();
|
||||
TracksForm.hide_all_recurring();
|
||||
$('#recurring_daily').show();
|
||||
TracksForm.toggle_overlay();
|
||||
});
|
||||
$("#recurring_edit_period input").live('click', function(){
|
||||
$.each(['daily', 'weekly', 'monthly', 'yearly'], function(){
|
||||
$('#recurring_edit_'+this).hide();
|
||||
});
|
||||
$('#recurring_edit_'+this.id.split('_')[5]).show();
|
||||
});
|
||||
|
||||
$("#recurring_period input").live('click', function(){
|
||||
$.each(['daily', 'weekly', 'monthly', 'yearly', 'target'], function(){
|
||||
$('#recurring_'+this).hide();
|
||||
});
|
||||
$('#recurring_'+this.id.split('_')[4]).show();
|
||||
});
|
||||
|
||||
$('div.context span#context_name').editable(function(value, settings){
|
||||
context_id = $(this).parents('.container.context').get(0).id.split('c')[1];
|
||||
highlight = function(){
|
||||
$('div.context span#context_name').effect('highlight', {}, 500);
|
||||
};
|
||||
$.post('/contexts/update/'+context_id, {'context[name]': value}, highlight);
|
||||
return(value);
|
||||
}, {style: 'padding:0px', submit: "OK"});
|
||||
|
||||
/* Projects behavior */
|
||||
|
||||
save_project_name = function(value, settings){
|
||||
project_id = $(this).parents('.container').children('div').get(0).id.split('_')[2];
|
||||
highlight = function(){
|
||||
$('h2#project_name').effect('highlight', {}, 500);
|
||||
};
|
||||
$.post('/projects/update/'+project_id, {'project[name]': value, 'update_project_name': 'true'}, highlight, 'script');
|
||||
return(value);
|
||||
};
|
||||
|
||||
$('h2#project_name').editable(save_project_name, {style: 'padding:0px', submit: "OK"});
|
||||
|
||||
$('.alphabetize_link').click(function(evt){
|
||||
evt.preventDefault();
|
||||
if(confirm('Are you sure that you want to sort these projects alphabetically? This will replace the existing sort order.')){
|
||||
alphaSort = $(this).parents('.alpha_sort');
|
||||
alphaSort.block({message:null});
|
||||
$.post(this.href, {}, function(){alphaSort.unblock()}, 'script');
|
||||
}
|
||||
});
|
||||
|
||||
$('.actionize_link').click(function(evt){
|
||||
evt.preventDefault();
|
||||
if(confirm('Are you sure that you want to sort these projects by the number of tasks? This will replace the existing sort order.')){
|
||||
taskSort = $(this).parents('.tasks_sort');
|
||||
taskSort.block({message:null});
|
||||
$.post(this.href, {}, function(){taskSort.unblock()}, 'script');
|
||||
}
|
||||
});
|
||||
|
||||
$('a.delete_project_button').live('click', function(evt){
|
||||
evt.preventDefault();
|
||||
if(confirm("Are you sure that you want to "+this.title+"?")){
|
||||
$(this).parents('.project').block({message: null});
|
||||
params = {_method: 'delete'};
|
||||
$.post(this.href, params, null, 'script');
|
||||
}
|
||||
});
|
||||
|
||||
$('#toggle_project_new').click(function(evt){
|
||||
TracksForm.toggle('toggle_project_new', 'project_new', 'project-form',
|
||||
'« Hide form', 'Hide new project form',
|
||||
'Create a new project »', 'Add a project');
|
||||
});
|
||||
|
||||
$(".project-list .edit-form a.negative").live('click', function(evt){
|
||||
evt.preventDefault();
|
||||
$(this).parents('.list').find('.project').show();
|
||||
$(this).parents('.edit-form').hide();
|
||||
$(this).parents('.edit-form').find('form').clearForm();
|
||||
});
|
||||
|
||||
$(".add_note_link a").live('click', function(){
|
||||
$('#new-note').show();
|
||||
$('#new-note form').clearForm();
|
||||
$('#new-note form input:text:first').focus();
|
||||
});
|
||||
|
||||
$("#list-active-projects").sortable({handle: '.handle', update: update_order});
|
||||
$("#list-hidden-projects").sortable({handle: '.handle', update: update_order});
|
||||
$("#list-completed-projects").sortable({handle: '.handle', update: update_order});
|
||||
|
||||
/* Contexts behavior */
|
||||
$('#toggle_context_new').click(function(evt){
|
||||
TracksForm.toggle('toggle_context_new', 'context_new', 'context-form',
|
||||
'« Hide form', 'Hide new context form',
|
||||
'Create a new context »', 'Add a context');
|
||||
});
|
||||
|
||||
$("#list-contexts-active").sortable({handle: '.handle', update: update_order});
|
||||
$("#list-contexts-hidden").sortable({handle: '.handle', update: update_order});
|
||||
|
||||
/* Gets called from some AJAX callbacks, too */
|
||||
enable_rich_interaction();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,127 +0,0 @@
|
|||
// ** I18N
|
||||
|
||||
// Calendar EN language
|
||||
// Author: Mihai Bazon, <mihai_bazon@yahoo.com>
|
||||
// Encoding: any
|
||||
// Distributed under the same terms as the calendar itself.
|
||||
|
||||
// For translators: please use UTF-8 if possible. We strongly believe that
|
||||
// Unicode is the answer to a real internationalized world. Also please
|
||||
// include your contact information in the header, as can be seen above.
|
||||
|
||||
// full day names
|
||||
Calendar._DN = new Array
|
||||
("Sunday",
|
||||
"Monday",
|
||||
"Tuesday",
|
||||
"Wednesday",
|
||||
"Thursday",
|
||||
"Friday",
|
||||
"Saturday",
|
||||
"Sunday");
|
||||
|
||||
// Please note that the following array of short day names (and the same goes
|
||||
// for short month names, _SMN) isn't absolutely necessary. We give it here
|
||||
// for exemplification on how one can customize the short day names, but if
|
||||
// they are simply the first N letters of the full name you can simply say:
|
||||
//
|
||||
// Calendar._SDN_len = N; // short day name length
|
||||
// Calendar._SMN_len = N; // short month name length
|
||||
//
|
||||
// If N = 3 then this is not needed either since we assume a value of 3 if not
|
||||
// present, to be compatible with translation files that were written before
|
||||
// this feature.
|
||||
|
||||
// short day names
|
||||
Calendar._SDN = new Array
|
||||
("Sun",
|
||||
"Mon",
|
||||
"Tue",
|
||||
"Wed",
|
||||
"Thu",
|
||||
"Fri",
|
||||
"Sat",
|
||||
"Sun");
|
||||
|
||||
// First day of the week. "0" means display Sunday first, "1" means display
|
||||
// Monday first, etc.
|
||||
Calendar._FD = 0;
|
||||
|
||||
// full month names
|
||||
Calendar._MN = new Array
|
||||
("January",
|
||||
"February",
|
||||
"March",
|
||||
"April",
|
||||
"May",
|
||||
"June",
|
||||
"July",
|
||||
"August",
|
||||
"September",
|
||||
"October",
|
||||
"November",
|
||||
"December");
|
||||
|
||||
// short month names
|
||||
Calendar._SMN = new Array
|
||||
("Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec");
|
||||
|
||||
// tooltips
|
||||
Calendar._TT = {};
|
||||
Calendar._TT["INFO"] = "About the calendar";
|
||||
|
||||
Calendar._TT["ABOUT"] =
|
||||
"DHTML Date/Time Selector\n" +
|
||||
"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
|
||||
"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
|
||||
"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
|
||||
"\n\n" +
|
||||
"Date selection:\n" +
|
||||
"- Use the \xab, \xbb buttons to select year\n" +
|
||||
"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
|
||||
"- Hold mouse button on any of the above buttons for faster selection.";
|
||||
Calendar._TT["ABOUT_TIME"] = "\n\n" +
|
||||
"Time selection:\n" +
|
||||
"- Click on any of the time parts to increase it\n" +
|
||||
"- or Shift-click to decrease it\n" +
|
||||
"- or click and drag for faster selection.";
|
||||
|
||||
Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
|
||||
Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
|
||||
Calendar._TT["GO_TODAY"] = "Go Today";
|
||||
Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
|
||||
Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
|
||||
Calendar._TT["SEL_DATE"] = "Select date";
|
||||
Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
|
||||
Calendar._TT["PART_TODAY"] = " (today)";
|
||||
|
||||
// the following is to inform that "%s" is to be the first day of week
|
||||
// %s will be replaced with the day name.
|
||||
Calendar._TT["DAY_FIRST"] = "Display %s first";
|
||||
|
||||
// This may be locale-dependent. It specifies the week-end days, as an array
|
||||
// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
|
||||
// means Monday, etc.
|
||||
Calendar._TT["WEEKEND"] = "0,6";
|
||||
|
||||
Calendar._TT["CLOSE"] = "Close";
|
||||
Calendar._TT["TODAY"] = "Today";
|
||||
Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
|
||||
|
||||
// date formats
|
||||
Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
|
||||
Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
|
||||
|
||||
Calendar._TT["WK"] = "wk";
|
||||
Calendar._TT["TIME"] = "Time:";
|
||||
|
|
@ -1,333 +0,0 @@
|
|||
/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
|
||||
* ---------------------------------------------------------------------------
|
||||
*
|
||||
* The DHTML Calendar
|
||||
*
|
||||
* Details and latest version at:
|
||||
* http://dynarch.com/mishoo/calendar.epl
|
||||
*
|
||||
* This script is distributed under the GNU Lesser General Public License.
|
||||
* Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
|
||||
*
|
||||
* This file defines helper functions for setting up the calendar. They are
|
||||
* intended to help non-programmers get a working calendar on their site
|
||||
* quickly. This script should not be seen as part of the calendar. It just
|
||||
* shows you what one can do with the calendar, while in the same time
|
||||
* providing a quick and simple method for setting it up. If you need
|
||||
* exhaustive customization of the calendar creation process feel free to
|
||||
* modify this code to suit your needs (this is recommended and much better
|
||||
* than modifying calendar.js itself).
|
||||
*/
|
||||
|
||||
// $Id: calendar-setup.js,v 1.25 2005/03/07 09:51:33 mishoo Exp $
|
||||
|
||||
/**
|
||||
* This function "patches" an input field (or other element) to use a calendar
|
||||
* widget for date selection.
|
||||
*
|
||||
* The "params" is a single object that can have the following properties:
|
||||
*
|
||||
* prop. name | description
|
||||
* -------------------------------------------------------------------------------------------------
|
||||
* inputField | the ID of an input field to store the date
|
||||
* displayArea | the ID of a DIV or other element to show the date
|
||||
* button | ID of a button or other element that will trigger the calendar
|
||||
* eventName | event that will trigger the calendar, without the "on" prefix (default: "click")
|
||||
* ifFormat | date format that will be stored in the input field
|
||||
* daFormat | the date format that will be used to display the date in displayArea
|
||||
* singleClick | (true/false) wether the calendar is in single click mode or not (default: true)
|
||||
* firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc.
|
||||
* align | alignment (default: "Br"); if you don't know what's this see the calendar documentation
|
||||
* range | array with 2 elements. Default: [1900, 2999] -- the range of years available
|
||||
* weekNumbers | (true/false) if it's true (default) the calendar will display week numbers
|
||||
* flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID
|
||||
* flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar)
|
||||
* disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar
|
||||
* onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay)
|
||||
* onClose | function that gets called when the calendar is closed. [default]
|
||||
* onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar.
|
||||
* date | the date that the calendar will be initially displayed to
|
||||
* showsTime | default: false; if true the calendar will include a time selector
|
||||
* timeFormat | the time format; can be "12" or "24", default is "12"
|
||||
* electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close
|
||||
* step | configures the step of the years in drop-down boxes; default: 2
|
||||
* position | configures the calendar absolute position; default: null
|
||||
* cache | if "true" (but default: "false") it will reuse the same calendar object, where possible
|
||||
* showOthers | if "true" (but default: "false") it will show days from other months too
|
||||
*
|
||||
* None of them is required, they all have default values. However, if you
|
||||
* pass none of "inputField", "displayArea" or "button" you'll get a warning
|
||||
* saying "nothing to setup".
|
||||
*/
|
||||
Calendar.setup = function (params) {
|
||||
function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
|
||||
|
||||
param_default("inputField", null);
|
||||
param_default("displayArea", null);
|
||||
param_default("button", null);
|
||||
param_default("eventName", "click");
|
||||
param_default("ifFormat", "%Y/%m/%d");
|
||||
param_default("daFormat", "%Y/%m/%d");
|
||||
param_default("singleClick", true);
|
||||
param_default("disableFunc", null);
|
||||
param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined
|
||||
param_default("dateText", null);
|
||||
param_default("firstDay", null);
|
||||
param_default("align", "Br");
|
||||
param_default("range", [1900, 2999]);
|
||||
param_default("weekNumbers", true);
|
||||
param_default("flat", null);
|
||||
param_default("flatCallback", null);
|
||||
param_default("onSelect", null);
|
||||
param_default("onClose", null);
|
||||
param_default("onUpdate", null);
|
||||
param_default("date", null);
|
||||
param_default("showsTime", false);
|
||||
param_default("timeFormat", "24");
|
||||
param_default("electric", true);
|
||||
param_default("step", 2);
|
||||
param_default("position", null);
|
||||
param_default("cache", false);
|
||||
param_default("showOthers", false);
|
||||
param_default("multiple", null);
|
||||
|
||||
var tmp = ["inputField", "displayArea", "button"];
|
||||
for (var i in tmp) {
|
||||
if (typeof params[tmp[i]] == "string") {
|
||||
params[tmp[i]] = document.getElementById(params[tmp[i]]);
|
||||
}
|
||||
}
|
||||
if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) {
|
||||
alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");
|
||||
return false;
|
||||
}
|
||||
|
||||
function onSelect(cal) {
|
||||
var p = cal.params;
|
||||
var update = (cal.dateClicked || p.electric);
|
||||
if (update && p.inputField) {
|
||||
p.inputField.value = cal.date.print(p.ifFormat);
|
||||
if (typeof p.inputField.onchange == "function")
|
||||
p.inputField.onchange();
|
||||
}
|
||||
if (update && p.displayArea)
|
||||
p.displayArea.innerHTML = cal.date.print(p.daFormat);
|
||||
if (update && typeof p.onUpdate == "function")
|
||||
p.onUpdate(cal);
|
||||
if (update && p.flat) {
|
||||
if (typeof p.flatCallback == "function")
|
||||
p.flatCallback(cal);
|
||||
}
|
||||
if (update && p.singleClick && cal.dateClicked)
|
||||
cal.callCloseHandler();
|
||||
};
|
||||
|
||||
if (params.flat != null) {
|
||||
if (typeof params.flat == "string")
|
||||
params.flat = document.getElementById(params.flat);
|
||||
if (!params.flat) {
|
||||
alert("Calendar.setup:\n Flat specified but can't find parent.");
|
||||
return false;
|
||||
}
|
||||
var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect);
|
||||
cal.showsOtherMonths = params.showOthers;
|
||||
cal.showsTime = params.showsTime;
|
||||
cal.time24 = (params.timeFormat == "24");
|
||||
cal.params = params;
|
||||
cal.weekNumbers = params.weekNumbers;
|
||||
cal.setRange(params.range[0], params.range[1]);
|
||||
cal.setDateStatusHandler(params.dateStatusFunc);
|
||||
cal.getDateText = params.dateText;
|
||||
if (params.ifFormat) {
|
||||
cal.setDateFormat(params.ifFormat);
|
||||
}
|
||||
if (params.inputField && typeof params.inputField.value == "string") {
|
||||
cal.parseDate(params.inputField.value);
|
||||
}
|
||||
cal.create(params.flat);
|
||||
cal.show();
|
||||
return false;
|
||||
}
|
||||
|
||||
var triggerEl = params.button || params.displayArea || params.inputField;
|
||||
triggerEl["on" + params.eventName] = function() {
|
||||
var dateEl = params.inputField || params.displayArea;
|
||||
var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
|
||||
var mustCreate = false;
|
||||
var cal = window.calendar;
|
||||
if (dateEl)
|
||||
params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
|
||||
if (!(cal && params.cache)) {
|
||||
window.calendar = cal = new Calendar(params.firstDay,
|
||||
params.date,
|
||||
params.onSelect || onSelect,
|
||||
params.onClose || function(cal) { cal.hide(); });
|
||||
cal.showsTime = params.showsTime;
|
||||
cal.time24 = (params.timeFormat == "24");
|
||||
cal.weekNumbers = params.weekNumbers;
|
||||
mustCreate = true;
|
||||
} else {
|
||||
if (params.date)
|
||||
cal.setDate(params.date);
|
||||
cal.hide();
|
||||
}
|
||||
if (params.multiple) {
|
||||
cal.multiple = {};
|
||||
for (var i = params.multiple.length; --i >= 0;) {
|
||||
var d = params.multiple[i];
|
||||
var ds = d.print("%Y%m%d");
|
||||
cal.multiple[ds] = d;
|
||||
}
|
||||
}
|
||||
cal.showsOtherMonths = params.showOthers;
|
||||
cal.yearStep = params.step;
|
||||
cal.setRange(params.range[0], params.range[1]);
|
||||
cal.params = params;
|
||||
cal.setDateStatusHandler(params.dateStatusFunc);
|
||||
cal.getDateText = params.dateText;
|
||||
cal.setDateFormat(dateFmt);
|
||||
if (mustCreate)
|
||||
cal.create();
|
||||
cal.refresh();
|
||||
if (!params.position)
|
||||
cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
|
||||
else
|
||||
cal.showAt(params.position[0], params.position[1]);
|
||||
return false;
|
||||
};
|
||||
|
||||
if (params.inputField) {
|
||||
new DateDueKeyboardShortcutSupport(params.inputField, params.ifFormat, cal);
|
||||
}
|
||||
return cal;
|
||||
};
|
||||
|
||||
/* Adds keyboard shortcuts to the passed in date field:
|
||||
*
|
||||
* 't' input today's date
|
||||
* '+' or '=' increment the date in the field by one day
|
||||
* '-' decrement the date in the field by one day
|
||||
*
|
||||
* If the calendar is visible, the shortcuts play nicely with it. If not,
|
||||
* they still work properly. Pressing '+' when no date is entered in the
|
||||
* field will set the date to tomorrow, and likewise '-' with no date
|
||||
* entered will set the date to yesterday.
|
||||
*/
|
||||
DateDueKeyboardShortcutSupport = Class.create();
|
||||
DateDueKeyboardShortcutSupport.prototype = {
|
||||
initialize: function(element, dateFormat) {
|
||||
this.element = $(element);
|
||||
this.dateFormat = dateFormat || "%Y/%m/%d";
|
||||
Event.observe(this.element,'keypress',this.onkeypress.bindAsEventListener(this));
|
||||
title = this.element.getAttributeNode("title");
|
||||
tooltip = 'Shortcuts: (t) today; (-) or (<) previous day; (+) or (>) next day; ([) previous week; (]) next week; ({) previous month; (}) next month; Click to show calendar';
|
||||
if (title && title.value)
|
||||
{
|
||||
this.element.setAttribute("title", title.value + ' (' + tooltip + ')');
|
||||
}
|
||||
else
|
||||
{
|
||||
this.element.setAttribute("title", tooltip);
|
||||
}
|
||||
},
|
||||
onkeypress: function(event) {
|
||||
handled = true;
|
||||
switch (this.getCharFromKeyPressEvent(event)) {
|
||||
case "t":
|
||||
this.setTextBoxToTodaysDate();
|
||||
break;
|
||||
case "+":
|
||||
case ">":
|
||||
case "=":
|
||||
this.setTextBoxToNextDay();
|
||||
break;
|
||||
case "-":
|
||||
case "<":
|
||||
this.setTextBoxToPreviousDay();
|
||||
break;
|
||||
case "[":
|
||||
this.setTextBoxToPreviousWeek();
|
||||
break;
|
||||
case "]":
|
||||
this.setTextBoxToNextWeek();
|
||||
break;
|
||||
case "{":
|
||||
this.setTextBoxToPreviousMonth();
|
||||
break;
|
||||
case "}":
|
||||
this.setTextBoxToNextMonth();
|
||||
break;
|
||||
default:
|
||||
handled = false;
|
||||
break;
|
||||
}
|
||||
if (handled) {
|
||||
this.cancel(event);
|
||||
}
|
||||
},
|
||||
|
||||
setTextBoxToChronicDate : function() {
|
||||
today = new Date();
|
||||
this.setDate(today);
|
||||
},
|
||||
|
||||
setTextBoxToTodaysDate : function() {
|
||||
today = new Date();
|
||||
this.setDate(today);
|
||||
},
|
||||
|
||||
setTextBoxToNextDay : function() {
|
||||
this.addDaysToTextBoxDate(1);
|
||||
},
|
||||
|
||||
setTextBoxToPreviousDay : function() {
|
||||
this.addDaysToTextBoxDate(-1);
|
||||
},
|
||||
|
||||
setTextBoxToNextWeek : function() {
|
||||
this.addDaysToTextBoxDate(7);
|
||||
},
|
||||
|
||||
setTextBoxToPreviousWeek : function() {
|
||||
this.addDaysToTextBoxDate(-7);
|
||||
},
|
||||
|
||||
addDaysToTextBoxDate : function(numDays) {
|
||||
date = Date.parseDate(this.element.value, this.dateFormat);
|
||||
date.setDate(date.getDate() + numDays);
|
||||
this.setDate(date);
|
||||
},
|
||||
|
||||
setTextBoxToNextMonth : function() {
|
||||
date = Date.parseDate(this.element.value, this.dateFormat);
|
||||
date.setMonth(date.getMonth() + 1);
|
||||
this.setDate(date);
|
||||
},
|
||||
|
||||
setTextBoxToPreviousMonth : function() {
|
||||
date = Date.parseDate(this.element.value, this.dateFormat);
|
||||
date.setMonth(date.getMonth() - 1);
|
||||
this.setDate(date);
|
||||
},
|
||||
|
||||
setDate : function(date) {
|
||||
this.element.value = date.print(this.dateFormat);
|
||||
if (window.calendar) {
|
||||
window.calendar.setDate(date);
|
||||
}
|
||||
},
|
||||
|
||||
cancel : function(event) {
|
||||
if (event.preventDefault) {
|
||||
event.preventDefault();
|
||||
}
|
||||
event.returnValue = false;
|
||||
},
|
||||
|
||||
getCharFromKeyPressEvent : function(event) {
|
||||
var charCode = (event.charCode) ? event.charCode :
|
||||
((event.keyCode) ? event.keyCode :
|
||||
((event.which) ? event.which : 0));
|
||||
return String.fromCharCode(charCode);
|
||||
}
|
||||
};
|
||||
File diff suppressed because it is too large
Load diff
963
public/javascripts/controls.js
vendored
963
public/javascripts/controls.js
vendored
|
|
@ -1,963 +0,0 @@
|
|||
// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
// Autocompleter.Base handles all the autocompletion functionality
|
||||
// that's independent of the data source for autocompletion. This
|
||||
// includes drawing the autocompletion menu, observing keyboard
|
||||
// and mouse events, and similar.
|
||||
//
|
||||
// Specific autocompleters need to provide, at the very least,
|
||||
// a getUpdatedChoices function that will be invoked every time
|
||||
// the text inside the monitored textbox changes. This method
|
||||
// should get the text for which to provide autocompletion by
|
||||
// invoking this.getToken(), NOT by directly accessing
|
||||
// this.element.value. This is to allow incremental tokenized
|
||||
// autocompletion. Specific auto-completion logic (AJAX, etc)
|
||||
// belongs in getUpdatedChoices.
|
||||
//
|
||||
// Tokenized incremental autocompletion is enabled automatically
|
||||
// when an autocompleter is instantiated with the 'tokens' option
|
||||
// in the options parameter, e.g.:
|
||||
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
|
||||
// will incrementally autocomplete with a comma as the token.
|
||||
// Additionally, ',' in the above example can be replaced with
|
||||
// a token array, e.g. { tokens: [',', '\n'] } which
|
||||
// enables autocompletion on multiple tokens. This is most
|
||||
// useful when one of the tokens is \n (a newline), as it
|
||||
// allows smart autocompletion after linebreaks.
|
||||
|
||||
if(typeof Effect == 'undefined')
|
||||
throw("controls.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Autocompleter = { };
|
||||
Autocompleter.Base = Class.create({
|
||||
baseInitialize: function(element, update, options) {
|
||||
element = $(element);
|
||||
this.element = element;
|
||||
this.update = $(update);
|
||||
this.hasFocus = false;
|
||||
this.changed = false;
|
||||
this.active = false;
|
||||
this.index = 0;
|
||||
this.entryCount = 0;
|
||||
this.oldElementValue = this.element.value;
|
||||
|
||||
if(this.setOptions)
|
||||
this.setOptions(options);
|
||||
else
|
||||
this.options = options || { };
|
||||
|
||||
this.options.paramName = this.options.paramName || this.element.name;
|
||||
this.options.tokens = this.options.tokens || [];
|
||||
this.options.frequency = this.options.frequency || 0.4;
|
||||
this.options.minChars = this.options.minChars || 1;
|
||||
this.options.onShow = this.options.onShow ||
|
||||
function(element, update){
|
||||
if(!update.style.position || update.style.position=='absolute') {
|
||||
update.style.position = 'absolute';
|
||||
Position.clone(element, update, {
|
||||
setHeight: false,
|
||||
offsetTop: element.offsetHeight
|
||||
});
|
||||
}
|
||||
Effect.Appear(update,{duration:0.15});
|
||||
};
|
||||
this.options.onHide = this.options.onHide ||
|
||||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
|
||||
|
||||
if(typeof(this.options.tokens) == 'string')
|
||||
this.options.tokens = new Array(this.options.tokens);
|
||||
// Force carriage returns as token delimiters anyway
|
||||
if (!this.options.tokens.include('\n'))
|
||||
this.options.tokens.push('\n');
|
||||
|
||||
this.observer = null;
|
||||
|
||||
this.element.setAttribute('autocomplete','off');
|
||||
|
||||
Element.hide(this.update);
|
||||
|
||||
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
|
||||
Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
show: function() {
|
||||
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
|
||||
if(!this.iefix &&
|
||||
(Prototype.Browser.IE) &&
|
||||
(Element.getStyle(this.update, 'position')=='absolute')) {
|
||||
new Insertion.After(this.update,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
this.iefix = $(this.update.id+'_iefix');
|
||||
}
|
||||
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
|
||||
},
|
||||
|
||||
fixIEOverlapping: function() {
|
||||
Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
|
||||
this.iefix.style.zIndex = 1;
|
||||
this.update.style.zIndex = 2;
|
||||
Element.show(this.iefix);
|
||||
},
|
||||
|
||||
hide: function() {
|
||||
this.stopIndicator();
|
||||
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
|
||||
if(this.iefix) Element.hide(this.iefix);
|
||||
},
|
||||
|
||||
startIndicator: function() {
|
||||
if(this.options.indicator) Element.show(this.options.indicator);
|
||||
},
|
||||
|
||||
stopIndicator: function() {
|
||||
if(this.options.indicator) Element.hide(this.options.indicator);
|
||||
},
|
||||
|
||||
onKeyPress: function(event) {
|
||||
if(this.active)
|
||||
switch(event.keyCode) {
|
||||
case Event.KEY_TAB:
|
||||
case Event.KEY_RETURN:
|
||||
this.selectEntry();
|
||||
Event.stop(event);
|
||||
case Event.KEY_ESC:
|
||||
this.hide();
|
||||
this.active = false;
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_LEFT:
|
||||
case Event.KEY_RIGHT:
|
||||
return;
|
||||
case Event.KEY_UP:
|
||||
this.markPrevious();
|
||||
this.render();
|
||||
Event.stop(event);
|
||||
return;
|
||||
case Event.KEY_DOWN:
|
||||
this.markNext();
|
||||
this.render();
|
||||
Event.stop(event);
|
||||
return;
|
||||
}
|
||||
else
|
||||
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
|
||||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
|
||||
|
||||
this.changed = true;
|
||||
this.hasFocus = true;
|
||||
|
||||
if(this.observer) clearTimeout(this.observer);
|
||||
this.observer =
|
||||
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
|
||||
},
|
||||
|
||||
activate: function() {
|
||||
this.changed = false;
|
||||
this.hasFocus = true;
|
||||
this.getUpdatedChoices();
|
||||
},
|
||||
|
||||
onHover: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
if(this.index != element.autocompleteIndex)
|
||||
{
|
||||
this.index = element.autocompleteIndex;
|
||||
this.render();
|
||||
}
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
onClick: function(event) {
|
||||
var element = Event.findElement(event, 'LI');
|
||||
this.index = element.autocompleteIndex;
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
},
|
||||
|
||||
onBlur: function(event) {
|
||||
// needed to make click events working
|
||||
setTimeout(this.hide.bind(this), 250);
|
||||
this.hasFocus = false;
|
||||
this.active = false;
|
||||
},
|
||||
|
||||
render: function() {
|
||||
if(this.entryCount > 0) {
|
||||
for (var i = 0; i < this.entryCount; i++)
|
||||
this.index==i ?
|
||||
Element.addClassName(this.getEntry(i),"selected") :
|
||||
Element.removeClassName(this.getEntry(i),"selected");
|
||||
if(this.hasFocus) {
|
||||
this.show();
|
||||
this.active = true;
|
||||
}
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
},
|
||||
|
||||
markPrevious: function() {
|
||||
if(this.index > 0) this.index--;
|
||||
else this.index = this.entryCount-1;
|
||||
this.getEntry(this.index).scrollIntoView(true);
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++;
|
||||
else this.index = 0;
|
||||
this.getEntry(this.index).scrollIntoView(false);
|
||||
},
|
||||
|
||||
getEntry: function(index) {
|
||||
return this.update.firstChild.childNodes[index];
|
||||
},
|
||||
|
||||
getCurrentEntry: function() {
|
||||
return this.getEntry(this.index);
|
||||
},
|
||||
|
||||
selectEntry: function() {
|
||||
this.active = false;
|
||||
this.updateElement(this.getCurrentEntry());
|
||||
},
|
||||
|
||||
updateElement: function(selectedElement) {
|
||||
if (this.options.updateElement) {
|
||||
this.options.updateElement(selectedElement);
|
||||
return;
|
||||
}
|
||||
var value = '';
|
||||
if (this.options.select) {
|
||||
var nodes = $(selectedElement).select('.' + this.options.select) || [];
|
||||
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
|
||||
} else
|
||||
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
|
||||
|
||||
var bounds = this.getTokenBounds();
|
||||
if (bounds[0] != -1) {
|
||||
var newValue = this.element.value.substr(0, bounds[0]);
|
||||
var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
|
||||
if (whitespace)
|
||||
newValue += whitespace[0];
|
||||
this.element.value = newValue + value + this.element.value.substr(bounds[1]);
|
||||
} else {
|
||||
this.element.value = value;
|
||||
}
|
||||
this.oldElementValue = this.element.value;
|
||||
this.element.focus();
|
||||
|
||||
if (this.options.afterUpdateElement)
|
||||
this.options.afterUpdateElement(this.element, selectedElement);
|
||||
},
|
||||
|
||||
updateChoices: function(choices) {
|
||||
if(!this.changed && this.hasFocus) {
|
||||
this.update.innerHTML = choices;
|
||||
Element.cleanWhitespace(this.update);
|
||||
Element.cleanWhitespace(this.update.down());
|
||||
|
||||
if(this.update.firstChild && this.update.down().childNodes) {
|
||||
this.entryCount =
|
||||
this.update.down().childNodes.length;
|
||||
for (var i = 0; i < this.entryCount; i++) {
|
||||
var entry = this.getEntry(i);
|
||||
entry.autocompleteIndex = i;
|
||||
this.addObservers(entry);
|
||||
}
|
||||
} else {
|
||||
this.entryCount = 0;
|
||||
}
|
||||
|
||||
this.stopIndicator();
|
||||
this.index = 0;
|
||||
|
||||
if(this.entryCount==1 && this.options.autoSelect) {
|
||||
this.selectEntry();
|
||||
this.hide();
|
||||
} else {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
addObservers: function(element) {
|
||||
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
|
||||
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
|
||||
},
|
||||
|
||||
onObserverEvent: function() {
|
||||
this.changed = false;
|
||||
this.tokenBounds = null;
|
||||
if(this.getToken().length>=this.options.minChars) {
|
||||
this.getUpdatedChoices();
|
||||
} else {
|
||||
this.active = false;
|
||||
this.hide();
|
||||
}
|
||||
this.oldElementValue = this.element.value;
|
||||
},
|
||||
|
||||
getToken: function() {
|
||||
var bounds = this.getTokenBounds();
|
||||
return this.element.value.substring(bounds[0], bounds[1]).strip();
|
||||
},
|
||||
|
||||
getTokenBounds: function() {
|
||||
if (null != this.tokenBounds) return this.tokenBounds;
|
||||
var value = this.element.value;
|
||||
if (value.strip().empty()) return [-1, 0];
|
||||
var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
|
||||
var offset = (diff == this.oldElementValue.length ? 1 : 0);
|
||||
var prevTokenPos = -1, nextTokenPos = value.length;
|
||||
var tp;
|
||||
for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
|
||||
tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
|
||||
if (tp > prevTokenPos) prevTokenPos = tp;
|
||||
tp = value.indexOf(this.options.tokens[index], diff + offset);
|
||||
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
|
||||
}
|
||||
return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
|
||||
}
|
||||
});
|
||||
|
||||
Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
|
||||
var boundary = Math.min(newS.length, oldS.length);
|
||||
for (var index = 0; index < boundary; ++index)
|
||||
if (newS[index] != oldS[index])
|
||||
return index;
|
||||
return boundary;
|
||||
};
|
||||
|
||||
Ajax.Autocompleter = Class.create(Autocompleter.Base, {
|
||||
initialize: function(element, update, url, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.asynchronous = true;
|
||||
this.options.onComplete = this.onComplete.bind(this);
|
||||
this.options.defaultParams = this.options.parameters || null;
|
||||
this.url = url;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.startIndicator();
|
||||
|
||||
var entry = encodeURIComponent(this.options.paramName) + '=' +
|
||||
encodeURIComponent(this.getToken());
|
||||
|
||||
this.options.parameters = this.options.callback ?
|
||||
this.options.callback(this.element, entry) : entry;
|
||||
|
||||
if(this.options.defaultParams)
|
||||
this.options.parameters += '&' + this.options.defaultParams;
|
||||
|
||||
new Ajax.Request(this.url, this.options);
|
||||
},
|
||||
|
||||
onComplete: function(request) {
|
||||
this.updateChoices(request.responseText);
|
||||
}
|
||||
});
|
||||
|
||||
// The local array autocompleter. Used when you'd prefer to
|
||||
// inject an array of autocompletion options into the page, rather
|
||||
// than sending out Ajax queries, which can be quite slow sometimes.
|
||||
//
|
||||
// The constructor takes four parameters. The first two are, as usual,
|
||||
// the id of the monitored textbox, and id of the autocompletion menu.
|
||||
// The third is the array you want to autocomplete from, and the fourth
|
||||
// is the options block.
|
||||
//
|
||||
// Extra local autocompletion options:
|
||||
// - choices - How many autocompletion choices to offer
|
||||
//
|
||||
// - partialSearch - If false, the autocompleter will match entered
|
||||
// text only at the beginning of strings in the
|
||||
// autocomplete array. Defaults to true, which will
|
||||
// match text at the beginning of any *word* in the
|
||||
// strings in the autocomplete array. If you want to
|
||||
// search anywhere in the string, additionally set
|
||||
// the option fullSearch to true (default: off).
|
||||
//
|
||||
// - fullSsearch - Search anywhere in autocomplete array strings.
|
||||
//
|
||||
// - partialChars - How many characters to enter before triggering
|
||||
// a partial match (unlike minChars, which defines
|
||||
// how many characters are required to do any match
|
||||
// at all). Defaults to 2.
|
||||
//
|
||||
// - ignoreCase - Whether to ignore case when autocompleting.
|
||||
// Defaults to true.
|
||||
//
|
||||
// It's possible to pass in a custom function as the 'selector'
|
||||
// option, if you prefer to write your own autocompletion logic.
|
||||
// In that case, the other options above will not apply unless
|
||||
// you support them.
|
||||
|
||||
Autocompleter.Local = Class.create(Autocompleter.Base, {
|
||||
initialize: function(element, update, array, options) {
|
||||
this.baseInitialize(element, update, options);
|
||||
this.options.array = array;
|
||||
},
|
||||
|
||||
getUpdatedChoices: function() {
|
||||
this.updateChoices(this.options.selector(this));
|
||||
},
|
||||
|
||||
setOptions: function(options) {
|
||||
this.options = Object.extend({
|
||||
choices: 10,
|
||||
partialSearch: true,
|
||||
partialChars: 2,
|
||||
ignoreCase: true,
|
||||
fullSearch: false,
|
||||
selector: function(instance) {
|
||||
var ret = []; // Beginning matches
|
||||
var partial = []; // Inside matches
|
||||
var entry = instance.getToken();
|
||||
var count = 0;
|
||||
|
||||
for (var i = 0; i < instance.options.array.length &&
|
||||
ret.length < instance.options.choices ; i++) {
|
||||
|
||||
var elem = instance.options.array[i];
|
||||
var foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase()) :
|
||||
elem.indexOf(entry);
|
||||
|
||||
while (foundPos != -1) {
|
||||
if (foundPos == 0 && elem.length != entry.length) {
|
||||
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
break;
|
||||
} else if (entry.length >= instance.options.partialChars &&
|
||||
instance.options.partialSearch && foundPos != -1) {
|
||||
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
|
||||
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
||||
foundPos + entry.length) + "</li>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foundPos = instance.options.ignoreCase ?
|
||||
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
|
||||
elem.indexOf(entry, foundPos + 1);
|
||||
|
||||
}
|
||||
}
|
||||
if (partial.length)
|
||||
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
|
||||
return "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, options || { });
|
||||
}
|
||||
});
|
||||
|
||||
// AJAX in-place editor and collection editor
|
||||
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
|
||||
|
||||
// Use this if you notice weird scrolling problems on some browsers,
|
||||
// the DOM might be a bit confused when this gets called so do this
|
||||
// waits 1 ms (with setTimeout) until it does the activation
|
||||
Field.scrollFreeActivate = function(field) {
|
||||
setTimeout(function() {
|
||||
Field.activate(field);
|
||||
}, 1);
|
||||
};
|
||||
|
||||
Ajax.InPlaceEditor = Class.create({
|
||||
initialize: function(element, url, options) {
|
||||
this.url = url;
|
||||
this.element = element = $(element);
|
||||
this.prepareOptions();
|
||||
this._controls = { };
|
||||
arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
|
||||
Object.extend(this.options, options || { });
|
||||
if (!this.options.formId && this.element.id) {
|
||||
this.options.formId = this.element.id + '-inplaceeditor';
|
||||
if ($(this.options.formId))
|
||||
this.options.formId = '';
|
||||
}
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl = $(this.options.externalControl);
|
||||
if (!this.options.externalControl)
|
||||
this.options.externalControlOnly = false;
|
||||
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
|
||||
this.element.title = this.options.clickToEditText;
|
||||
this._boundCancelHandler = this.handleFormCancellation.bind(this);
|
||||
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
|
||||
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
|
||||
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
|
||||
this._boundWrapperHandler = this.wrapUp.bind(this);
|
||||
this.registerListeners();
|
||||
},
|
||||
checkForEscapeOrReturn: function(e) {
|
||||
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
|
||||
if (Event.KEY_ESC == e.keyCode)
|
||||
this.handleFormCancellation(e);
|
||||
else if (Event.KEY_RETURN == e.keyCode)
|
||||
this.handleFormSubmission(e);
|
||||
},
|
||||
createControl: function(mode, handler, extraClasses) {
|
||||
var control = this.options[mode + 'Control'];
|
||||
var text = this.options[mode + 'Text'];
|
||||
if ('button' == control) {
|
||||
var btn = document.createElement('input');
|
||||
btn.type = 'submit';
|
||||
btn.value = text;
|
||||
btn.className = 'editor_' + mode + '_button';
|
||||
if ('cancel' == mode)
|
||||
btn.onclick = this._boundCancelHandler;
|
||||
this._form.appendChild(btn);
|
||||
this._controls[mode] = btn;
|
||||
} else if ('link' == control) {
|
||||
var link = document.createElement('a');
|
||||
link.href = '#';
|
||||
link.appendChild(document.createTextNode(text));
|
||||
link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
|
||||
link.className = 'editor_' + mode + '_link';
|
||||
if (extraClasses)
|
||||
link.className += ' ' + extraClasses;
|
||||
this._form.appendChild(link);
|
||||
this._controls[mode] = link;
|
||||
}
|
||||
},
|
||||
createEditField: function() {
|
||||
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
|
||||
var fld;
|
||||
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
|
||||
fld = document.createElement('input');
|
||||
fld.type = 'text';
|
||||
var size = this.options.size || this.options.cols || 0;
|
||||
if (0 < size) fld.size = size;
|
||||
} else {
|
||||
fld = document.createElement('textarea');
|
||||
fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
|
||||
fld.cols = this.options.cols || 40;
|
||||
}
|
||||
fld.name = this.options.paramName;
|
||||
fld.value = text; // No HTML breaks conversion anymore
|
||||
fld.className = 'editor_field';
|
||||
if (this.options.submitOnBlur)
|
||||
fld.onblur = this._boundSubmitHandler;
|
||||
this._controls.editor = fld;
|
||||
if (this.options.loadTextURL)
|
||||
this.loadExternalText();
|
||||
this._form.appendChild(this._controls.editor);
|
||||
},
|
||||
createForm: function() {
|
||||
var ipe = this;
|
||||
function addText(mode, condition) {
|
||||
var text = ipe.options['text' + mode + 'Controls'];
|
||||
if (!text || condition === false) return;
|
||||
ipe._form.appendChild(document.createTextNode(text));
|
||||
};
|
||||
this._form = $(document.createElement('form'));
|
||||
this._form.id = this.options.formId;
|
||||
this._form.addClassName(this.options.formClassName);
|
||||
this._form.onsubmit = this._boundSubmitHandler;
|
||||
this.createEditField();
|
||||
if ('textarea' == this._controls.editor.tagName.toLowerCase())
|
||||
this._form.appendChild(document.createElement('br'));
|
||||
if (this.options.onFormCustomization)
|
||||
this.options.onFormCustomization(this, this._form);
|
||||
addText('Before', this.options.okControl || this.options.cancelControl);
|
||||
this.createControl('ok', this._boundSubmitHandler);
|
||||
addText('Between', this.options.okControl && this.options.cancelControl);
|
||||
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
|
||||
addText('After', this.options.okControl || this.options.cancelControl);
|
||||
},
|
||||
destroy: function() {
|
||||
if (this._oldInnerHTML)
|
||||
this.element.innerHTML = this._oldInnerHTML;
|
||||
this.leaveEditMode();
|
||||
this.unregisterListeners();
|
||||
},
|
||||
enterEditMode: function(e) {
|
||||
if (this._saving || this._editing) return;
|
||||
this._editing = true;
|
||||
this.triggerCallback('onEnterEditMode');
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.hide();
|
||||
this.element.hide();
|
||||
this.createForm();
|
||||
this.element.parentNode.insertBefore(this._form, this.element);
|
||||
if (!this.options.loadTextURL)
|
||||
this.postProcessEditField();
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
enterHover: function(e) {
|
||||
if (this.options.hoverClassName)
|
||||
this.element.addClassName(this.options.hoverClassName);
|
||||
if (this._saving) return;
|
||||
this.triggerCallback('onEnterHover');
|
||||
},
|
||||
getText: function() {
|
||||
return this.element.innerHTML.unescapeHTML();
|
||||
},
|
||||
handleAJAXFailure: function(transport) {
|
||||
this.triggerCallback('onFailure', transport);
|
||||
if (this._oldInnerHTML) {
|
||||
this.element.innerHTML = this._oldInnerHTML;
|
||||
this._oldInnerHTML = null;
|
||||
}
|
||||
},
|
||||
handleFormCancellation: function(e) {
|
||||
this.wrapUp();
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
handleFormSubmission: function(e) {
|
||||
var form = this._form;
|
||||
var value = $F(this._controls.editor);
|
||||
this.prepareSubmission();
|
||||
var params = this.options.callback(form, value) || '';
|
||||
if (Object.isString(params))
|
||||
params = params.toQueryParams();
|
||||
params.editorId = this.element.id;
|
||||
if (this.options.htmlResponse) {
|
||||
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: params,
|
||||
onComplete: this._boundWrapperHandler,
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Updater({ success: this.element }, this.url, options);
|
||||
} else {
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: params,
|
||||
onComplete: this._boundWrapperHandler,
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Request(this.url, options);
|
||||
}
|
||||
if (e) Event.stop(e);
|
||||
},
|
||||
leaveEditMode: function() {
|
||||
this.element.removeClassName(this.options.savingClassName);
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.element.style.backgroundColor = this._originalBackground;
|
||||
this.element.show();
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.show();
|
||||
this._saving = false;
|
||||
this._editing = false;
|
||||
this._oldInnerHTML = null;
|
||||
this.triggerCallback('onLeaveEditMode');
|
||||
},
|
||||
leaveHover: function(e) {
|
||||
if (this.options.hoverClassName)
|
||||
this.element.removeClassName(this.options.hoverClassName);
|
||||
if (this._saving) return;
|
||||
this.triggerCallback('onLeaveHover');
|
||||
},
|
||||
loadExternalText: function() {
|
||||
this._form.addClassName(this.options.loadingClassName);
|
||||
this._controls.editor.disabled = true;
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
this._form.removeClassName(this.options.loadingClassName);
|
||||
var text = transport.responseText;
|
||||
if (this.options.stripLoadedTextTags)
|
||||
text = text.stripTags();
|
||||
this._controls.editor.value = text;
|
||||
this._controls.editor.disabled = false;
|
||||
this.postProcessEditField();
|
||||
}.bind(this),
|
||||
onFailure: this._boundFailureHandler
|
||||
});
|
||||
new Ajax.Request(this.options.loadTextURL, options);
|
||||
},
|
||||
postProcessEditField: function() {
|
||||
var fpc = this.options.fieldPostCreation;
|
||||
if (fpc)
|
||||
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
|
||||
},
|
||||
prepareOptions: function() {
|
||||
this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
|
||||
Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
|
||||
[this._extraDefaultOptions].flatten().compact().each(function(defs) {
|
||||
Object.extend(this.options, defs);
|
||||
}.bind(this));
|
||||
},
|
||||
prepareSubmission: function() {
|
||||
this._saving = true;
|
||||
this.removeForm();
|
||||
this.leaveHover();
|
||||
this.showSaving();
|
||||
},
|
||||
registerListeners: function() {
|
||||
this._listeners = { };
|
||||
var listener;
|
||||
$H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
|
||||
listener = this[pair.value].bind(this);
|
||||
this._listeners[pair.key] = listener;
|
||||
if (!this.options.externalControlOnly)
|
||||
this.element.observe(pair.key, listener);
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.observe(pair.key, listener);
|
||||
}.bind(this));
|
||||
},
|
||||
removeForm: function() {
|
||||
if (!this._form) return;
|
||||
this._form.remove();
|
||||
this._form = null;
|
||||
this._controls = { };
|
||||
},
|
||||
showSaving: function() {
|
||||
this._oldInnerHTML = this.element.innerHTML;
|
||||
this.element.innerHTML = this.options.savingText;
|
||||
this.element.addClassName(this.options.savingClassName);
|
||||
this.element.style.backgroundColor = this._originalBackground;
|
||||
this.element.show();
|
||||
},
|
||||
triggerCallback: function(cbName, arg) {
|
||||
if ('function' == typeof this.options[cbName]) {
|
||||
this.options[cbName](this, arg);
|
||||
}
|
||||
},
|
||||
unregisterListeners: function() {
|
||||
$H(this._listeners).each(function(pair) {
|
||||
if (!this.options.externalControlOnly)
|
||||
this.element.stopObserving(pair.key, pair.value);
|
||||
if (this.options.externalControl)
|
||||
this.options.externalControl.stopObserving(pair.key, pair.value);
|
||||
}.bind(this));
|
||||
},
|
||||
wrapUp: function(transport) {
|
||||
this.leaveEditMode();
|
||||
// Can't use triggerCallback due to backward compatibility: requires
|
||||
// binding + direct element
|
||||
this._boundComplete(transport, this.element);
|
||||
}
|
||||
});
|
||||
|
||||
Object.extend(Ajax.InPlaceEditor.prototype, {
|
||||
dispose: Ajax.InPlaceEditor.prototype.destroy
|
||||
});
|
||||
|
||||
Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
|
||||
initialize: function($super, element, url, options) {
|
||||
this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
|
||||
$super(element, url, options);
|
||||
},
|
||||
|
||||
createEditField: function() {
|
||||
var list = document.createElement('select');
|
||||
list.name = this.options.paramName;
|
||||
list.size = 1;
|
||||
this._controls.editor = list;
|
||||
this._collection = this.options.collection || [];
|
||||
if (this.options.loadCollectionURL)
|
||||
this.loadCollection();
|
||||
else
|
||||
this.checkForExternalText();
|
||||
this._form.appendChild(this._controls.editor);
|
||||
},
|
||||
|
||||
loadCollection: function() {
|
||||
this._form.addClassName(this.options.loadingClassName);
|
||||
this.showLoadingText(this.options.loadingCollectionText);
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
var js = transport.responseText.strip();
|
||||
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
|
||||
throw('Server returned an invalid collection representation.');
|
||||
this._collection = eval(js);
|
||||
this.checkForExternalText();
|
||||
}.bind(this),
|
||||
onFailure: this.onFailure
|
||||
});
|
||||
new Ajax.Request(this.options.loadCollectionURL, options);
|
||||
},
|
||||
|
||||
showLoadingText: function(text) {
|
||||
this._controls.editor.disabled = true;
|
||||
var tempOption = this._controls.editor.firstChild;
|
||||
if (!tempOption) {
|
||||
tempOption = document.createElement('option');
|
||||
tempOption.value = '';
|
||||
this._controls.editor.appendChild(tempOption);
|
||||
tempOption.selected = true;
|
||||
}
|
||||
tempOption.update((text || '').stripScripts().stripTags());
|
||||
},
|
||||
|
||||
checkForExternalText: function() {
|
||||
this._text = this.getText();
|
||||
if (this.options.loadTextURL)
|
||||
this.loadExternalText();
|
||||
else
|
||||
this.buildOptionList();
|
||||
},
|
||||
|
||||
loadExternalText: function() {
|
||||
this.showLoadingText(this.options.loadingText);
|
||||
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
|
||||
Object.extend(options, {
|
||||
parameters: 'editorId=' + encodeURIComponent(this.element.id),
|
||||
onComplete: Prototype.emptyFunction,
|
||||
onSuccess: function(transport) {
|
||||
this._text = transport.responseText.strip();
|
||||
this.buildOptionList();
|
||||
}.bind(this),
|
||||
onFailure: this.onFailure
|
||||
});
|
||||
new Ajax.Request(this.options.loadTextURL, options);
|
||||
},
|
||||
|
||||
buildOptionList: function() {
|
||||
this._form.removeClassName(this.options.loadingClassName);
|
||||
this._collection = this._collection.map(function(entry) {
|
||||
return 2 === entry.length ? entry : [entry, entry].flatten();
|
||||
});
|
||||
var marker = ('value' in this.options) ? this.options.value : this._text;
|
||||
var textFound = this._collection.any(function(entry) {
|
||||
return entry[0] == marker;
|
||||
}.bind(this));
|
||||
this._controls.editor.update('');
|
||||
var option;
|
||||
this._collection.each(function(entry, index) {
|
||||
option = document.createElement('option');
|
||||
option.value = entry[0];
|
||||
option.selected = textFound ? entry[0] == marker : 0 == index;
|
||||
option.appendChild(document.createTextNode(entry[1]));
|
||||
this._controls.editor.appendChild(option);
|
||||
}.bind(this));
|
||||
this._controls.editor.disabled = false;
|
||||
Field.scrollFreeActivate(this._controls.editor);
|
||||
}
|
||||
});
|
||||
|
||||
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
|
||||
//**** This only exists for a while, in order to let ****
|
||||
//**** users adapt to the new API. Read up on the new ****
|
||||
//**** API and convert your code to it ASAP! ****
|
||||
|
||||
Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
|
||||
if (!options) return;
|
||||
function fallback(name, expr) {
|
||||
if (name in options || expr === undefined) return;
|
||||
options[name] = expr;
|
||||
};
|
||||
fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
|
||||
options.cancelLink == options.cancelButton == false ? false : undefined)));
|
||||
fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
|
||||
options.okLink == options.okButton == false ? false : undefined)));
|
||||
fallback('highlightColor', options.highlightcolor);
|
||||
fallback('highlightEndColor', options.highlightendcolor);
|
||||
};
|
||||
|
||||
Object.extend(Ajax.InPlaceEditor, {
|
||||
DefaultOptions: {
|
||||
ajaxOptions: { },
|
||||
autoRows: 3, // Use when multi-line w/ rows == 1
|
||||
cancelControl: 'link', // 'link'|'button'|false
|
||||
cancelText: 'cancel',
|
||||
clickToEditText: 'Click to edit',
|
||||
externalControl: null, // id|elt
|
||||
externalControlOnly: false,
|
||||
fieldPostCreation: 'activate', // 'activate'|'focus'|false
|
||||
formClassName: 'inplaceeditor-form',
|
||||
formId: null, // id|elt
|
||||
highlightColor: '#ffff99',
|
||||
highlightEndColor: '#ffffff',
|
||||
hoverClassName: '',
|
||||
htmlResponse: true,
|
||||
loadingClassName: 'inplaceeditor-loading',
|
||||
loadingText: 'Loading...',
|
||||
okControl: 'button', // 'link'|'button'|false
|
||||
okText: 'ok',
|
||||
paramName: 'value',
|
||||
rows: 1, // If 1 and multi-line, uses autoRows
|
||||
savingClassName: 'inplaceeditor-saving',
|
||||
savingText: 'Saving...',
|
||||
size: 0,
|
||||
stripLoadedTextTags: false,
|
||||
submitOnBlur: false,
|
||||
textAfterControls: '',
|
||||
textBeforeControls: '',
|
||||
textBetweenControls: ''
|
||||
},
|
||||
DefaultCallbacks: {
|
||||
callback: function(form) {
|
||||
return Form.serialize(form);
|
||||
},
|
||||
onComplete: function(transport, element) {
|
||||
// For backward compatibility, this one is bound to the IPE, and passes
|
||||
// the element directly. It was too often customized, so we don't break it.
|
||||
new Effect.Highlight(element, {
|
||||
startcolor: this.options.highlightColor, keepBackgroundImage: true });
|
||||
},
|
||||
onEnterEditMode: null,
|
||||
onEnterHover: function(ipe) {
|
||||
ipe.element.style.backgroundColor = ipe.options.highlightColor;
|
||||
if (ipe._effect)
|
||||
ipe._effect.cancel();
|
||||
},
|
||||
onFailure: function(transport, ipe) {
|
||||
alert('Error communication with the server: ' + transport.responseText.stripTags());
|
||||
},
|
||||
onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
|
||||
onLeaveEditMode: null,
|
||||
onLeaveHover: function(ipe) {
|
||||
ipe._effect = new Effect.Highlight(ipe.element, {
|
||||
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
|
||||
restorecolor: ipe._originalBackground, keepBackgroundImage: true
|
||||
});
|
||||
}
|
||||
},
|
||||
Listeners: {
|
||||
click: 'enterEditMode',
|
||||
keydown: 'checkForEscapeOrReturn',
|
||||
mouseover: 'enterHover',
|
||||
mouseout: 'leaveHover'
|
||||
}
|
||||
});
|
||||
|
||||
Ajax.InPlaceCollectionEditor.DefaultOptions = {
|
||||
loadingCollectionText: 'Loading options...'
|
||||
};
|
||||
|
||||
// Delayed observer, like Form.Element.Observer,
|
||||
// but waits for delay after last key input
|
||||
// Ideal for live-search fields
|
||||
|
||||
Form.Element.DelayedObserver = Class.create({
|
||||
initialize: function(element, delay, callback) {
|
||||
this.delay = delay || 0.5;
|
||||
this.element = $(element);
|
||||
this.callback = callback;
|
||||
this.timer = null;
|
||||
this.lastValue = $F(this.element);
|
||||
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
|
||||
},
|
||||
delayedListener: function(event) {
|
||||
if(this.lastValue == $F(this.element)) return;
|
||||
if(this.timer) clearTimeout(this.timer);
|
||||
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
|
||||
this.lastValue = $F(this.element);
|
||||
},
|
||||
onTimerEvent: function() {
|
||||
this.timer = null;
|
||||
this.callback(this.element, $F(this.element));
|
||||
}
|
||||
});
|
||||
973
public/javascripts/dragdrop.js
vendored
973
public/javascripts/dragdrop.js
vendored
|
|
@ -1,973 +0,0 @@
|
|||
// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// script.aculo.us is freely distributable under the terms of an MIT-style license.
|
||||
// For details, see the script.aculo.us web site: http://script.aculo.us/
|
||||
|
||||
if(Object.isUndefined(Effect))
|
||||
throw("dragdrop.js requires including script.aculo.us' effects.js library");
|
||||
|
||||
var Droppables = {
|
||||
drops: [],
|
||||
|
||||
remove: function(element) {
|
||||
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
|
||||
},
|
||||
|
||||
add: function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
greedy: true,
|
||||
hoverclass: null,
|
||||
tree: false
|
||||
}, arguments[1] || { });
|
||||
|
||||
// cache containers
|
||||
if(options.containment) {
|
||||
options._containers = [];
|
||||
var containment = options.containment;
|
||||
if(Object.isArray(containment)) {
|
||||
containment.each( function(c) { options._containers.push($(c)) });
|
||||
} else {
|
||||
options._containers.push($(containment));
|
||||
}
|
||||
}
|
||||
|
||||
if(options.accept) options.accept = [options.accept].flatten();
|
||||
|
||||
Element.makePositioned(element); // fix IE
|
||||
options.element = element;
|
||||
|
||||
this.drops.push(options);
|
||||
},
|
||||
|
||||
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) &&
|
||||
((!drop._containers) ||
|
||||
this.isContained(element, drop)) &&
|
||||
((!drop.accept) ||
|
||||
(Element.classNames(element).detect(
|
||||
function(v) { return drop.accept.include(v) } ) )) &&
|
||||
Position.within(drop.element, point[0], point[1]) );
|
||||
},
|
||||
|
||||
deactivate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.removeClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = null;
|
||||
},
|
||||
|
||||
activate: function(drop) {
|
||||
if(drop.hoverclass)
|
||||
Element.addClassName(drop.element, drop.hoverclass);
|
||||
this.last_active = drop;
|
||||
},
|
||||
|
||||
show: function(point, element) {
|
||||
if(!this.drops.length) return;
|
||||
var drop, affected = [];
|
||||
|
||||
this.drops.each( function(drop) {
|
||||
if(Droppables.isAffected(point, element, drop))
|
||||
affected.push(drop);
|
||||
});
|
||||
|
||||
if(affected.length>0)
|
||||
drop = Droppables.findDeepestChild(affected);
|
||||
|
||||
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
|
||||
if (drop) {
|
||||
Position.within(drop.element, point[0], point[1]);
|
||||
if(drop.onHover)
|
||||
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
|
||||
|
||||
if (drop != this.last_active) Droppables.activate(drop);
|
||||
}
|
||||
},
|
||||
|
||||
fire: function(event, element) {
|
||||
if(!this.last_active) return;
|
||||
Position.prepare();
|
||||
|
||||
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
|
||||
if (this.last_active.onDrop) {
|
||||
this.last_active.onDrop(element, this.last_active.element, event);
|
||||
return true;
|
||||
}
|
||||
},
|
||||
|
||||
reset: function() {
|
||||
if(this.last_active)
|
||||
this.deactivate(this.last_active);
|
||||
}
|
||||
};
|
||||
|
||||
var Draggables = {
|
||||
drags: [],
|
||||
observers: [],
|
||||
|
||||
register: function(draggable) {
|
||||
if(this.drags.length == 0) {
|
||||
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
|
||||
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
|
||||
this.eventKeypress = this.keyPress.bindAsEventListener(this);
|
||||
|
||||
Event.observe(document, "mouseup", this.eventMouseUp);
|
||||
Event.observe(document, "mousemove", this.eventMouseMove);
|
||||
Event.observe(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
this.drags.push(draggable);
|
||||
},
|
||||
|
||||
unregister: function(draggable) {
|
||||
this.drags = this.drags.reject(function(d) { return d==draggable });
|
||||
if(this.drags.length == 0) {
|
||||
Event.stopObserving(document, "mouseup", this.eventMouseUp);
|
||||
Event.stopObserving(document, "mousemove", this.eventMouseMove);
|
||||
Event.stopObserving(document, "keypress", this.eventKeypress);
|
||||
}
|
||||
},
|
||||
|
||||
activate: function(draggable) {
|
||||
if(draggable.options.delay) {
|
||||
this._timeout = setTimeout(function() {
|
||||
Draggables._timeout = null;
|
||||
window.focus();
|
||||
Draggables.activeDraggable = draggable;
|
||||
}.bind(this), draggable.options.delay);
|
||||
} else {
|
||||
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
|
||||
this.activeDraggable = draggable;
|
||||
}
|
||||
},
|
||||
|
||||
deactivate: function() {
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
updateDrag: function(event) {
|
||||
if(!this.activeDraggable) return;
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
// Mozilla-based browsers fire successive mousemove events with
|
||||
// the same coordinates, prevent needless redrawing (moz bug?)
|
||||
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
|
||||
this._lastPointer = pointer;
|
||||
|
||||
this.activeDraggable.updateDrag(event, pointer);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(this._timeout) {
|
||||
clearTimeout(this._timeout);
|
||||
this._timeout = null;
|
||||
}
|
||||
if(!this.activeDraggable) return;
|
||||
this._lastPointer = null;
|
||||
this.activeDraggable.endDrag(event);
|
||||
this.activeDraggable = null;
|
||||
},
|
||||
|
||||
keyPress: function(event) {
|
||||
if(this.activeDraggable)
|
||||
this.activeDraggable.keyPress(event);
|
||||
},
|
||||
|
||||
addObserver: function(observer) {
|
||||
this.observers.push(observer);
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
removeObserver: function(element) { // element instead of observer fixes mem leaks
|
||||
this.observers = this.observers.reject( function(o) { return o.element==element });
|
||||
this._cacheObserverCallbacks();
|
||||
},
|
||||
|
||||
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
|
||||
if(this[eventName+'Count'] > 0)
|
||||
this.observers.each( function(o) {
|
||||
if(o[eventName]) o[eventName](eventName, draggable, event);
|
||||
});
|
||||
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
|
||||
},
|
||||
|
||||
_cacheObserverCallbacks: function() {
|
||||
['onStart','onEnd','onDrag'].each( function(eventName) {
|
||||
Draggables[eventName+'Count'] = Draggables.observers.select(
|
||||
function(o) { return o[eventName]; }
|
||||
).length;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var Draggable = Class.create({
|
||||
initialize: function(element) {
|
||||
var defaults = {
|
||||
handle: false,
|
||||
reverteffect: function(element, top_offset, left_offset) {
|
||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
|
||||
queue: {scope:'_draggable', position:'end'}
|
||||
});
|
||||
},
|
||||
endeffect: function(element) {
|
||||
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
|
||||
queue: {scope:'_draggable', position:'end'},
|
||||
afterFinish: function(){
|
||||
Draggable._dragging[element] = false
|
||||
}
|
||||
});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false,
|
||||
quiet: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
delay: 0
|
||||
};
|
||||
|
||||
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
|
||||
Object.extend(defaults, {
|
||||
starteffect: function(element) {
|
||||
element._opacity = Element.getOpacity(element);
|
||||
Draggable._dragging[element] = true;
|
||||
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
|
||||
}
|
||||
});
|
||||
|
||||
var options = Object.extend(defaults, arguments[1] || { });
|
||||
|
||||
this.element = $(element);
|
||||
|
||||
if(options.handle && Object.isString(options.handle))
|
||||
this.handle = this.element.down('.'+options.handle, 0);
|
||||
|
||||
if(!this.handle) this.handle = $(options.handle);
|
||||
if(!this.handle) this.handle = this.element;
|
||||
|
||||
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
|
||||
options.scroll = $(options.scroll);
|
||||
this._isScrollChild = Element.childOf(this.element, options.scroll);
|
||||
}
|
||||
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
this.options = options;
|
||||
this.dragging = false;
|
||||
|
||||
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
|
||||
Event.observe(this.handle, "mousedown", this.eventMouseDown);
|
||||
|
||||
Draggables.register(this);
|
||||
},
|
||||
|
||||
destroy: function() {
|
||||
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
|
||||
Draggables.unregister(this);
|
||||
},
|
||||
|
||||
currentDelta: function() {
|
||||
return([
|
||||
parseInt(Element.getStyle(this.element,'left') || '0'),
|
||||
parseInt(Element.getStyle(this.element,'top') || '0')]);
|
||||
},
|
||||
|
||||
initDrag: function(event) {
|
||||
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
|
||||
Draggable._dragging[this.element]) return;
|
||||
if(Event.isLeftClick(event)) {
|
||||
// abort on form elements, fixes a Firefox issue
|
||||
var src = Event.element(event);
|
||||
if((tag_name = src.tagName.toUpperCase()) && (
|
||||
tag_name=='INPUT' ||
|
||||
tag_name=='SELECT' ||
|
||||
tag_name=='OPTION' ||
|
||||
tag_name=='BUTTON' ||
|
||||
tag_name=='TEXTAREA')) return;
|
||||
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
|
||||
|
||||
Draggables.activate(this);
|
||||
Event.stop(event);
|
||||
}
|
||||
},
|
||||
|
||||
startDrag: function(event) {
|
||||
this.dragging = true;
|
||||
if(!this.delta)
|
||||
this.delta = this.currentDelta();
|
||||
|
||||
if(this.options.zindex) {
|
||||
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
|
||||
this.element.style.zIndex = this.options.zindex;
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
this._clone = this.element.cloneNode(true);
|
||||
this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
|
||||
if (!this._originallyAbsolute)
|
||||
Position.absolutize(this.element);
|
||||
this.element.parentNode.insertBefore(this._clone, this.element);
|
||||
}
|
||||
|
||||
if(this.options.scroll) {
|
||||
if (this.options.scroll == window) {
|
||||
var where = this._getWindowScroll(this.options.scroll);
|
||||
this.originalScrollLeft = where.left;
|
||||
this.originalScrollTop = where.top;
|
||||
} else {
|
||||
this.originalScrollLeft = this.options.scroll.scrollLeft;
|
||||
this.originalScrollTop = this.options.scroll.scrollTop;
|
||||
}
|
||||
}
|
||||
|
||||
Draggables.notify('onStart', this, event);
|
||||
|
||||
if(this.options.starteffect) this.options.starteffect(this.element);
|
||||
},
|
||||
|
||||
updateDrag: function(event, pointer) {
|
||||
if(!this.dragging) this.startDrag(event);
|
||||
|
||||
if(!this.options.quiet){
|
||||
Position.prepare();
|
||||
Droppables.show(pointer, this.element);
|
||||
}
|
||||
|
||||
Draggables.notify('onDrag', this, event);
|
||||
|
||||
this.draw(pointer);
|
||||
if(this.options.change) this.options.change(this);
|
||||
|
||||
if(this.options.scroll) {
|
||||
this.stopScrolling();
|
||||
|
||||
var p;
|
||||
if (this.options.scroll == window) {
|
||||
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
|
||||
} else {
|
||||
p = Position.page(this.options.scroll);
|
||||
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
|
||||
p[1] += this.options.scroll.scrollTop + Position.deltaY;
|
||||
p.push(p[0]+this.options.scroll.offsetWidth);
|
||||
p.push(p[1]+this.options.scroll.offsetHeight);
|
||||
}
|
||||
var speed = [0,0];
|
||||
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
|
||||
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
|
||||
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
|
||||
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
|
||||
this.startScrolling(speed);
|
||||
}
|
||||
|
||||
// fix AppleWebKit rendering
|
||||
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
|
||||
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
finishDrag: function(event, success) {
|
||||
this.dragging = false;
|
||||
|
||||
if(this.options.quiet){
|
||||
Position.prepare();
|
||||
var pointer = [Event.pointerX(event), Event.pointerY(event)];
|
||||
Droppables.show(pointer, this.element);
|
||||
}
|
||||
|
||||
if(this.options.ghosting) {
|
||||
if (!this._originallyAbsolute)
|
||||
Position.relativize(this.element);
|
||||
delete this._originallyAbsolute;
|
||||
Element.remove(this._clone);
|
||||
this._clone = null;
|
||||
}
|
||||
|
||||
var dropped = false;
|
||||
if(success) {
|
||||
dropped = Droppables.fire(event, this.element);
|
||||
if (!dropped) dropped = false;
|
||||
}
|
||||
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
|
||||
Draggables.notify('onEnd', this, event);
|
||||
|
||||
var revert = this.options.revert;
|
||||
if(revert && Object.isFunction(revert)) revert = revert(this.element);
|
||||
|
||||
var d = this.currentDelta();
|
||||
if(revert && this.options.reverteffect) {
|
||||
if (dropped == 0 || revert != 'failure')
|
||||
this.options.reverteffect(this.element,
|
||||
d[1]-this.delta[1], d[0]-this.delta[0]);
|
||||
} else {
|
||||
this.delta = d;
|
||||
}
|
||||
|
||||
if(this.options.zindex)
|
||||
this.element.style.zIndex = this.originalZ;
|
||||
|
||||
if(this.options.endeffect)
|
||||
this.options.endeffect(this.element);
|
||||
|
||||
Draggables.deactivate(this);
|
||||
Droppables.reset();
|
||||
},
|
||||
|
||||
keyPress: function(event) {
|
||||
if(event.keyCode!=Event.KEY_ESC) return;
|
||||
this.finishDrag(event, false);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
endDrag: function(event) {
|
||||
if(!this.dragging) return;
|
||||
this.stopScrolling();
|
||||
this.finishDrag(event, true);
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
draw: function(point) {
|
||||
var pos = Position.cumulativeOffset(this.element);
|
||||
if(this.options.ghosting) {
|
||||
var r = Position.realOffset(this.element);
|
||||
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
|
||||
}
|
||||
|
||||
var d = this.currentDelta();
|
||||
pos[0] -= d[0]; pos[1] -= d[1];
|
||||
|
||||
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
|
||||
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||
}
|
||||
|
||||
var p = [0,1].map(function(i){
|
||||
return (point[i]-pos[i]-this.offset[i])
|
||||
}.bind(this));
|
||||
|
||||
if(this.options.snap) {
|
||||
if(Object.isFunction(this.options.snap)) {
|
||||
p = this.options.snap(p[0],p[1],this);
|
||||
} else {
|
||||
if(Object.isArray(this.options.snap)) {
|
||||
p = p.map( function(v, i) {
|
||||
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
|
||||
} else {
|
||||
p = p.map( function(v) {
|
||||
return (v/this.options.snap).round()*this.options.snap }.bind(this));
|
||||
}
|
||||
}}
|
||||
|
||||
var style = this.element.style;
|
||||
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
|
||||
style.left = p[0] + "px";
|
||||
if((!this.options.constraint) || (this.options.constraint=='vertical'))
|
||||
style.top = p[1] + "px";
|
||||
|
||||
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
|
||||
},
|
||||
|
||||
stopScrolling: function() {
|
||||
if(this.scrollInterval) {
|
||||
clearInterval(this.scrollInterval);
|
||||
this.scrollInterval = null;
|
||||
Draggables._lastScrollPointer = null;
|
||||
}
|
||||
},
|
||||
|
||||
startScrolling: function(speed) {
|
||||
if(!(speed[0] || speed[1])) return;
|
||||
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
|
||||
this.lastScrolled = new Date();
|
||||
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
|
||||
},
|
||||
|
||||
scroll: function() {
|
||||
var current = new Date();
|
||||
var delta = current - this.lastScrolled;
|
||||
this.lastScrolled = current;
|
||||
if(this.options.scroll == window) {
|
||||
with (this._getWindowScroll(this.options.scroll)) {
|
||||
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
|
||||
var d = delta / 1000;
|
||||
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
|
||||
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
|
||||
}
|
||||
|
||||
Position.prepare();
|
||||
Droppables.show(Draggables._lastPointer, this.element);
|
||||
Draggables.notify('onDrag', this);
|
||||
if (this._isScrollChild) {
|
||||
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);
|
||||
},
|
||||
|
||||
_getWindowScroll: function(w) {
|
||||
var T, L, W, H;
|
||||
with (w.document) {
|
||||
if (w.document.documentElement && documentElement.scrollTop) {
|
||||
T = documentElement.scrollTop;
|
||||
L = documentElement.scrollLeft;
|
||||
} else if (w.document.body) {
|
||||
T = body.scrollTop;
|
||||
L = body.scrollLeft;
|
||||
}
|
||||
if (w.innerWidth) {
|
||||
W = w.innerWidth;
|
||||
H = w.innerHeight;
|
||||
} else if (w.document.documentElement && documentElement.clientWidth) {
|
||||
W = documentElement.clientWidth;
|
||||
H = documentElement.clientHeight;
|
||||
} else {
|
||||
W = body.offsetWidth;
|
||||
H = body.offsetHeight;
|
||||
}
|
||||
}
|
||||
return { top: T, left: L, width: W, height: H };
|
||||
}
|
||||
});
|
||||
|
||||
Draggable._dragging = { };
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
var SortableObserver = Class.create({
|
||||
initialize: function(element, observer) {
|
||||
this.element = $(element);
|
||||
this.observer = observer;
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
onStart: function() {
|
||||
this.lastValue = Sortable.serialize(this.element);
|
||||
},
|
||||
|
||||
onEnd: function() {
|
||||
Sortable.unmark();
|
||||
if(this.lastValue != Sortable.serialize(this.element))
|
||||
this.observer(this.element)
|
||||
}
|
||||
});
|
||||
|
||||
var Sortable = {
|
||||
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
|
||||
|
||||
sortables: { },
|
||||
|
||||
_findRootElement: function(element) {
|
||||
while (element.tagName.toUpperCase() != "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);
|
||||
var s = Sortable.sortables[element.id];
|
||||
|
||||
if(s) {
|
||||
Draggables.removeObserver(s.element);
|
||||
s.droppables.each(function(d){ Droppables.remove(d) });
|
||||
s.draggables.invoke('destroy');
|
||||
|
||||
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,
|
||||
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
|
||||
handle: false, // or a CSS class
|
||||
only: false,
|
||||
delay: 0,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
quiet: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
format: this.SERIALIZE_RULE,
|
||||
|
||||
// these take arrays of elements or ids and can be
|
||||
// used for better initialization performance
|
||||
elements: false,
|
||||
handles: false,
|
||||
|
||||
onChange: Prototype.emptyFunction,
|
||||
onUpdate: Prototype.emptyFunction
|
||||
}, arguments[1] || { });
|
||||
|
||||
// clear any old sortable with same element
|
||||
this.destroy(element);
|
||||
|
||||
// build options for the draggables
|
||||
var options_for_draggable = {
|
||||
revert: true,
|
||||
quiet: options.quiet,
|
||||
scroll: options.scroll,
|
||||
scrollSpeed: options.scrollSpeed,
|
||||
scrollSensitivity: options.scrollSensitivity,
|
||||
delay: options.delay,
|
||||
ghosting: options.ghosting,
|
||||
constraint: options.constraint,
|
||||
handle: options.handle };
|
||||
|
||||
if(options.starteffect)
|
||||
options_for_draggable.starteffect = options.starteffect;
|
||||
|
||||
if(options.reverteffect)
|
||||
options_for_draggable.reverteffect = options.reverteffect;
|
||||
else
|
||||
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
|
||||
element.style.top = 0;
|
||||
element.style.left = 0;
|
||||
};
|
||||
|
||||
if(options.endeffect)
|
||||
options_for_draggable.endeffect = options.endeffect;
|
||||
|
||||
if(options.zindex)
|
||||
options_for_draggable.zindex = options.zindex;
|
||||
|
||||
// build options for the droppables
|
||||
var options_for_droppable = {
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
tree: options.tree,
|
||||
hoverclass: options.hoverclass,
|
||||
onHover: Sortable.onHover
|
||||
};
|
||||
|
||||
var options_for_tree = {
|
||||
onHover: Sortable.onEmptyHover,
|
||||
overlap: options.overlap,
|
||||
containment: options.containment,
|
||||
hoverclass: options.hoverclass
|
||||
};
|
||||
|
||||
// fix for gecko engine
|
||||
Element.cleanWhitespace(element);
|
||||
|
||||
options.draggables = [];
|
||||
options.droppables = [];
|
||||
|
||||
// drop on empty handling
|
||||
if(options.dropOnEmpty || options.tree) {
|
||||
Droppables.add(element, options_for_tree);
|
||||
options.droppables.push(element);
|
||||
}
|
||||
|
||||
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
|
||||
var handle = options.handles ? $(options.handles[i]) :
|
||||
(options.handle ? $(e).select('.' + options.handle)[0] : e);
|
||||
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[element.id] = options;
|
||||
|
||||
// for onupdate
|
||||
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
|
||||
|
||||
},
|
||||
|
||||
// return all suitable-for-sortable elements in a guaranteed order
|
||||
findElements: function(element, options) {
|
||||
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(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;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, dropon);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(element);
|
||||
}
|
||||
} else {
|
||||
Sortable.mark(dropon, 'after');
|
||||
var nextElement = dropon.nextSibling || null;
|
||||
if(nextElement != element) {
|
||||
var oldParentNode = element.parentNode;
|
||||
element.style.visibility = "hidden"; // fix gecko rendering
|
||||
dropon.parentNode.insertBefore(element, nextElement);
|
||||
if(dropon.parentNode!=oldParentNode)
|
||||
Sortable.options(oldParentNode).onChange(element);
|
||||
Sortable.options(dropon.parentNode).onChange(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, only: droponOptions.only});
|
||||
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);
|
||||
droponOptions.onChange(element);
|
||||
}
|
||||
},
|
||||
|
||||
unmark: function() {
|
||||
if(Sortable._marker) Sortable._marker.hide();
|
||||
},
|
||||
|
||||
mark: function(dropon, position) {
|
||||
// mark on ghosting only
|
||||
var sortable = Sortable.options(dropon.parentNode);
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker =
|
||||
($('dropmarker') || Element.extend(document.createElement('DIV'))).
|
||||
hide().addClassName('dropmarker').setStyle({position:'absolute'});
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
|
||||
else
|
||||
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
|
||||
|
||||
Sortable._marker.show();
|
||||
},
|
||||
|
||||
_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: [],
|
||||
position: parent.children.length,
|
||||
container: $(children[i]).down(options.treeTag)
|
||||
};
|
||||
|
||||
/* 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;
|
||||
},
|
||||
|
||||
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: [],
|
||||
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);
|
||||
var options = Object.extend(this.options(element), arguments[1] || { });
|
||||
|
||||
return $(this.findElements(element, options) || []).map( function(item) {
|
||||
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
|
||||
});
|
||||
},
|
||||
|
||||
setSequence: function(element, new_sequence) {
|
||||
element = $(element);
|
||||
var options = Object.extend(this.options(element), arguments[2] || { });
|
||||
|
||||
var nodeMap = { };
|
||||
this.findElements(element, options).each( function(n) {
|
||||
if (n.id.match(options.format))
|
||||
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
|
||||
n.parentNode.removeChild(n);
|
||||
});
|
||||
|
||||
new_sequence.each(function(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);
|
||||
|
||||
if (options.tree) {
|
||||
return Sortable.tree(element, arguments[1]).children.map( function (item) {
|
||||
return [name + Sortable._constructIndex(item) + "[id]=" +
|
||||
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) {
|
||||
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
|
||||
};
|
||||
1128
public/javascripts/effects.js
vendored
1128
public/javascripts/effects.js
vendored
File diff suppressed because it is too large
Load diff
32
public/javascripts/jquery-1.2.6.min.js
vendored
32
public/javascripts/jquery-1.2.6.min.js
vendored
File diff suppressed because one or more lines are too long
104
public/javascripts/jquery-ui.js
vendored
Executable file
104
public/javascripts/jquery-ui.js
vendored
Executable file
File diff suppressed because one or more lines are too long
809
public/javascripts/jquery.autocomplete.js
Normal file
809
public/javascripts/jquery.autocomplete.js
Normal file
|
|
@ -0,0 +1,809 @@
|
|||
/*
|
||||
* jQuery Autocomplete plugin 1.1
|
||||
*
|
||||
* Copyright (c) 2009 Jörn Zaefferer
|
||||
*
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Revision: $Id: jquery.autocomplete.js 15 2009-08-22 10:30:27Z joern.zaefferer $
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
$.fn.extend({
|
||||
autocomplete: function(urlOrData, options) {
|
||||
var isUrl = typeof urlOrData == "string";
|
||||
options = $.extend({}, $.Autocompleter.defaults, {
|
||||
url: isUrl ? urlOrData : null,
|
||||
data: isUrl ? null : urlOrData,
|
||||
delay: isUrl ? $.Autocompleter.defaults.delay : 10,
|
||||
max: options && !options.scroll ? 10 : 150
|
||||
}, options);
|
||||
|
||||
// if highlight is set to false, replace it with a do-nothing function
|
||||
options.highlight = options.highlight || function(value) { return value; };
|
||||
|
||||
// if the formatMatch option is not specified, then use formatItem for backwards compatibility
|
||||
options.formatMatch = options.formatMatch || options.formatItem;
|
||||
|
||||
return this.each(function() {
|
||||
new $.Autocompleter(this, options);
|
||||
});
|
||||
},
|
||||
result: function(handler) {
|
||||
return this.bind("result", handler);
|
||||
},
|
||||
search: function(handler) {
|
||||
return this.trigger("search", [handler]);
|
||||
},
|
||||
flushCache: function() {
|
||||
return this.trigger("flushCache");
|
||||
},
|
||||
setOptions: function(options){
|
||||
return this.trigger("setOptions", [options]);
|
||||
},
|
||||
unautocomplete: function() {
|
||||
return this.trigger("unautocomplete");
|
||||
}
|
||||
});
|
||||
|
||||
$.Autocompleter = function(input, options) {
|
||||
|
||||
var KEY = {
|
||||
UP: 38,
|
||||
DOWN: 40,
|
||||
DEL: 46,
|
||||
TAB: 9,
|
||||
RETURN: 13,
|
||||
ESC: 27,
|
||||
COMMA: 188,
|
||||
PAGEUP: 33,
|
||||
PAGEDOWN: 34,
|
||||
BACKSPACE: 8
|
||||
};
|
||||
|
||||
// Create $ object for input element
|
||||
var $input = $(input).attr("autocomplete", "off").addClass(options.inputClass);
|
||||
|
||||
var timeout;
|
||||
var previousValue = "";
|
||||
var cache = $.Autocompleter.Cache(options);
|
||||
var hasFocus = 0;
|
||||
var lastKeyPressCode;
|
||||
var config = {
|
||||
mouseDownOnSelect: false
|
||||
};
|
||||
var select = $.Autocompleter.Select(options, input, selectCurrent, config);
|
||||
|
||||
var blockSubmit;
|
||||
|
||||
// prevent form submit in opera when selecting with return key
|
||||
$.browser.opera && $(input.form).bind("submit.autocomplete", function() {
|
||||
if (blockSubmit) {
|
||||
blockSubmit = false;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
// only opera doesn't trigger keydown multiple times while pressed, others don't work with keypress at all
|
||||
$input.bind(($.browser.opera ? "keypress" : "keydown") + ".autocomplete", function(event) {
|
||||
// a keypress means the input has focus
|
||||
// avoids issue where input had focus before the autocomplete was applied
|
||||
hasFocus = 1;
|
||||
// track last key pressed
|
||||
lastKeyPressCode = event.keyCode;
|
||||
switch(event.keyCode) {
|
||||
|
||||
case KEY.UP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.prev();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.DOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.next();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEUP:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageUp();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.PAGEDOWN:
|
||||
event.preventDefault();
|
||||
if ( select.visible() ) {
|
||||
select.pageDown();
|
||||
} else {
|
||||
onChange(0, true);
|
||||
}
|
||||
break;
|
||||
|
||||
// matches also semicolon
|
||||
case options.multiple && $.trim(options.multipleSeparator) == "," && KEY.COMMA:
|
||||
case KEY.TAB:
|
||||
case KEY.RETURN:
|
||||
if( selectCurrent() ) {
|
||||
// stop default to prevent a form submit, Opera needs special handling
|
||||
event.preventDefault();
|
||||
blockSubmit = true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
case KEY.ESC:
|
||||
select.hide();
|
||||
break;
|
||||
|
||||
default:
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(onChange, options.delay);
|
||||
break;
|
||||
}
|
||||
}).focus(function(){
|
||||
// track whether the field has focus, we shouldn't process any
|
||||
// results if the field no longer has focus
|
||||
hasFocus++;
|
||||
}).blur(function() {
|
||||
hasFocus = 0;
|
||||
if (!config.mouseDownOnSelect) {
|
||||
hideResults();
|
||||
}
|
||||
}).click(function() {
|
||||
// show select when clicking in a focused field
|
||||
if ( hasFocus++ > 1 && !select.visible() ) {
|
||||
onChange(0, true);
|
||||
}
|
||||
}).bind("search", function() {
|
||||
// TODO why not just specifying both arguments?
|
||||
var fn = (arguments.length > 1) ? arguments[1] : null;
|
||||
function findValueCallback(q, data) {
|
||||
var result;
|
||||
if( data && data.length ) {
|
||||
for (var i=0; i < data.length; i++) {
|
||||
if( data[i].result.toLowerCase() == q.toLowerCase() ) {
|
||||
result = data[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if( typeof fn == "function" ) fn(result);
|
||||
else $input.trigger("result", result && [result.data, result.value]);
|
||||
}
|
||||
$.each(trimWords($input.val()), function(i, value) {
|
||||
request(value, findValueCallback, findValueCallback);
|
||||
});
|
||||
}).bind("flushCache", function() {
|
||||
cache.flush();
|
||||
}).bind("setOptions", function() {
|
||||
$.extend(options, arguments[1]);
|
||||
// if we've updated the data, repopulate
|
||||
if ( "data" in arguments[1] )
|
||||
cache.populate();
|
||||
}).bind("unautocomplete", function() {
|
||||
select.unbind();
|
||||
$input.unbind();
|
||||
$(input.form).unbind(".autocomplete");
|
||||
});
|
||||
|
||||
|
||||
function selectCurrent() {
|
||||
var selected = select.selected();
|
||||
if( !selected )
|
||||
return false;
|
||||
|
||||
var v = selected.result;
|
||||
previousValue = v;
|
||||
|
||||
if ( options.multiple ) {
|
||||
var words = trimWords($input.val());
|
||||
if ( words.length > 1 ) {
|
||||
var seperator = options.multipleSeparator.length;
|
||||
var cursorAt = $(input).selection().start;
|
||||
var wordAt, progress = 0;
|
||||
$.each(words, function(i, word) {
|
||||
progress += word.length;
|
||||
if (cursorAt <= progress) {
|
||||
wordAt = i;
|
||||
return false;
|
||||
}
|
||||
progress += seperator;
|
||||
});
|
||||
words[wordAt] = v;
|
||||
// TODO this should set the cursor to the right position, but it gets overriden somewhere
|
||||
//$.Autocompleter.Selection(input, progress + seperator, progress + seperator);
|
||||
v = words.join( options.multipleSeparator );
|
||||
}
|
||||
v += options.multipleSeparator;
|
||||
}
|
||||
|
||||
$input.val(v);
|
||||
hideResultsNow();
|
||||
$input.trigger("result", [selected.data, selected.value]);
|
||||
return true;
|
||||
}
|
||||
|
||||
function onChange(crap, skipPrevCheck) {
|
||||
if( lastKeyPressCode == KEY.DEL ) {
|
||||
select.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
var currentValue = $input.val();
|
||||
|
||||
if ( !skipPrevCheck && currentValue == previousValue )
|
||||
return;
|
||||
|
||||
previousValue = currentValue;
|
||||
|
||||
currentValue = lastWord(currentValue);
|
||||
if ( currentValue.length >= options.minChars) {
|
||||
$input.addClass(options.loadingClass);
|
||||
if (!options.matchCase)
|
||||
currentValue = currentValue.toLowerCase();
|
||||
request(currentValue, receiveData, hideResultsNow);
|
||||
} else {
|
||||
stopLoading();
|
||||
select.hide();
|
||||
}
|
||||
};
|
||||
|
||||
function trimWords(value) {
|
||||
if (!value)
|
||||
return [""];
|
||||
if (!options.multiple)
|
||||
return [$.trim(value)];
|
||||
return $.map(value.split(options.multipleSeparator), function(word) {
|
||||
return $.trim(value).length ? $.trim(word) : null;
|
||||
});
|
||||
}
|
||||
|
||||
function lastWord(value) {
|
||||
if ( !options.multiple )
|
||||
return value;
|
||||
var words = trimWords(value);
|
||||
if (words.length == 1)
|
||||
return words[0];
|
||||
var cursorAt = $(input).selection().start;
|
||||
if (cursorAt == value.length) {
|
||||
words = trimWords(value)
|
||||
} else {
|
||||
words = trimWords(value.replace(value.substring(cursorAt), ""));
|
||||
}
|
||||
return words[words.length - 1];
|
||||
}
|
||||
|
||||
// fills in the input box w/the first match (assumed to be the best match)
|
||||
// q: the term entered
|
||||
// sValue: the first matching result
|
||||
function autoFill(q, sValue){
|
||||
// autofill in the complete box w/the first match as long as the user hasn't entered in more data
|
||||
// if the last user key pressed was backspace, don't autofill
|
||||
if( options.autoFill && (lastWord($input.val()).toLowerCase() == q.toLowerCase()) && lastKeyPressCode != KEY.BACKSPACE ) {
|
||||
// fill in the value (keep the case the user has typed)
|
||||
$input.val($input.val() + sValue.substring(lastWord(previousValue).length));
|
||||
// select the portion of the value not typed by the user (so the next character will erase)
|
||||
$(input).selection(previousValue.length, previousValue.length + sValue.length);
|
||||
}
|
||||
};
|
||||
|
||||
function hideResults() {
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(hideResultsNow, 200);
|
||||
};
|
||||
|
||||
function hideResultsNow() {
|
||||
var wasVisible = select.visible();
|
||||
select.hide();
|
||||
clearTimeout(timeout);
|
||||
stopLoading();
|
||||
if (options.mustMatch) {
|
||||
// call search and run callback
|
||||
$input.search(
|
||||
function (result){
|
||||
// if no value found, clear the input box
|
||||
if( !result ) {
|
||||
if (options.multiple) {
|
||||
var words = trimWords($input.val()).slice(0, -1);
|
||||
$input.val( words.join(options.multipleSeparator) + (words.length ? options.multipleSeparator : "") );
|
||||
}
|
||||
else {
|
||||
$input.val( "" );
|
||||
$input.trigger("result", null);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
function receiveData(q, data) {
|
||||
if ( data && data.length && hasFocus ) {
|
||||
stopLoading();
|
||||
select.display(data, q);
|
||||
autoFill(q, data[0].value);
|
||||
select.show();
|
||||
} else {
|
||||
hideResultsNow();
|
||||
}
|
||||
};
|
||||
|
||||
function request(term, success, failure) {
|
||||
if (!options.matchCase)
|
||||
term = term.toLowerCase();
|
||||
var data = cache.load(term);
|
||||
// recieve the cached data
|
||||
if (data && data.length) {
|
||||
success(term, data);
|
||||
// if an AJAX url has been supplied, try loading the data now
|
||||
} else if( (typeof options.url == "string") && (options.url.length > 0) ){
|
||||
|
||||
var extraParams = {
|
||||
timestamp: +new Date()
|
||||
};
|
||||
$.each(options.extraParams, function(key, param) {
|
||||
extraParams[key] = typeof param == "function" ? param() : param;
|
||||
});
|
||||
|
||||
$.ajax({
|
||||
// try to leverage ajaxQueue plugin to abort previous requests
|
||||
mode: "abort",
|
||||
// limit abortion to this input
|
||||
port: "autocomplete" + input.name,
|
||||
dataType: options.dataType,
|
||||
type: 'POST',
|
||||
url: options.url,
|
||||
data: $.extend({
|
||||
q: lastWord(term),
|
||||
limit: options.max
|
||||
}, extraParams),
|
||||
success: function(data) {
|
||||
var parsed = options.parse && options.parse(data) || parse(data);
|
||||
cache.add(term, parsed);
|
||||
success(term, parsed);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if we have a failure, we need to empty the list -- this prevents the the [TAB] key from selecting the last successful match
|
||||
select.emptyList();
|
||||
failure(term);
|
||||
}
|
||||
};
|
||||
|
||||
function parse(data) {
|
||||
var parsed = [];
|
||||
var rows = data.split("\n");
|
||||
for (var i=0; i < rows.length; i++) {
|
||||
var row = $.trim(rows[i]);
|
||||
if (row) {
|
||||
row = row.split("|");
|
||||
parsed[parsed.length] = {
|
||||
data: row,
|
||||
value: row[0],
|
||||
result: options.formatResult && options.formatResult(row, row[0]) || row[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
};
|
||||
|
||||
function stopLoading() {
|
||||
$input.removeClass(options.loadingClass);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
$.Autocompleter.defaults = {
|
||||
inputClass: "ac_input",
|
||||
resultsClass: "ac_results",
|
||||
loadingClass: "ac_loading",
|
||||
minChars: 1,
|
||||
delay: 400,
|
||||
matchCase: false,
|
||||
matchSubset: true,
|
||||
matchContains: false,
|
||||
cacheLength: 10,
|
||||
max: 100,
|
||||
mustMatch: false,
|
||||
extraParams: {},
|
||||
selectFirst: true,
|
||||
formatItem: function(row) { return row[0]; },
|
||||
formatMatch: null,
|
||||
autoFill: false,
|
||||
width: 0,
|
||||
multiple: false,
|
||||
multipleSeparator: ", ",
|
||||
highlight: function(value, term) {
|
||||
return value.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + term.replace(/([\^\$\(\)\[\]\{\}\*\.\+\?\|\\])/gi, "\\$1") + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
|
||||
},
|
||||
scroll: true,
|
||||
scrollHeight: 180
|
||||
};
|
||||
|
||||
$.Autocompleter.Cache = function(options) {
|
||||
|
||||
var data = {};
|
||||
var length = 0;
|
||||
|
||||
function matchSubset(s, sub) {
|
||||
if (!options.matchCase)
|
||||
s = s.toLowerCase();
|
||||
var i = s.indexOf(sub);
|
||||
if (options.matchContains == "word"){
|
||||
i = s.toLowerCase().search("\\b" + sub.toLowerCase());
|
||||
}
|
||||
if (i == -1) return false;
|
||||
return i == 0 || options.matchContains;
|
||||
};
|
||||
|
||||
function add(q, value) {
|
||||
if (length > options.cacheLength){
|
||||
flush();
|
||||
}
|
||||
if (!data[q]){
|
||||
length++;
|
||||
}
|
||||
data[q] = value;
|
||||
}
|
||||
|
||||
function populate(){
|
||||
if( !options.data ) return false;
|
||||
// track the matches
|
||||
var stMatchSets = {},
|
||||
nullData = 0;
|
||||
|
||||
// no url was specified, we need to adjust the cache length to make sure it fits the local data store
|
||||
if( !options.url ) options.cacheLength = 1;
|
||||
|
||||
// track all options for minChars = 0
|
||||
stMatchSets[""] = [];
|
||||
|
||||
// loop through the array and create a lookup structure
|
||||
for ( var i = 0, ol = options.data.length; i < ol; i++ ) {
|
||||
var rawValue = options.data[i];
|
||||
// if rawValue is a string, make an array otherwise just reference the array
|
||||
rawValue = (typeof rawValue == "string") ? [rawValue] : rawValue;
|
||||
|
||||
var value = options.formatMatch(rawValue, i+1, options.data.length);
|
||||
if ( value === false )
|
||||
continue;
|
||||
|
||||
var firstChar = value.charAt(0).toLowerCase();
|
||||
// if no lookup array for this character exists, look it up now
|
||||
if( !stMatchSets[firstChar] )
|
||||
stMatchSets[firstChar] = [];
|
||||
|
||||
// if the match is a string
|
||||
var row = {
|
||||
value: value,
|
||||
data: rawValue,
|
||||
result: options.formatResult && options.formatResult(rawValue) || value
|
||||
};
|
||||
|
||||
// push the current match into the set list
|
||||
stMatchSets[firstChar].push(row);
|
||||
|
||||
// keep track of minChars zero items
|
||||
if ( nullData++ < options.max ) {
|
||||
stMatchSets[""].push(row);
|
||||
}
|
||||
};
|
||||
|
||||
// add the data items to the cache
|
||||
$.each(stMatchSets, function(i, value) {
|
||||
// increase the cache size
|
||||
options.cacheLength++;
|
||||
// add to the cache
|
||||
add(i, value);
|
||||
});
|
||||
}
|
||||
|
||||
// populate any existing data
|
||||
setTimeout(populate, 25);
|
||||
|
||||
function flush(){
|
||||
data = {};
|
||||
length = 0;
|
||||
}
|
||||
|
||||
return {
|
||||
flush: flush,
|
||||
add: add,
|
||||
populate: populate,
|
||||
load: function(q) {
|
||||
if (!options.cacheLength || !length)
|
||||
return null;
|
||||
/*
|
||||
* if dealing w/local data and matchContains than we must make sure
|
||||
* to loop through all the data collections looking for matches
|
||||
*/
|
||||
if( !options.url && options.matchContains ){
|
||||
// track all matches
|
||||
var csub = [];
|
||||
// loop through all the data grids for matches
|
||||
for( var k in data ){
|
||||
// don't search through the stMatchSets[""] (minChars: 0) cache
|
||||
// this prevents duplicates
|
||||
if( k.length > 0 ){
|
||||
var c = data[k];
|
||||
$.each(c, function(i, x) {
|
||||
// if we've got a match, add it to the array
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub.push(x);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return csub;
|
||||
} else
|
||||
// if the exact item exists, use it
|
||||
if (data[q]){
|
||||
return data[q];
|
||||
} else
|
||||
if (options.matchSubset) {
|
||||
for (var i = q.length - 1; i >= options.minChars; i--) {
|
||||
var c = data[q.substr(0, i)];
|
||||
if (c) {
|
||||
var csub = [];
|
||||
$.each(c, function(i, x) {
|
||||
if (matchSubset(x.value, q)) {
|
||||
csub[csub.length] = x;
|
||||
}
|
||||
});
|
||||
return csub;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.Autocompleter.Select = function (options, input, select, config) {
|
||||
var CLASSES = {
|
||||
ACTIVE: "ac_over"
|
||||
};
|
||||
|
||||
var listItems,
|
||||
active = -1,
|
||||
data,
|
||||
term = "",
|
||||
needsInit = true,
|
||||
element,
|
||||
list;
|
||||
|
||||
// Create results
|
||||
function init() {
|
||||
if (!needsInit)
|
||||
return;
|
||||
element = $("<div/>")
|
||||
.hide()
|
||||
.addClass(options.resultsClass)
|
||||
.css("position", "absolute")
|
||||
.appendTo(document.body);
|
||||
|
||||
list = $("<ul/>").appendTo(element).mouseover( function(event) {
|
||||
if(target(event).nodeName && target(event).nodeName.toUpperCase() == 'LI') {
|
||||
active = $("li", list).removeClass(CLASSES.ACTIVE).index(target(event));
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
}
|
||||
}).click(function(event) {
|
||||
$(target(event)).addClass(CLASSES.ACTIVE);
|
||||
select();
|
||||
// TODO provide option to avoid setting focus again after selection? useful for cleanup-on-focus
|
||||
input.focus();
|
||||
return false;
|
||||
}).mousedown(function() {
|
||||
config.mouseDownOnSelect = true;
|
||||
}).mouseup(function() {
|
||||
config.mouseDownOnSelect = false;
|
||||
});
|
||||
|
||||
if( options.width > 0 )
|
||||
element.css("width", options.width);
|
||||
|
||||
needsInit = false;
|
||||
}
|
||||
|
||||
function target(event) {
|
||||
var element = event.target;
|
||||
while(element && element.tagName != "LI")
|
||||
element = element.parentNode;
|
||||
// more fun with IE, sometimes event.target is empty, just ignore it then
|
||||
if(!element)
|
||||
return [];
|
||||
return element;
|
||||
}
|
||||
|
||||
function moveSelect(step) {
|
||||
listItems.slice(active, active + 1).removeClass(CLASSES.ACTIVE);
|
||||
movePosition(step);
|
||||
var activeItem = listItems.slice(active, active + 1).addClass(CLASSES.ACTIVE);
|
||||
if(options.scroll) {
|
||||
var offset = 0;
|
||||
listItems.slice(0, active).each(function() {
|
||||
offset += this.offsetHeight;
|
||||
});
|
||||
if((offset + activeItem[0].offsetHeight - list.scrollTop()) > list[0].clientHeight) {
|
||||
list.scrollTop(offset + activeItem[0].offsetHeight - list.innerHeight());
|
||||
} else if(offset < list.scrollTop()) {
|
||||
list.scrollTop(offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function movePosition(step) {
|
||||
active += step;
|
||||
if (active < 0) {
|
||||
active = listItems.size() - 1;
|
||||
} else if (active >= listItems.size()) {
|
||||
active = 0;
|
||||
}
|
||||
}
|
||||
|
||||
function limitNumberOfItems(available) {
|
||||
return options.max && options.max < available
|
||||
? options.max
|
||||
: available;
|
||||
}
|
||||
|
||||
function fillList() {
|
||||
list.empty();
|
||||
var max = limitNumberOfItems(data.length);
|
||||
for (var i=0; i < max; i++) {
|
||||
if (!data[i])
|
||||
continue;
|
||||
var formatted = options.formatItem(data[i].data, i+1, max, data[i].value, term);
|
||||
if ( formatted === false )
|
||||
continue;
|
||||
var li = $("<li/>").html( options.highlight(formatted, term) ).addClass(i%2 == 0 ? "ac_even" : "ac_odd").appendTo(list)[0];
|
||||
$.data(li, "ac_data", data[i]);
|
||||
}
|
||||
listItems = list.find("li");
|
||||
if ( options.selectFirst ) {
|
||||
listItems.slice(0, 1).addClass(CLASSES.ACTIVE);
|
||||
active = 0;
|
||||
}
|
||||
// apply bgiframe if available
|
||||
if ( $.fn.bgiframe )
|
||||
list.bgiframe();
|
||||
}
|
||||
|
||||
return {
|
||||
display: function(d, q) {
|
||||
init();
|
||||
data = d;
|
||||
term = q;
|
||||
fillList();
|
||||
},
|
||||
next: function() {
|
||||
moveSelect(1);
|
||||
},
|
||||
prev: function() {
|
||||
moveSelect(-1);
|
||||
},
|
||||
pageUp: function() {
|
||||
if (active != 0 && active - 8 < 0) {
|
||||
moveSelect( -active );
|
||||
} else {
|
||||
moveSelect(-8);
|
||||
}
|
||||
},
|
||||
pageDown: function() {
|
||||
if (active != listItems.size() - 1 && active + 8 > listItems.size()) {
|
||||
moveSelect( listItems.size() - 1 - active );
|
||||
} else {
|
||||
moveSelect(8);
|
||||
}
|
||||
},
|
||||
hide: function() {
|
||||
element && element.hide();
|
||||
listItems && listItems.removeClass(CLASSES.ACTIVE);
|
||||
active = -1;
|
||||
},
|
||||
visible : function() {
|
||||
return element && element.is(":visible");
|
||||
},
|
||||
current: function() {
|
||||
return this.visible() && (listItems.filter("." + CLASSES.ACTIVE)[0] || options.selectFirst && listItems[0]);
|
||||
},
|
||||
show: function() {
|
||||
var offset = $(input).offset();
|
||||
element.css({
|
||||
width: typeof options.width == "string" || options.width > 0 ? options.width : $(input).width(),
|
||||
top: offset.top + input.offsetHeight,
|
||||
left: offset.left
|
||||
}).show();
|
||||
if(options.scroll) {
|
||||
list.scrollTop(0);
|
||||
list.css({
|
||||
maxHeight: options.scrollHeight,
|
||||
overflow: 'auto'
|
||||
});
|
||||
|
||||
if($.browser.msie && typeof document.body.style.maxHeight === "undefined") {
|
||||
var listHeight = 0;
|
||||
listItems.each(function() {
|
||||
listHeight += this.offsetHeight;
|
||||
});
|
||||
var scrollbarsVisible = listHeight > options.scrollHeight;
|
||||
list.css('height', scrollbarsVisible ? options.scrollHeight : listHeight );
|
||||
if (!scrollbarsVisible) {
|
||||
// IE doesn't recalculate width when scrollbar disappears
|
||||
listItems.width( list.width() - parseInt(listItems.css("padding-left")) - parseInt(listItems.css("padding-right")) );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
selected: function() {
|
||||
var selected = listItems && listItems.filter("." + CLASSES.ACTIVE).removeClass(CLASSES.ACTIVE);
|
||||
return selected && selected.length && $.data(selected[0], "ac_data");
|
||||
},
|
||||
emptyList: function (){
|
||||
list && list.empty();
|
||||
},
|
||||
unbind: function() {
|
||||
element && element.remove();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
$.fn.selection = function(start, end) {
|
||||
if (start !== undefined) {
|
||||
return this.each(function() {
|
||||
if( this.createTextRange ){
|
||||
var selRange = this.createTextRange();
|
||||
if (end === undefined || start == end) {
|
||||
selRange.move("character", start);
|
||||
selRange.select();
|
||||
} else {
|
||||
selRange.collapse(true);
|
||||
selRange.moveStart("character", start);
|
||||
selRange.moveEnd("character", end);
|
||||
selRange.select();
|
||||
}
|
||||
} else if( this.setSelectionRange ){
|
||||
this.setSelectionRange(start, end);
|
||||
} else if( this.selectionStart ){
|
||||
this.selectionStart = start;
|
||||
this.selectionEnd = end;
|
||||
}
|
||||
});
|
||||
}
|
||||
var field = this[0];
|
||||
if ( field.createTextRange ) {
|
||||
var range = document.selection.createRange(),
|
||||
orig = field.value,
|
||||
teststring = "<->",
|
||||
textLength = range.text.length;
|
||||
range.text = teststring;
|
||||
var caretAt = field.value.indexOf(teststring);
|
||||
field.value = orig;
|
||||
this.selection(caretAt, caretAt + textLength);
|
||||
return {
|
||||
start: caretAt,
|
||||
end: caretAt + textLength
|
||||
}
|
||||
} else if( field.selectionStart !== undefined ){
|
||||
return {
|
||||
start: field.selectionStart,
|
||||
end: field.selectionEnd
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
462
public/javascripts/jquery.blockUI.js
Normal file
462
public/javascripts/jquery.blockUI.js
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
/*!
|
||||
* jQuery blockUI plugin
|
||||
* Version 2.25 (29-AUG-2009)
|
||||
* @requires jQuery v1.2.3 or later
|
||||
*
|
||||
* Examples at: http://malsup.com/jquery/block/
|
||||
* Copyright (c) 2007-2008 M. Alsup
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
* Thanks to Amir-Hossein Sobhi for some excellent contributions!
|
||||
*/
|
||||
|
||||
;(function($) {
|
||||
|
||||
if (/1\.(0|1|2)\.(0|1|2)/.test($.fn.jquery) || /^1.1/.test($.fn.jquery)) {
|
||||
alert('blockUI requires jQuery v1.2.3 or later! You are using v' + $.fn.jquery);
|
||||
return;
|
||||
}
|
||||
|
||||
$.fn._fadeIn = $.fn.fadeIn;
|
||||
|
||||
// this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
|
||||
// retarded userAgent strings on Vista)
|
||||
var mode = document.documentMode || 0;
|
||||
var setExpr = $.browser.msie && (($.browser.version < 8 && !mode) || mode < 8);
|
||||
var ie6 = $.browser.msie && /MSIE 6.0/.test(navigator.userAgent) && !mode;
|
||||
|
||||
// global $ methods for blocking/unblocking the entire page
|
||||
$.blockUI = function(opts) { install(window, opts); };
|
||||
$.unblockUI = function(opts) { remove(window, opts); };
|
||||
|
||||
// convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
|
||||
$.growlUI = function(title, message, timeout, onClose) {
|
||||
var $m = $('<div class="growlUI"></div>');
|
||||
if (title) $m.append('<h1>'+title+'</h1>');
|
||||
if (message) $m.append('<h2>'+message+'</h2>');
|
||||
if (timeout == undefined) timeout = 3000;
|
||||
$.blockUI({
|
||||
message: $m, fadeIn: 700, fadeOut: 1000, centerY: false,
|
||||
timeout: timeout, showOverlay: false,
|
||||
onUnblock: onClose,
|
||||
css: $.blockUI.defaults.growlCSS
|
||||
});
|
||||
};
|
||||
|
||||
// plugin method for blocking element content
|
||||
$.fn.block = function(opts) {
|
||||
return this.unblock({ fadeOut: 0 }).each(function() {
|
||||
if ($.css(this,'position') == 'static')
|
||||
this.style.position = 'relative';
|
||||
if ($.browser.msie)
|
||||
this.style.zoom = 1; // force 'hasLayout'
|
||||
install(this, opts);
|
||||
});
|
||||
};
|
||||
|
||||
// plugin method for unblocking element content
|
||||
$.fn.unblock = function(opts) {
|
||||
return this.each(function() {
|
||||
remove(this, opts);
|
||||
});
|
||||
};
|
||||
|
||||
$.blockUI.version = 2.25; // 2nd generation blocking at no extra cost!
|
||||
|
||||
// override these in your code to change the default behavior and style
|
||||
$.blockUI.defaults = {
|
||||
// message displayed when blocking (use null for no message)
|
||||
message: '<h1>Please wait...</h1>',
|
||||
|
||||
title: null, // title string; only used when theme == true
|
||||
draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
|
||||
|
||||
theme: false, // set to true to use with jQuery UI themes
|
||||
|
||||
// styles for the message when blocking; if you wish to disable
|
||||
// these and use an external stylesheet then do this in your code:
|
||||
// $.blockUI.defaults.css = {};
|
||||
css: {
|
||||
padding: 0,
|
||||
margin: 0,
|
||||
width: '30%',
|
||||
top: '40%',
|
||||
left: '35%',
|
||||
textAlign: 'center',
|
||||
color: '#000',
|
||||
border: '3px solid #aaa',
|
||||
backgroundColor:'#fff',
|
||||
cursor: 'wait'
|
||||
},
|
||||
|
||||
// minimal style set used when themes are used
|
||||
themedCSS: {
|
||||
width: '30%',
|
||||
top: '40%',
|
||||
left: '35%'
|
||||
},
|
||||
|
||||
// styles for the overlay
|
||||
overlayCSS: {
|
||||
opacity: 0.6,
|
||||
cursor: 'wait'
|
||||
},
|
||||
|
||||
// styles applied when using $.growlUI
|
||||
growlCSS: {
|
||||
width: '350px',
|
||||
top: '10px',
|
||||
left: '',
|
||||
right: '10px',
|
||||
border: 'none',
|
||||
padding: '5px',
|
||||
opacity: 0.6,
|
||||
cursor: null,
|
||||
color: '#fff',
|
||||
backgroundColor: '#000',
|
||||
'-webkit-border-radius': '10px',
|
||||
'-moz-border-radius': '10px'
|
||||
},
|
||||
|
||||
// IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
|
||||
// (hat tip to Jorge H. N. de Vasconcelos)
|
||||
iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
|
||||
|
||||
// force usage of iframe in non-IE browsers (handy for blocking applets)
|
||||
forceIframe: false,
|
||||
|
||||
// z-index for the blocking overlay
|
||||
baseZ: 1000,
|
||||
|
||||
// set these to true to have the message automatically centered
|
||||
centerX: true, // <-- only effects element blocking (page block controlled via css above)
|
||||
centerY: true,
|
||||
|
||||
// allow body element to be stetched in ie6; this makes blocking look better
|
||||
// on "short" pages. disable if you wish to prevent changes to the body height
|
||||
allowBodyStretch: true,
|
||||
|
||||
// enable if you want key and mouse events to be disabled for content that is blocked
|
||||
bindEvents: true,
|
||||
|
||||
// be default blockUI will supress tab navigation from leaving blocking content
|
||||
// (if bindEvents is true)
|
||||
constrainTabKey: true,
|
||||
|
||||
// fadeIn time in millis; set to 0 to disable fadeIn on block
|
||||
fadeIn: 200,
|
||||
|
||||
// fadeOut time in millis; set to 0 to disable fadeOut on unblock
|
||||
fadeOut: 400,
|
||||
|
||||
// time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
|
||||
timeout: 0,
|
||||
|
||||
// disable if you don't want to show the overlay
|
||||
showOverlay: true,
|
||||
|
||||
// if true, focus will be placed in the first available input field when
|
||||
// page blocking
|
||||
focusInput: true,
|
||||
|
||||
// suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
|
||||
applyPlatformOpacityRules: true,
|
||||
|
||||
// callback method invoked when unblocking has completed; the callback is
|
||||
// passed the element that has been unblocked (which is the window object for page
|
||||
// blocks) and the options that were passed to the unblock call:
|
||||
// onUnblock(element, options)
|
||||
onUnblock: null,
|
||||
|
||||
// don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
|
||||
quirksmodeOffsetHack: 4
|
||||
};
|
||||
|
||||
// private data and functions follow...
|
||||
|
||||
var pageBlock = null;
|
||||
var pageBlockEls = [];
|
||||
|
||||
function install(el, opts) {
|
||||
var full = (el == window);
|
||||
var msg = opts && opts.message !== undefined ? opts.message : undefined;
|
||||
opts = $.extend({}, $.blockUI.defaults, opts || {});
|
||||
opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
|
||||
var css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
|
||||
var themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
|
||||
msg = msg === undefined ? opts.message : msg;
|
||||
|
||||
// remove the current block (if there is one)
|
||||
if (full && pageBlock)
|
||||
remove(window, {fadeOut:0});
|
||||
|
||||
// if an existing element is being used as the blocking content then we capture
|
||||
// its current place in the DOM (and current display style) so we can restore
|
||||
// it when we unblock
|
||||
if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
|
||||
var node = msg.jquery ? msg[0] : msg;
|
||||
var data = {};
|
||||
$(el).data('blockUI.history', data);
|
||||
data.el = node;
|
||||
data.parent = node.parentNode;
|
||||
data.display = node.style.display;
|
||||
data.position = node.style.position;
|
||||
if (data.parent)
|
||||
data.parent.removeChild(node);
|
||||
}
|
||||
|
||||
var z = opts.baseZ;
|
||||
|
||||
// blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
|
||||
// layer1 is the iframe layer which is used to supress bleed through of underlying content
|
||||
// layer2 is the overlay layer which has opacity and a wait cursor (by default)
|
||||
// layer3 is the message content that is displayed while blocking
|
||||
|
||||
var lyr1 = ($.browser.msie || opts.forceIframe)
|
||||
? $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>')
|
||||
: $('<div class="blockUI" style="display:none"></div>');
|
||||
var lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
|
||||
|
||||
var lyr3;
|
||||
if (opts.theme && full) {
|
||||
var s = '<div class="blockUI blockMsg blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+z+';display:none;position:fixed">' +
|
||||
'<div class="ui-widget-header ui-dialog-titlebar blockTitle">'+(opts.title || ' ')+'</div>' +
|
||||
'<div class="ui-widget-content ui-dialog-content"></div>' +
|
||||
'</div>';
|
||||
lyr3 = $(s);
|
||||
}
|
||||
else {
|
||||
lyr3 = full ? $('<div class="blockUI blockMsg blockPage" style="z-index:'+z+';display:none;position:fixed"></div>')
|
||||
: $('<div class="blockUI blockMsg blockElement" style="z-index:'+z+';display:none;position:absolute"></div>');
|
||||
}
|
||||
|
||||
// if we have a message, style it
|
||||
if (msg) {
|
||||
if (opts.theme) {
|
||||
lyr3.css(themedCSS);
|
||||
lyr3.addClass('ui-widget-content');
|
||||
}
|
||||
else
|
||||
lyr3.css(css);
|
||||
}
|
||||
|
||||
// style the overlay
|
||||
if (!opts.applyPlatformOpacityRules || !($.browser.mozilla && /Linux/.test(navigator.platform)))
|
||||
lyr2.css(opts.overlayCSS);
|
||||
lyr2.css('position', full ? 'fixed' : 'absolute');
|
||||
|
||||
// make iframe layer transparent in IE
|
||||
if ($.browser.msie || opts.forceIframe)
|
||||
lyr1.css('opacity',0.0);
|
||||
|
||||
$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
|
||||
|
||||
if (opts.theme && opts.draggable && $.fn.draggable) {
|
||||
lyr3.draggable({
|
||||
handle: '.ui-dialog-titlebar',
|
||||
cancel: 'li'
|
||||
});
|
||||
}
|
||||
|
||||
// ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
|
||||
var expr = setExpr && (!$.boxModel || $('object,embed', full ? null : el).length > 0);
|
||||
if (ie6 || expr) {
|
||||
// give body 100% height
|
||||
if (full && opts.allowBodyStretch && $.boxModel)
|
||||
$('html,body').css('height','100%');
|
||||
|
||||
// fix ie6 issue when blocked element has a border width
|
||||
if ((ie6 || !$.boxModel) && !full) {
|
||||
var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
|
||||
var fixT = t ? '(0 - '+t+')' : 0;
|
||||
var fixL = l ? '(0 - '+l+')' : 0;
|
||||
}
|
||||
|
||||
// simulate fixed position
|
||||
$.each([lyr1,lyr2,lyr3], function(i,o) {
|
||||
var s = o[0].style;
|
||||
s.position = 'absolute';
|
||||
if (i < 2) {
|
||||
full ? s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"')
|
||||
: s.setExpression('height','this.parentNode.offsetHeight + "px"');
|
||||
full ? s.setExpression('width','jQuery.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"')
|
||||
: s.setExpression('width','this.parentNode.offsetWidth + "px"');
|
||||
if (fixL) s.setExpression('left', fixL);
|
||||
if (fixT) s.setExpression('top', fixT);
|
||||
}
|
||||
else if (opts.centerY) {
|
||||
if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
|
||||
s.marginTop = 0;
|
||||
}
|
||||
else if (!opts.centerY && full) {
|
||||
var top = (opts.css && opts.css.top) ? parseInt(opts.css.top) : 0;
|
||||
var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
|
||||
s.setExpression('top',expression);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// show the message
|
||||
if (msg) {
|
||||
if (opts.theme)
|
||||
lyr3.find('.ui-widget-content').append(msg);
|
||||
else
|
||||
lyr3.append(msg);
|
||||
if (msg.jquery || msg.nodeType)
|
||||
$(msg).show();
|
||||
}
|
||||
|
||||
if (($.browser.msie || opts.forceIframe) && opts.showOverlay)
|
||||
lyr1.show(); // opacity is zero
|
||||
if (opts.fadeIn) {
|
||||
if (opts.showOverlay)
|
||||
lyr2._fadeIn(opts.fadeIn);
|
||||
if (msg)
|
||||
lyr3.fadeIn(opts.fadeIn);
|
||||
}
|
||||
else {
|
||||
if (opts.showOverlay)
|
||||
lyr2.show();
|
||||
if (msg)
|
||||
lyr3.show();
|
||||
}
|
||||
|
||||
// bind key and mouse events
|
||||
bind(1, el, opts);
|
||||
|
||||
if (full) {
|
||||
pageBlock = lyr3[0];
|
||||
pageBlockEls = $(':input:enabled:visible',pageBlock);
|
||||
if (opts.focusInput)
|
||||
setTimeout(focus, 20);
|
||||
}
|
||||
else
|
||||
center(lyr3[0], opts.centerX, opts.centerY);
|
||||
|
||||
if (opts.timeout) {
|
||||
// auto-unblock
|
||||
var to = setTimeout(function() {
|
||||
full ? $.unblockUI(opts) : $(el).unblock(opts);
|
||||
}, opts.timeout);
|
||||
$(el).data('blockUI.timeout', to);
|
||||
}
|
||||
};
|
||||
|
||||
// remove the block
|
||||
function remove(el, opts) {
|
||||
var full = (el == window);
|
||||
var $el = $(el);
|
||||
var data = $el.data('blockUI.history');
|
||||
var to = $el.data('blockUI.timeout');
|
||||
if (to) {
|
||||
clearTimeout(to);
|
||||
$el.removeData('blockUI.timeout');
|
||||
}
|
||||
opts = $.extend({}, $.blockUI.defaults, opts || {});
|
||||
bind(0, el, opts); // unbind events
|
||||
|
||||
var els;
|
||||
if (full) // crazy selector to handle odd field errors in ie6/7
|
||||
els = $('body').children().filter('.blockUI').add('body > .blockUI');
|
||||
else
|
||||
els = $('.blockUI', el);
|
||||
|
||||
if (full)
|
||||
pageBlock = pageBlockEls = null;
|
||||
|
||||
if (opts.fadeOut) {
|
||||
els.fadeOut(opts.fadeOut);
|
||||
setTimeout(function() { reset(els,data,opts,el); }, opts.fadeOut);
|
||||
}
|
||||
else
|
||||
reset(els, data, opts, el);
|
||||
};
|
||||
|
||||
// move blocking element back into the DOM where it started
|
||||
function reset(els,data,opts,el) {
|
||||
els.each(function(i,o) {
|
||||
// remove via DOM calls so we don't lose event handlers
|
||||
if (this.parentNode)
|
||||
this.parentNode.removeChild(this);
|
||||
});
|
||||
|
||||
if (data && data.el) {
|
||||
data.el.style.display = data.display;
|
||||
data.el.style.position = data.position;
|
||||
if (data.parent)
|
||||
data.parent.appendChild(data.el);
|
||||
$(data.el).removeData('blockUI.history');
|
||||
}
|
||||
|
||||
if (typeof opts.onUnblock == 'function')
|
||||
opts.onUnblock(el,opts);
|
||||
};
|
||||
|
||||
// bind/unbind the handler
|
||||
function bind(b, el, opts) {
|
||||
var full = el == window, $el = $(el);
|
||||
|
||||
// don't bother unbinding if there is nothing to unbind
|
||||
if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
|
||||
return;
|
||||
if (!full)
|
||||
$el.data('blockUI.isBlocked', b);
|
||||
|
||||
// don't bind events when overlay is not in use or if bindEvents is false
|
||||
if (!opts.bindEvents || (b && !opts.showOverlay))
|
||||
return;
|
||||
|
||||
// bind anchors and inputs for mouse and key events
|
||||
var events = 'mousedown mouseup keydown keypress';
|
||||
b ? $(document).bind(events, opts, handler) : $(document).unbind(events, handler);
|
||||
|
||||
// former impl...
|
||||
// var $e = $('a,:input');
|
||||
// b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
|
||||
};
|
||||
|
||||
// event handler to suppress keyboard/mouse events when blocking
|
||||
function handler(e) {
|
||||
// allow tab navigation (conditionally)
|
||||
if (e.keyCode && e.keyCode == 9) {
|
||||
if (pageBlock && e.data.constrainTabKey) {
|
||||
var els = pageBlockEls;
|
||||
var fwd = !e.shiftKey && e.target == els[els.length-1];
|
||||
var back = e.shiftKey && e.target == els[0];
|
||||
if (fwd || back) {
|
||||
setTimeout(function(){focus(back)},10);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// allow events within the message content
|
||||
if ($(e.target).parents('div.blockMsg').length > 0)
|
||||
return true;
|
||||
|
||||
// allow events for content that is not being blocked
|
||||
return $(e.target).parents().children().filter('div.blockUI').length == 0;
|
||||
};
|
||||
|
||||
function focus(back) {
|
||||
if (!pageBlockEls)
|
||||
return;
|
||||
var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
|
||||
if (e)
|
||||
e.focus();
|
||||
};
|
||||
|
||||
function center(el, x, y) {
|
||||
var p = el.parentNode, s = el.style;
|
||||
var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
|
||||
var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
|
||||
if (x) s.left = l > 0 ? (l+'px') : '0';
|
||||
if (y) s.top = t > 0 ? (t+'px') : '0';
|
||||
};
|
||||
|
||||
function sz(el, p) {
|
||||
return parseInt($.css(el,p))||0;
|
||||
};
|
||||
|
||||
})(jQuery);
|
||||
96
public/javascripts/jquery.cookie.js
Normal file
96
public/javascripts/jquery.cookie.js
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
/**
|
||||
* Cookie plugin
|
||||
*
|
||||
* Copyright (c) 2006 Klaus Hartl (stilbuero.de)
|
||||
* Dual licensed under the MIT and GPL licenses:
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
* http://www.gnu.org/licenses/gpl.html
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Create a cookie with the given name and value and other optional parameters.
|
||||
*
|
||||
* @example $.cookie('the_cookie', 'the_value');
|
||||
* @desc Set the value of a cookie.
|
||||
* @example $.cookie('the_cookie', 'the_value', { expires: 7, path: '/', domain: 'jquery.com', secure: true });
|
||||
* @desc Create a cookie with all available options.
|
||||
* @example $.cookie('the_cookie', 'the_value');
|
||||
* @desc Create a session cookie.
|
||||
* @example $.cookie('the_cookie', null);
|
||||
* @desc Delete a cookie by passing null as value. Keep in mind that you have to use the same path and domain
|
||||
* used when the cookie was set.
|
||||
*
|
||||
* @param String name The name of the cookie.
|
||||
* @param String value The value of the cookie.
|
||||
* @param Object options An object literal containing key/value pairs to provide optional cookie attributes.
|
||||
* @option Number|Date expires Either an integer specifying the expiration date from now on in days or a Date object.
|
||||
* If a negative value is specified (e.g. a date in the past), the cookie will be deleted.
|
||||
* If set to null or omitted, the cookie will be a session cookie and will not be retained
|
||||
* when the the browser exits.
|
||||
* @option String path The value of the path atribute of the cookie (default: path of page that created the cookie).
|
||||
* @option String domain The value of the domain attribute of the cookie (default: domain of page that created the cookie).
|
||||
* @option Boolean secure If true, the secure attribute of the cookie will be set and the cookie transmission will
|
||||
* require a secure protocol (like HTTPS).
|
||||
* @type undefined
|
||||
*
|
||||
* @name $.cookie
|
||||
* @cat Plugins/Cookie
|
||||
* @author Klaus Hartl/klaus.hartl@stilbuero.de
|
||||
*/
|
||||
|
||||
/**
|
||||
* Get the value of a cookie with the given name.
|
||||
*
|
||||
* @example $.cookie('the_cookie');
|
||||
* @desc Get the value of a cookie.
|
||||
*
|
||||
* @param String name The name of the cookie.
|
||||
* @return The value of the cookie.
|
||||
* @type String
|
||||
*
|
||||
* @name $.cookie
|
||||
* @cat Plugins/Cookie
|
||||
* @author Klaus Hartl/klaus.hartl@stilbuero.de
|
||||
*/
|
||||
jQuery.cookie = function(name, value, options) {
|
||||
if (typeof value != 'undefined') { // name and value given, set cookie
|
||||
options = options || {};
|
||||
if (value === null) {
|
||||
value = '';
|
||||
options.expires = -1;
|
||||
}
|
||||
var expires = '';
|
||||
if (options.expires && (typeof options.expires == 'number' || options.expires.toUTCString)) {
|
||||
var date;
|
||||
if (typeof options.expires == 'number') {
|
||||
date = new Date();
|
||||
date.setTime(date.getTime() + (options.expires * 24 * 60 * 60 * 1000));
|
||||
} else {
|
||||
date = options.expires;
|
||||
}
|
||||
expires = '; expires=' + date.toUTCString(); // use expires attribute, max-age is not supported by IE
|
||||
}
|
||||
// CAUTION: Needed to parenthesize options.path and options.domain
|
||||
// in the following expressions, otherwise they evaluate to undefined
|
||||
// in the packed version for some reason...
|
||||
var path = options.path ? '; path=' + (options.path) : '';
|
||||
var domain = options.domain ? '; domain=' + (options.domain) : '';
|
||||
var secure = options.secure ? '; secure' : '';
|
||||
document.cookie = [name, '=', encodeURIComponent(value), expires, path, domain, secure].join('');
|
||||
} else { // only name given, get cookie
|
||||
var cookieValue = null;
|
||||
if (document.cookie && document.cookie != '') {
|
||||
var cookies = document.cookie.split(';');
|
||||
for (var i = 0; i < cookies.length; i++) {
|
||||
var cookie = jQuery.trim(cookies[i]);
|
||||
// Does this cookie string begin with the name we want?
|
||||
if (cookie.substring(0, name.length + 1) == (name + '=')) {
|
||||
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return cookieValue;
|
||||
}
|
||||
};
|
||||
38
public/javascripts/jquery.jeditable.js
Normal file
38
public/javascripts/jquery.jeditable.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
|
||||
(function($){$.fn.editable=function(target,options){if('disable'==target){$(this).data('disabled.editable',true);return;}
|
||||
if('enable'==target){$(this).data('disabled.editable',false);return;}
|
||||
if('destroy'==target){$(this).unbind($(this).data('event.editable')).removeData('disabled.editable').removeData('event.editable');return;}
|
||||
var settings=$.extend({},$.fn.editable.defaults,{target:target},options);var plugin=$.editable.types[settings.type].plugin||function(){};var submit=$.editable.types[settings.type].submit||function(){};var buttons=$.editable.types[settings.type].buttons||$.editable.types['defaults'].buttons;var content=$.editable.types[settings.type].content||$.editable.types['defaults'].content;var element=$.editable.types[settings.type].element||$.editable.types['defaults'].element;var reset=$.editable.types[settings.type].reset||$.editable.types['defaults'].reset;var callback=settings.callback||function(){};var onedit=settings.onedit||function(){};var onsubmit=settings.onsubmit||function(){};var onreset=settings.onreset||function(){};var onerror=settings.onerror||reset;if(settings.tooltip){$(this).attr('title',settings.tooltip);}
|
||||
settings.autowidth='auto'==settings.width;settings.autoheight='auto'==settings.height;return this.each(function(){var self=this;var savedwidth=$(self).width();var savedheight=$(self).height();$(this).data('event.editable',settings.event);if(!$.trim($(this).html())){$(this).html(settings.placeholder);}
|
||||
$(this).bind(settings.event,function(e){if(true===$(this).data('disabled.editable')){return;}
|
||||
if(self.editing){return;}
|
||||
if(false===onedit.apply(this,[settings,self])){return;}
|
||||
e.preventDefault();e.stopPropagation();if(settings.tooltip){$(self).removeAttr('title');}
|
||||
if(0==$(self).width()){settings.width=savedwidth;settings.height=savedheight;}else{if(settings.width!='none'){settings.width=settings.autowidth?$(self).width():settings.width;}
|
||||
if(settings.height!='none'){settings.height=settings.autoheight?$(self).height():settings.height;}}
|
||||
if($(this).html().toLowerCase().replace(/(;|")/g,'')==settings.placeholder.toLowerCase().replace(/(;|")/g,'')){$(this).html('');}
|
||||
self.editing=true;self.revert=$(self).html();$(self).html('');var form=$('<form />');if(settings.cssclass){if('inherit'==settings.cssclass){form.attr('class',$(self).attr('class'));}else{form.attr('class',settings.cssclass);}}
|
||||
if(settings.style){if('inherit'==settings.style){form.attr('style',$(self).attr('style'));form.css('display',$(self).css('display'));}else{form.attr('style',settings.style);}}
|
||||
var input=element.apply(form,[settings,self]);var input_content;if(settings.loadurl){var t=setTimeout(function(){input.disabled=true;content.apply(form,[settings.loadtext,settings,self]);},100);var loaddata={};loaddata[settings.id]=self.id;if($.isFunction(settings.loaddata)){$.extend(loaddata,settings.loaddata.apply(self,[self.revert,settings]));}else{$.extend(loaddata,settings.loaddata);}
|
||||
$.ajax({type:settings.loadtype,url:settings.loadurl,data:loaddata,async:false,success:function(result){window.clearTimeout(t);input_content=result;input.disabled=false;}});}else if(settings.data){input_content=settings.data;if($.isFunction(settings.data)){input_content=settings.data.apply(self,[self.revert,settings]);}}else{input_content=self.revert;}
|
||||
content.apply(form,[input_content,settings,self]);input.attr('name',settings.name);buttons.apply(form,[settings,self]);$(self).append(form);plugin.apply(form,[settings,self]);$(':input:visible:enabled:first',form).focus();if(settings.select){input.select();}
|
||||
input.keydown(function(e){if(e.keyCode==27){e.preventDefault();reset.apply(form,[settings,self]);}});var t;if('cancel'==settings.onblur){input.blur(function(e){t=setTimeout(function(){reset.apply(form,[settings,self]);},500);});}else if('submit'==settings.onblur){input.blur(function(e){t=setTimeout(function(){form.submit();},200);});}else if($.isFunction(settings.onblur)){input.blur(function(e){settings.onblur.apply(self,[input.val(),settings]);});}else{input.blur(function(e){});}
|
||||
form.submit(function(e){if(t){clearTimeout(t);}
|
||||
e.preventDefault();if(false!==onsubmit.apply(form,[settings,self])){if(false!==submit.apply(form,[settings,self])){if($.isFunction(settings.target)){var str=settings.target.apply(self,[input.val(),settings]);$(self).html(str);self.editing=false;callback.apply(self,[self.innerHTML,settings]);if(!$.trim($(self).html())){$(self).html(settings.placeholder);}}else{var submitdata={};submitdata[settings.name]=input.val();submitdata[settings.id]=self.id;if($.isFunction(settings.submitdata)){$.extend(submitdata,settings.submitdata.apply(self,[self.revert,settings]));}else{$.extend(submitdata,settings.submitdata);}
|
||||
if('PUT'==settings.method){submitdata['_method']='put';}
|
||||
$(self).html(settings.indicator);var ajaxoptions={type:'POST',data:submitdata,dataType:'html',url:settings.target,success:function(result,status){if(ajaxoptions.dataType=='html'){$(self).html(result);}
|
||||
self.editing=false;callback.apply(self,[result,settings]);if(!$.trim($(self).html())){$(self).html(settings.placeholder);}},error:function(xhr,status,error){onerror.apply(form,[settings,self,xhr]);}};$.extend(ajaxoptions,settings.ajaxoptions);$.ajax(ajaxoptions);}}}
|
||||
$(self).attr('title',settings.tooltip);return false;});});this.reset=function(form){if(this.editing){if(false!==onreset.apply(form,[settings,self])){$(self).html(self.revert);self.editing=false;if(!$.trim($(self).html())){$(self).html(settings.placeholder);}
|
||||
if(settings.tooltip){$(self).attr('title',settings.tooltip);}}}};});};$.editable={types:{defaults:{element:function(settings,original){var input=$('<input type="hidden"></input>');$(this).append(input);return(input);},content:function(string,settings,original){$(':input:first',this).val(string);},reset:function(settings,original){original.reset(this);},buttons:function(settings,original){var form=this;if(settings.submit){if(settings.submit.match(/>$/)){var submit=$(settings.submit).click(function(){if(submit.attr("type")!="submit"){form.submit();}});}else{var submit=$('<button type="submit" />');submit.html(settings.submit);}
|
||||
$(this).append(submit);}
|
||||
if(settings.cancel){if(settings.cancel.match(/>$/)){var cancel=$(settings.cancel);}else{var cancel=$('<button type="cancel" />');cancel.html(settings.cancel);}
|
||||
$(this).append(cancel);$(cancel).click(function(event){if($.isFunction($.editable.types[settings.type].reset)){var reset=$.editable.types[settings.type].reset;}else{var reset=$.editable.types['defaults'].reset;}
|
||||
reset.apply(form,[settings,original]);return false;});}}},text:{element:function(settings,original){var input=$('<input />');if(settings.width!='none'){input.width(settings.width);}
|
||||
if(settings.height!='none'){input.height(settings.height);}
|
||||
input.attr('autocomplete','off');$(this).append(input);return(input);}},textarea:{element:function(settings,original){var textarea=$('<textarea />');if(settings.rows){textarea.attr('rows',settings.rows);}else if(settings.height!="none"){textarea.height(settings.height);}
|
||||
if(settings.cols){textarea.attr('cols',settings.cols);}else if(settings.width!="none"){textarea.width(settings.width);}
|
||||
$(this).append(textarea);return(textarea);}},select:{element:function(settings,original){var select=$('<select />');$(this).append(select);return(select);},content:function(data,settings,original){if(String==data.constructor){eval('var json = '+data);}else{var json=data;}
|
||||
for(var key in json){if(!json.hasOwnProperty(key)){continue;}
|
||||
if('selected'==key){continue;}
|
||||
var option=$('<option />').val(key).append(json[key]);$('select',this).append(option);}
|
||||
$('select',this).children().each(function(){if($(this).val()==json['selected']||$(this).text()==$.trim(original.revert)){$(this).attr('selected','selected');}});}}},addInputType:function(name,input){$.editable.types[name]=input;}};$.fn.editable.defaults={name:'value',id:'id',type:'text',width:'auto',height:'auto',event:'click.editable',onblur:'cancel',loadtype:'GET',loadtext:'Loading...',placeholder:'Click to edit',loaddata:{},submitdata:{},ajaxoptions:{}};})(jQuery);
|
||||
19
public/javascripts/jquery.js
vendored
Normal file
19
public/javascripts/jquery.js
vendored
Normal file
File diff suppressed because one or more lines are too long
1
public/javascripts/jrails.js
Normal file
1
public/javascripts/jrails.js
Normal file
|
|
@ -0,0 +1 @@
|
|||
(function($){$.ajaxSettings.accepts._default="text/javascript, text/html, application/xml, text/xml, */*"})(jQuery);(function($){$.fn.reset=function(){return this.each(function(){if(typeof this.reset=="function"||(typeof this.reset=="object"&&!this.reset.nodeType)){this.reset()}})};$.fn.enable=function(){return this.each(function(){this.disabled=false})};$.fn.disable=function(){return this.each(function(){this.disabled=true})}})(jQuery);(function($){$.extend({fieldEvent:function(el,obs){var field=el[0]||el,e="change";if(field.type=="radio"||field.type=="checkbox"){e="click"}else{if(obs&&(field.type=="text"||field.type=="textarea"||field.type=="password")){e="keyup"}}return e}});$.fn.extend({delayedObserver:function(delay,callback){var el=$(this);if(typeof window.delayedObserverStack=="undefined"){window.delayedObserverStack=[]}if(typeof window.delayedObserverCallback=="undefined"){window.delayedObserverCallback=function(stackPos){var observed=window.delayedObserverStack[stackPos];if(observed.timer){clearTimeout(observed.timer)}observed.timer=setTimeout(function(){observed.timer=null;observed.callback(observed.obj,observed.obj.formVal())},observed.delay*1000);observed.oldVal=observed.obj.formVal()}}window.delayedObserverStack.push({obj:el,timer:null,delay:delay,oldVal:el.formVal(),callback:callback});var stackPos=window.delayedObserverStack.length-1;if(el[0].tagName=="FORM"){$(":input",el).each(function(){var field=$(this);field.bind($.fieldEvent(field,delay),function(){var observed=window.delayedObserverStack[stackPos];if(observed.obj.formVal()==observed.oldVal){return}else{window.delayedObserverCallback(stackPos)}})})}else{el.bind($.fieldEvent(el,delay),function(){var observed=window.delayedObserverStack[stackPos];if(observed.obj.formVal()==observed.oldVal){return}else{window.delayedObserverCallback(stackPos)}})}},formVal:function(){var el=this[0];if(el.tagName=="FORM"){return this.serialize()}if(el.type=="checkbox"||el.type=="radio"){return this.filter("input:checked").val()||""}else{return this.val()}}})})(jQuery);(function($){$.fn.extend({visualEffect:function(o,options){if(options){speed=options.duration*1000}else{speed=null}e=o.replace(/\_(.)/g,function(m,l){return l.toUpperCase()});return eval("$(this)."+e+"("+speed+")")},appear:function(speed,callback){return this.fadeIn(speed,callback)},blindDown:function(speed,callback){return this.show("blind",{direction:"vertical"},speed,callback)},blindUp:function(speed,callback){return this.hide("blind",{direction:"vertical"},speed,callback)},blindRight:function(speed,callback){return this.show("blind",{direction:"horizontal"},speed,callback)},blindLeft:function(speed,callback){this.hide("blind",{direction:"horizontal"},speed,callback);return this},dropOut:function(speed,callback){return this.hide("drop",{direction:"down"},speed,callback)},dropIn:function(speed,callback){return this.show("drop",{direction:"up"},speed,callback)},fade:function(speed,callback){return this.fadeOut(speed,callback)},fadeToggle:function(speed,callback){return this.animate({opacity:"toggle"},speed,callback)},fold:function(speed,callback){return this.hide("fold",{},speed,callback)},foldOut:function(speed,callback){return this.show("fold",{},speed,callback)},grow:function(speed,callback){return this.show("scale",{},speed,callback)},highlight:function(speed,callback){return this.show("highlight",{},speed,callback)},puff:function(speed,callback){return this.hide("puff",{},speed,callback)},pulsate:function(speed,callback){return this.show("pulsate",{},speed,callback)},shake:function(speed,callback){return this.show("shake",{},speed,callback)},shrink:function(speed,callback){return this.hide("scale",{},speed,callback)},squish:function(speed,callback){return this.hide("scale",{origin:["top","left"]},speed,callback)},slideUp:function(speed,callback){return this.hide("slide",{direction:"up"},speed,callback)},slideDown:function(speed,callback){return this.show("slide",{direction:"up"},speed,callback)},switchOff:function(speed,callback){return this.hide("clip",{},speed,callback)},switchOn:function(speed,callback){return this.show("clip",{},speed,callback)}})})(jQuery);
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue