Merged mobile_controller into the todos_controller. The lightweight mobile HTML is

arguably just another representation of the same resources, so it seems to fit
the RESTful Rails paradigm to use an extension (.m) to switch on in the
respond_to stanza.

I needed some hackery to make this work. See my note in todos_controller for
a full explanation.
 
I also added a route to get to the mobile view by using 'domain.com/m'

Created some selenium tests for the mobile view, too. 

In optimizing the data access for the mobile view, I ran into "a bug in rails
pagination":http://dev.rubyonrails.org/ticket/7885" and integrated a nice
pagination plugin from the Err the Blog guys
("will_paginate":http://errtheblog.com/post/929) to work around the issue.

NOTE that this changeset includes a new line in environment.rb.tmpl (at
the bottom). Be sure to copy this into your environment.rb file. 

These changes fix #489 (cannot edit action using mobile interface).
Thanks for the bug report, lrbalt!

In the name of consistency, I made the argument to the block for all
respond_to calls 'format' (instead of the formerly cool 'wants').

Lastly, I added a link to the project's new contribute page to the footer of
the main web UI. Help us join the Mac on Intel world. :-)



git-svn-id: http://www.rousette.org.uk/svn/tracks-repos/trunk@517 a4c988fc-2ded-0310-b66e-134b36920a42
This commit is contained in:
lukemelia 2007-04-02 04:18:19 +00:00
parent f3f881e47e
commit ba0b52ff1a
32 changed files with 445 additions and 258 deletions

View file

@ -28,7 +28,7 @@ class ApplicationController < ActionController::Base
def set_charset def set_charset
headers["Content-Type"] ||= "text/html; charset=UTF-8" headers["Content-Type"] ||= "text/html; charset=UTF-8"
end end
def set_session_expiration def set_session_expiration
# http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions # http://wiki.rubyonrails.com/rails/show/HowtoChangeSessionOptions
unless session == nil unless session == nil
@ -56,13 +56,13 @@ class ApplicationController < ActionController::Base
def rescue_action(exception) def rescue_action(exception)
log_error(exception) if logger log_error(exception) if logger
respond_to do |wants| respond_to do |format|
wants.html do format.html do
notify :warning, "An error occurred on the server." notify :warning, "An error occurred on the server."
render :action => "index" render :action => "index"
end end
wants.js { render :action => 'error' } format.js { render :action => 'error' }
wants.xml { render :text => 'An error occurred on the server.' + $! } format.xml { render :text => 'An error occurred on the server.' + $! }
end end
end end

View file

@ -41,9 +41,9 @@ class ContextsController < ApplicationController
end end
@saved = @context.save @saved = @context.save
@context_not_done_counts = { @context.id => 0 } @context_not_done_counts = { @context.id => 0 }
respond_to do |wants| respond_to do |format|
wants.js format.js
wants.xml do format.xml do
if @context.new_record? && params_are_invalid if @context.new_record? && params_are_invalid
render_failure "Expected post format is valid xml like so: <request><context><name>context name</name></context></request>." render_failure "Expected post format is valid xml like so: <request><context><name>context name</name></context></request>."
elsif @context.new_record? elsif @context.new_record?

View file

@ -1,89 +0,0 @@
class MobileController < ApplicationController
layout 'mobile'
before_filter :init, :except => :update
# Plain list of all next actions, paginated 6 per page
# Sorted by due date, then creation date
#
def index
@page_title = @desc = "All actions"
@todos_pages, @todos = paginate( :todos, :order => 'due IS NULL, due ASC, created_at ASC',
:conditions => ['user_id = ? and state = ?', @user.id, "active"],
:per_page => 6 )
@count = @all_todos.reject { |x| !x.active? || x.context.hide? }.size
end
def detail
@item = check_user_return_item
@place = @item.context.id
end
def update
if params[:id]
@item = check_user_return_item
@item.update_attributes params[:todo]
if params[:todo][:state] == "1"
@item.state = "completed"
else
@item.state = "active"
end
else
params[:todo][:user_id] = @user.id
@item = Todo.new(params[:todo]) if params[:todo]
end
if @item.save
redirect_to :action => 'index'
else
self.init
if params[:id]
render :partial => 'mobile_edit'
else
render :action => 'show_add_form'
end
end
end
def show_add_form
# Just render the view
end
def filter
@type = params[:type]
case params[:type]
when 'context'
@context = Context.find( params[:context][:id] )
@page_title = @desc = "#{@context.name}"
@todos = Todo.find( :all, :order => 'due IS NULL, due ASC, created_at ASC',
:conditions => ['user_id = ? and state = ? and context_id = ?', @user.id, "active", @context.id] )
@count = @all_todos.reject { |x| x.completed? || x.context_id != @context.id }.size
when 'project'
@project = Project.find( params[:project][:id] )
@page_title = @desc = "#{@project.name}"
@todos = Todo.find( :all, :order => 'due IS NULL, due ASC, created_at ASC',
:conditions => ['user_id = ? and state = ? and project_id = ?', @user.id, "active", @project.id] )
@count = @all_todos.reject { |x| x.completed? || x.project_id != @project.id }.size
end
end
protected
def check_user_return_item
item = Todo.find( params['id'] )
if @user == item.user
return item
else
notify :warning, "Item and session user mis-match: #{item.user.name} and #{@user.name}!"
render_text ""
end
end
def init
@contexts = @user.contexts.find(:all, :order => 'position ASC')
@projects = @user.projects.find_in_state(:all, :active, :order => 'position ASC')
@all_todos = @user.todos.find(:all, :conditions => ['state = ? or state = ?', "active", "completed"])
end
end

View file

@ -3,9 +3,9 @@ class NotesController < ApplicationController
def index def index
@all_notes = @user.notes @all_notes = @user.notes
@page_title = "TRACKS::All notes" @page_title = "TRACKS::All notes"
respond_to do |wants| respond_to do |format|
wants.html format.html
wants.xml { render :xml => @all_notes.to_xml( :except => :user_id ) } format.xml { render :xml => @all_notes.to_xml( :except => :user_id ) }
end end
end end

View file

@ -53,9 +53,9 @@ class ProjectsController < ApplicationController
@project_not_done_counts = { @project.id => 0 } @project_not_done_counts = { @project.id => 0 }
@active_projects_count = @user.projects.count(:conditions => "state = 'active'") @active_projects_count = @user.projects.count(:conditions => "state = 'active'")
@contexts = @user.contexts @contexts = @user.contexts
respond_to do |wants| respond_to do |format|
wants.js format.js
wants.xml do format.xml do
if @project.new_record? && params_are_invalid if @project.new_record? && params_are_invalid
render_failure "Expected post format is valid xml like so: <request><project><name>project name</name></project></request>." render_failure "Expected post format is valid xml like so: <request><project><name>project name</name></project></request>."
elsif @project.new_record? elsif @project.new_record?

View file

@ -2,13 +2,17 @@ class TodosController < ApplicationController
helper :todos helper :todos
append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred ]
append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :show, :update, :destroy ]
skip_before_filter :login_required, :only => [:index] skip_before_filter :login_required, :only => [:index]
prepend_before_filter :login_or_feed_token_required, :only => [:index] prepend_before_filter :login_or_feed_token_required, :only => [:index]
append_before_filter :init, :except => [ :destroy, :completed, :completed_archive, :check_deferred ]
append_before_filter :get_todo_from_params, :only => [ :edit, :toggle_check, :show, :update, :destroy ]
prepend_before_filter :enable_mobile_content_negotiation
after_filter :restore_content_type_for_mobile
session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) } session :off, :only => :index, :if => Proc.new { |req| is_feed_request(req) }
layout 'standard' layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
def index def index
@projects = @user.projects.find(:all, :include => [ :todos ]) @projects = @user.projects.find(:all, :include => [ :todos ])
@ -17,20 +21,29 @@ class TodosController < ApplicationController
@contexts_to_show = @contexts.reject {|x| x.hide? } @contexts_to_show = @contexts.reject {|x| x.hide? }
respond_to do |format| respond_to do |format|
format.html &render_todos_html format.html &render_todos_html
format.xml { render :action => 'list.rxml', :layout => false } format.m &render_todos_mobile
format.xml { render :action => 'list.rxml', :layout => false }
format.rss &render_rss_feed format.rss &render_rss_feed
format.atom &render_atom_feed format.atom &render_atom_feed
format.text &render_text_feed format.text &render_text_feed
format.ics &render_ical_feed format.ics &render_ical_feed
end end
end end
def new
@projects = @user.projects.find(:all)
@contexts = @user.contexts.find(:all)
respond_to do |format|
format.m { render :action => "new_mobile" }
end
end
def create def create
@todo = @user.todos.build @todo = @user.todos.build
p = params['request'] || params p = params['request'] || params
if p['todo']['show_from'] if p['todo']['show_from'] && !mobile?
p['todo']['show_from'] = parse_date_per_user_prefs(p['todo']['show_from']) p['todo']['show_from'] = parse_date_per_user_prefs(p['todo']['show_from'])
end end
@ -60,34 +73,46 @@ class TodosController < ApplicationController
end end
if @todo.due? if @todo.due?
@todo.due = parse_date_per_user_prefs(p['todo']['due']) @todo.due = parse_date_per_user_prefs(p['todo']['due']) unless mobile?
else else
@todo.due = "" @todo.due = ""
end end
@saved = @todo.save @saved = @todo.save
if @saved if @saved
@todo.tag_with(params[:tag_list],@user) @todo.tag_with(params[:tag_list],@user) if params[:tag_list]
@todo.reload @todo.reload
end end
respond_to do |wants| respond_to do |format|
wants.html { redirect_to :action => "index" } format.html { redirect_to :action => "index" }
wants.js do format.m do
if @saved if @saved
determine_down_count redirect_to :action => "index", :format => :m
else
render :action => "new", :format => :m
end end
end
format.js do
determine_down_count if @saved
render :action => 'create' render :action => 'create'
end end
wants.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) } format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
end end
end end
def edit def edit
@projects = @user.projects.find(:all)
@contexts = @user.contexts.find(:all)
end end
def show def show
respond_to do |format| respond_to do |format|
format.m do
@projects = @user.projects.find(:all)
@contexts = @user.contexts.find(:all)
render :action => 'show_mobile'
end
format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) } format.xml { render :xml => @todo.to_xml( :root => 'todo', :except => :user_id ) }
end end
end end
@ -120,7 +145,7 @@ class TodosController < ApplicationController
end end
def update def update
@todo.tag_with(params[:tag_list],@user) @todo.tag_with(params[:tag_list],@user) if params[:tag_list]
@original_item_context_id = @todo.context_id @original_item_context_id = @todo.context_id
@original_item_project_id = @todo.project_id @original_item_project_id = @todo.project_id
@original_item_was_deferred = @todo.deferred? @original_item_was_deferred = @todo.deferred?
@ -160,6 +185,10 @@ class TodosController < ApplicationController
params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from']) params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from'])
end end
if params['done'] == '1' && !@todo.completed?
@todo.complete!
end
@saved = @todo.update_attributes params["todo"] @saved = @todo.update_attributes params["todo"]
@context_changed = @original_item_context_id != @todo.context_id @context_changed = @original_item_context_id != @todo.context_id
@todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active? @todo_was_activated_from_deferred_state = @original_item_was_deferred && @todo.active?
@ -167,6 +196,16 @@ class TodosController < ApplicationController
@project_changed = @original_item_project_id != @todo.project_id @project_changed = @original_item_project_id != @todo.project_id
if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = @user.projects.find(@original_item_project_id).not_done_todo_count; end if (@project_changed && !@original_item_project_id.nil?) then @remaining_undone_in_project = @user.projects.find(@original_item_project_id).not_done_todo_count; end
determine_down_count determine_down_count
respond_to do |format|
format.js
format.m do
if @saved
redirect_to formatted_todos_path(:m)
else
render :action => "edit", :format => :m
end
end
end
end end
def destroy def destroy
@ -175,9 +214,9 @@ class TodosController < ApplicationController
@project_id = @todo.project_id @project_id = @todo.project_id
@saved = @todo.destroy @saved = @todo.destroy
respond_to do |wants| respond_to do |format|
wants.html do format.html do
if @saved if @saved
notify :notice, "Successfully deleted next action", 2.0 notify :notice, "Successfully deleted next action", 2.0
redirect_to :action => 'index' redirect_to :action => 'index'
@ -185,9 +224,9 @@ class TodosController < ApplicationController
notify :error, "Failed to delete the action", 2.0 notify :error, "Failed to delete the action", 2.0
redirect_to :action => 'index' redirect_to :action => 'index'
end end
end end
wants.js do format.js do
if @saved if @saved
determine_down_count determine_down_count
source_view do |from| source_view do |from|
@ -199,7 +238,7 @@ class TodosController < ApplicationController
render render
end end
wants.xml { render :text => '200 OK. Action deleted.', :status => 200 } format.xml { render :text => '200 OK. Action deleted.', :status => 200 }
end end
end end
@ -236,6 +275,16 @@ class TodosController < ApplicationController
end end
end end
def filter_to_context
context = @user.contexts.find(params['context']['id'])
redirect_to formatted_context_todos_path(context, :m)
end
def filter_to_project
project = @user.projects.find(params['project']['id'])
redirect_to formatted_project_todos_path(project, :m)
end
# /todos/tag/[tag_name] shows all the actions tagged with tag_name # /todos/tag/[tag_name] shows all the actions tagged with tag_name
# #
def tag def tag
@ -266,15 +315,47 @@ class TodosController < ApplicationController
end end
private # 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 makes some sense that tne pages of that
# mobile version are simply alternate representations of the same
# Todo resources. The implementation goal was to treat mobile
# as another format and be able to use respond_to to render both
# versions. Unfortunately, I ran into a lot of trouble simply
# registering a new mime type 'text/html' with format :m because
# :html already is linked to that mime type and the new
# registration was forcing all html requests to be rendered in
# the mobile view. The before_filter and after_filter hackery
# below accomplishs that implementation goal by using a 'fake'
# mime type during the processing and then setting it to
# 'text/html' in an 'after_filter' -LKM 2007-04-01
def mobile?
return params[:format] == 'm' || response.content_type == MOBILE_CONTENT_TYPE
end
def enable_mobile_content_negotiation
if mobile?
request.accepts.unshift(Mime::Type::lookup(MOBILE_CONTENT_TYPE))
end
end
def restore_content_type_for_mobile
if mobile?
response.content_type = 'text/html'
end
end
private
def get_todo_from_params def get_todo_from_params
@todo = @user.todos.find(params['id']) @todo = @user.todos.find(params['id'])
end end
def init def init
@source_view = params['_source_view'] || 'todo' @source_view = params['_source_view'] || 'todo'
init_data_for_sidebar init_data_for_sidebar unless mobile?
init_todos init_todos
end end
@ -321,13 +402,13 @@ class TodosController < ApplicationController
def with_parent_resource_scope(&block) def with_parent_resource_scope(&block)
if (params[:context_id]) if (params[:context_id])
context = @user.contexts.find_by_params(params) @context = @user.contexts.find_by_params(params)
Todo.with_scope :find => {:conditions => ['todos.context_id = ?', context.id]} do Todo.with_scope :find => {:conditions => ['todos.context_id = ?', @context.id]} do
yield yield
end end
elsif (params[:project_id]) elsif (params[:project_id])
project = @user.projects.find_by_params(params) @project = @user.projects.find_by_params(params)
Todo.with_scope :find => {:conditions => ['todos.project_id = ?', project.id]} do Todo.with_scope :find => {:conditions => ['todos.project_id = ?', @project.id]} do
yield yield
end end
else else
@ -350,12 +431,26 @@ class TodosController < ApplicationController
with_feed_query_scope do with_feed_query_scope do
with_parent_resource_scope do with_parent_resource_scope do
with_limit_scope do with_limit_scope do
if mobile?
@todos, @page = @user.todos.paginate(:all,
:conditions => ['state = ?', 'active' ], :include => [:context],
:order => 'due IS NULL, due ASC, todos.created_at ASC',
:page => params[:page], :per_page => 6)
@pagination_params = { :format => :m }
@pagination_params[:context_id] = @context.to_param if @context
@pagination_params[:project_id] = @project.to_param if @project
else
# Exclude hidden projects from count on home page
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ])
# Exclude hidden projects from count on home page # Exclude hidden projects from the home page
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ]) @not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ])
# Exclude hidden projects from the home page end
@not_done_todos = @user.todos.find(:all, :conditions => ['todos.state = ?', 'active'], :order => "todos.due IS NULL, todos.due ASC, todos.created_at ASC", :include => [ :project, :context, :tags ])
end end
end end
@ -415,6 +510,23 @@ class TodosController < ApplicationController
render render
end end
end end
def render_todos_mobile
lambda do
@page_title = "All actions"
if @context
@page_title += " in context #{@context.name}"
@down_count = @context.not_done_todo_count
elsif @project
@page_title += " in project #{@project.name}"
@down_count = @project.not_done_todo_count
else
determine_down_count
end
render :action => 'index_mobile'
end
end
def render_rss_feed def render_rss_feed
lambda do lambda do

View file

@ -185,6 +185,11 @@ module TodosHelper
split_notes = notes.split(/\n/) split_notes = notes.split(/\n/)
joined_notes = split_notes.join("\\n") joined_notes = split_notes.join("\\n")
end end
def formatted_pagination(total, per_page)
s = will_paginate(@down_count, 6)
(s.gsub /(<\/[^<]+>)/, '\1 ').chomp(' ')
end
private private

View file

@ -1,35 +0,0 @@
<%= render_flash %>
<% if @todos.length == 0 -%>
<p>There are no incomplete actions in this <%= @type %></p>
<% else -%>
<ul>
<% for todo in @todos -%>
<li>
<%= link_to "&raquo;", :controller => 'mobile', :action => 'detail', :id => todo.id %>
<% if todo.due? -%>
<%= due_date_mobile(todo.due) %>
<% end -%>
<%= todo.description %>
(<em><%= todo.context.name %></em>)
<% end -%>
</li>
<ul>
<% if !@todos_pages.nil? -%>
<% if @todos_pages.length > 1 -%>
<hr />
Pages: <%= pagination_links( @todos_pages, :always_show_anchors => true ) %>
<% end -%>
<% end -%>
<% end -%>
<hr />
<% form_tag( { :action => "filter", :type => "context" } ) do -%>
<%= collection_select( "context", "id", @contexts, "id", "name",
{ :include_blank => true } ) %>
<%= submit_tag( value = "Go" ) %>
<% end -%>
<% form_tag( {:action => "filter", :type => "project" }) do -%>
<%= collection_select( "project", "id", @projects, "id", "name",
{ :include_blank => true } ) %>
<%= submit_tag( value = "Go" ) %>
<% end -%>

View file

@ -1,4 +0,0 @@
<% form_tag :action => 'update', :id => @item.id do -%>
<%= render :partial => 'mobile_edit' %>
<% end -%>
<%= button_to "Back", :controller => 'mobile', :action => 'index' %>

View file

@ -1,6 +0,0 @@
<h1><span class="count"><%= @count.to_s %></span> <%= @desc %>
<%= link_to "+", :controller => 'mobile', :action => 'add_action' %></h1>
<hr />
<%= render :partial => 'mobile_actions' %>
<%= link_to "View All", :controller => 'mobile', :action => 'index' %>

View file

@ -1,5 +0,0 @@
<h1><span class="count"><%= @count.to_s %></span> <%= @desc %>
<%= link_to "+", :controller => 'mobile', :action => 'add_action' %></h1>
<hr />
<%= render :partial => 'mobile_actions' %>
<%= link_to "Logout", :controller => 'login', :action => 'logout' %>

View file

@ -1,4 +0,0 @@
<% form_tag :action => 'update' do %>
<%= render :partial => 'mobile_edit' %>
<% end -%>
<%= button_to "Back", :controller => 'mobile', :action => 'index' %>

View file

@ -1,3 +1,3 @@
<div id="footer"> <div id="footer">
<p>Send feedback: <a href="http://dev.rousette.org.uk/report/6">Trac</a> | <a href="http://www.rousette.org.uk/projects/forums/">Forum</a> | <a href="http://www.rousette.org.uk/projects/wiki/index">Wiki</a> | <a href="mailto:butshesagirl@rousette.org.uk?subject=Tracks feedback">Email</a> | <a href="http://www.rousette.org.uk/projects/">Website</a></p> <p>Send feedback: <a href="http://dev.rousette.org.uk/report/6">Trac</a> | <a href="http://www.rousette.org.uk/projects/forums/">Forum</a> | <a href="http://www.rousette.org.uk/projects/wiki/index">Wiki</a> | <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>

View file

@ -2,8 +2,8 @@
<%= error_messages_for("todo") %> <%= error_messages_for("todo") %>
</span> </span>
<% this_year = user_time.to_date.strftime("%Y").to_i -%> <% this_year = user_time.to_date.strftime("%Y").to_i -%>
<p><label for="todo_state">Done?</label></p> <p><label for="done">Done?</label></p>
<p><%= check_box( "todo", "state", "tabindex" => 1) %></p> <p><%= check_box_tag("done", 1, @todo && @todo.completed?, "tabindex" => 1) %></p>
<p><label for="todo_description">Next action</label></p> <p><label for="todo_description">Next action</label></p>
<p><%= text_field( "todo", "description", "tabindex" => 2) %></p> <p><%= text_field( "todo", "description", "tabindex" => 2) %></p>
<p><label for="todo_notes">Notes</label></p> <p><label for="todo_notes">Notes</label></p>
@ -18,4 +18,3 @@
<p><label for="todo_show_from">Show from</label></p> <p><label for="todo_show_from">Show from</label></p>
<p><%= date_select("todo", "show_from", :order => [:day, :month, :year], <p><%= date_select("todo", "show_from", :order => [:day, :month, :year],
:start_year => this_year, :include_blank => true) %></p> :start_year => this_year, :include_blank => true) %></p>
<p><input type="submit" value="Update" tabindex="6" /></p>

View file

@ -0,0 +1,33 @@
<%= render_flash %>
<% if @todos.length == 0 -%>
<p>There are no incomplete actions in this <%= @type %></p>
<% else -%>
<ul>
<% for todo in @todos -%>
<li id="<%= dom_id(todo) %>">
<%= link_to "&raquo;", formatted_todo_path(todo, :m) %>
<% if todo.due? -%>
<%= due_date_mobile(todo.due) %>
<% end -%>
<%= todo.description %>
(<em><%= todo.context.name %></em>)
</li>
<% end -%>
</ul>
<% if @down_count > 6 -%>
<hr />
Pages: <%= formatted_pagination(@down_count, 6) %>
<% end -%>
<% end -%>
<hr />
<% form_tag( formatted_filter_to_context_todos_path(:m), :method => :post ) do -%>
<%= collection_select( "context", "id", @contexts, "id", "name",
{ :include_blank => true } ) %>
<%= submit_tag( "Go", :id => 'change_context' ) %>
<% end -%>
<% form_tag( formatted_filter_to_project_todos_path(:m), :method => :post ) do -%>
<%= collection_select( "project", "id", @projects, "id", "name",
{ :include_blank => true } ) %>
<%= submit_tag( "Go", :id => 'change_project' ) %>
<% end -%>

View file

@ -0,0 +1,6 @@
<h1><span class="count"><%= @down_count %></span> <%= @page_title %>
<%= link_to "+", formatted_new_todo_path(:m) %></h1>
<hr />
<%= render :partial => 'mobile_actions' %>
<hr />
<%= link_to "Logout", :controller => 'login', :action => 'logout' %>

View file

@ -0,0 +1,5 @@
<% form_tag formatted_todos_path(:m), :method => :post do %>
<%= render :partial => 'edit_mobile' %>
<p><input type="submit" value="Create" tabindex="6" /></p>
<% end -%>
<%= link_to "Back", formatted_todos_path(:m) %>

View file

@ -0,0 +1,5 @@
<% form_tag formatted_todo_path(@todo, :m), :method => :put do %>
<%= render :partial => 'edit_mobile' %>
<p><input type="submit" value="Update" tabindex="6" /></p>
<% end -%>
<%= link_to "Back", formatted_todos_path(:m) %>

View file

@ -80,3 +80,6 @@ if (AUTHENTICATION_SCHEMES.include? 'open_id')
#requires ruby-openid gem to be installed #requires ruby-openid gem to be installed
end end
MOBILE_CONTENT_TYPE = 'tracks/mobile'
Mime::Type.register(MOBILE_CONTENT_TYPE, :m)

View file

@ -1,10 +1,6 @@
ActionController::Routing::Routes.draw do |map| ActionController::Routing::Routes.draw do |map|
UJS::routes UJS::routes
# Mobile/lite version
map.connect 'mobile', :controller => 'mobile', :action => 'index'
map.connect 'mobile/add_action', :controller => 'mobile', :action => 'show_add_form'
# Login Routes # Login Routes
map.connect 'login', :controller => 'login', :action => 'login' map.connect 'login', :controller => 'login', :action => 'login'
map.connect 'logout', :controller => 'login', :action => 'logout' map.connect 'logout', :controller => 'login', :action => 'logout'
@ -13,30 +9,33 @@ ActionController::Routing::Routes.draw do |map|
:member => {:change_password => :get, :update_password => :post, :member => {:change_password => :get, :update_password => :post,
:change_auth_type => :get, :update_auth_type => :post, :complete => :get, :change_auth_type => :get, :update_auth_type => :post, :complete => :get,
:refresh_token => :post } :refresh_token => :post }
map.with_options :controller => "users" do |users| map.with_options :controller => "users" do |users|
users.signup 'signup', :action => "new" users.signup 'signup', :action => "new"
end end
# Context Routes # Context Routes
map.resources :contexts, :collection => {:order => :post} do |contexts| map.resources :contexts, :collection => {:order => :post} do |contexts|
contexts.resources :todos contexts.resources :todos, :name_prefix => "context_"
end end
# Projects Routes # Projects Routes
map.resources :projects, :collection => {:order => :post} do |projects| map.resources :projects, :collection => {:order => :post} do |projects|
projects.resources :todos projects.resources :todos, :name_prefix => "project_"
end end
# ToDo Routes # ToDo Routes
map.resources :todos, map.resources :todos,
:member => {:toggle_check => :post}, :member => {:toggle_check => :post},
:collection => {:check_deferred => :post} :collection => {:check_deferred => :post, :filter_to_context => :post, :filter_to_project => :post}
map.with_options :controller => "todos" do |todos| map.with_options :controller => "todos" do |todos|
todos.home '', :action => "index" todos.home '', :action => "index"
todos.tickler 'tickler', :action => "list_deferred" todos.tickler 'tickler', :action => "list_deferred"
todos.done 'done', :action => "completed" todos.done 'done', :action => "completed"
todos.done_archive 'done/archive', :action => "completed_archive" todos.done_archive 'done/archive', :action => "completed_archive"
todos.tag 'todos/tag/:name', :action => "tag" todos.tag 'todos/tag/:name', :action => "tag"
todos.mobile 'mobile', :action => "index", :format => 'm'
todos.mobile_abbrev 'm', :action => "index", :format => 'm'
todos.mobile_abbrev_new 'm/new', :action => "new", :format => 'm'
end end
# Notes Routes # Notes Routes

View file

@ -6,7 +6,7 @@ ActiveRecord::Schema.define(:version => 31) do
create_table "contexts", :force => true do |t| create_table "contexts", :force => true do |t|
t.column "name", :string, :default => "", :null => false t.column "name", :string, :default => "", :null => false
t.column "hide", :boolean, :default => false t.column "hide", :integer, :limit => 4, :default => 0, :null => false
t.column "position", :integer, :default => 0, :null => false t.column "position", :integer, :default => 0, :null => false
t.column "user_id", :integer, :default => 0, :null => false t.column "user_id", :integer, :default => 0, :null => false
t.column "created_at", :datetime t.column "created_at", :datetime

View file

@ -114,6 +114,7 @@ module LoginSystem
def access_denied def access_denied
respond_to do |format| respond_to do |format|
format.html { redirect_to :controller=>"login", :action =>"login" } format.html { redirect_to :controller=>"login", :action =>"login" }
format.m { redirect_to :controller=>"login", :action =>"login" }
format.js { render :partial => 'login/redirect_to_login' } format.js { render :partial => 'login/redirect_to_login' }
format.xml { basic_auth_denied } format.xml { basic_auth_denied }
format.rss { basic_auth_denied } format.rss { basic_auth_denied }

View file

@ -27,7 +27,7 @@ module Tracks
end end
def source_view def source_view
responder = Tracks::SourceViewSwitching::Responder.new(params[:_source_view]) responder = Tracks::SourceViewSwitching::Responder.new(params[:_source_view] || @source_view)
block_given? ? yield(responder) : responder block_given? ? yield(responder) : responder
end end

View file

@ -1,54 +0,0 @@
require File.dirname(__FILE__) + '/../test_helper'
require 'mobile_controller'
# Re-raise errors caught by the controller.
class MobileController; def rescue_action(e) raise e end; end
class MobileControllerTest < Test::Unit::TestCase
fixtures :users, :preferences, :projects, :contexts, :todos
def setup
@controller = MobileController.new
@request = ActionController::TestRequest.new
@response = ActionController::TestResponse.new
end
def test_get_index_when_not_logged_in
get :index
assert_redirected_to :controller => 'login', :action => 'login'
end
def test_create_todo
@count = Todo.find(:all)
@request.session['user_id'] = users(:admin_user).id
xhr :post, :update, "todo"=>{"context_id"=>"1", "project_id"=>"2", "notes"=>"", "description"=>"Invest in spam stock offer", "due"=>"01/01/2007", "show_from"=>"", "state"=>"0"}
@todos = Todo.find(:all)
assert_equal @count.size+1, @todos.size
t = Todo.find(:first, :conditions => ['description = ?', "Invest in spam stock offer"])
assert_equal "Invest in spam stock offer", t.description
assert_equal Date.parse("01/01/2007"), t.due
assert_equal users(:admin_user).id, t.user_id
assert_equal 1, t.context_id
assert_equal 2, t.project_id
assert_equal "active", t.state
end
def test_update_todo
t = Todo.find(1)
@request.session['user_id'] = users(:admin_user).id
xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Warren Buffet to find out how much he makes per day", "due"=>"11/30/2006"}
t = Todo.find(1)
assert_equal "Call Warren Buffet to find out how much he makes per day", t.description
assert_equal Date.parse("11/30/2006"), t.due
assert_equal users(:admin_user).id, t.user_id
assert_equal "active", t.state
end
def test_complete_todo
t = Todo.find(1)
@request.session['user_id'] = users(:admin_user).id
xhr :post, :update, :id => 1, :_source_view => 'todo', "todo"=>{"context_id"=>"1", "project_id"=>"2", "id"=>"1", "notes"=>"", "description"=>"Call Bill Gates to find out how much he makes per day", "state"=>"1"}
t = Todo.find(1)
assert_equal "completed", t.state
end
end

View file

@ -242,5 +242,34 @@ class TodosControllerTest < Test::Unit::TestCase
assert !(/&nbsp;/.match(@response.body)) assert !(/&nbsp;/.match(@response.body))
#puts @response.body #puts @response.body
end end
def test_mobile_index_uses_text_html_content_type
@request.session['user_id'] = users(:admin_user).id
get :index, { :format => "m" }
assert_equal 'text/html; charset=utf-8', @response.headers["Content-Type"]
end
def test_mobile_index_assigns_down_count
@request.session['user_id'] = users(:admin_user).id
get :index, { :format => "m" }
assert_equal 10, assigns['down_count']
end
def test_mobile_create_action
@request.session['user_id'] = users(:admin_user).id
post :create, {"format"=>"m", "todo"=>{"context_id"=>"2",
"due(1i)"=>"2007", "due(2i)"=>"1", "due(3i)"=>"2",
"show_from(1i)"=>"", "show_from(2i)"=>"", "show_from(3i)"=>"",
"project_id"=>"1",
"notes"=>"test notes", "description"=>"test_mobile_create_action", "state"=>"0"}}
t = Todo.find_by_description("test_mobile_create_action")
assert_not_nil t
assert_equal 2, t.context_id
assert_equal 1, t.project_id
assert t.active?
assert_equal 'test notes', t.notes
assert_nil t.show_from
assert_equal Date.new(2007,1,2).to_s, t.due.to_s
end
end end

View file

@ -0,0 +1,18 @@
setup :fixtures => :all
login :as => 'admin'
open '/m'
wait_for_text 'css=h1 span.count', '10'
click_and_wait "link=+"
type "todo_notes", "test notes"
type "todo_description", "test name"
select "todo_context_id", "label=call"
select "todo_project_id", "label=Make more money than Billy Gates"
select "todo_due_3i", "label=1"
select "todo_due_2i", "label=January"
select "todo_due_1i", "label=2007"
click_and_wait "//input[@value='Create']"
wait_for_text 'css=h1 span.count', '11'

View file

@ -0,0 +1,11 @@
setup :fixtures => :all
login :as => 'admin'
open '/m'
wait_for_text 'css=h1 span.count', '10'
open_and_wait '/todos/6.m'
click "done"
click_and_wait "//input[@value='Update']"
wait_for_text 'css=h1 span.count', '9'

View file

@ -0,0 +1,25 @@
setup :fixtures => :all
login :as => 'admin'
open '/m'
wait_for_title "All actions"
wait_for_text 'css=h1 span.count', '10'
click_and_wait "link=2"
verify_title "All actions"
wait_for_text 'css=h1 span.count', '10'
select "context_id", "label=agenda"
click_and_wait "change_context"
verify_title "All actions in context agenda"
wait_for_text 'css=h1 span.count', '5'
select "context_id", "label=call"
click_and_wait "change_context"
verify_title "All actions in context call"
wait_for_text 'css=h1 span.count', '3'
select "project_id", "label=Build a working time machine"
click_and_wait "change_project"
verify_title "All actions in project Build a working time machine"
wait_for_text 'css=h1 span.count', '2'

View file

@ -0,0 +1,58 @@
WillPaginate
Ruby port by: PJ Hyett
Original PHP source: http://www.strangerstudios.com/sandbox/pagination/diggstyle.php
Contributors: K. Adam Christensen, Chris Wanstrath, Dr. Nic Williams
Example usage:
app/models/post.rb
class Post < ActiveRecord::Base
cattr_reader :per_page
@@per_page = 50
end
app/controller/posts_controller.rb
def index
@board = Board.find(params[:id])
@posts, @page = Post.paginate_all_by_board_id(@board.id, :page => params[:page])
end
app/views/posts/index.rhtml
<%= will_paginate(@board.topic_count, Post.per_page) %>
Copy the following css into your stylesheet for a good start:
.pagination {
padding: 3px;
margin: 3px;
}
.pagination a {
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #aaaadd;
text-decoration: none;
color: #000099;
}
.pagination a:hover, .pagination a:active {
border: 1px solid #000099;
color: #000;
}
.pagination span.current {
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #000099;
font-weight: bold;
background-color: #000099;
color: #FFF;
}
.pagination span.disabled {
padding: 2px 5px 2px 5px;
margin: 2px;
border: 1px solid #eee;
color: #ddd;
}

View file

@ -0,0 +1,4 @@
require 'will_paginate'
require 'finder'
ActionView::Base.send(:include, WillPaginate)
ActiveRecord::Base.send(:include, WillPaginate::Finder)

View file

@ -0,0 +1,28 @@
module WillPaginate
module Finder
def self.included(base)
base.extend ClassMethods
class << base
define_method(:per_page) { 30 } unless respond_to? :per_page
end
end
module ClassMethods
def method_missing_with_will_paginate(method_id, *args, &block)
unless match = /^paginate/.match(method_id.to_s)
return method_missing_without_will_paginate(method_id, *args, &block)
end
options = args.last.is_a?(Hash) ? args.pop : {}
page = (page = options.delete(:page).to_i).zero? ? 1 : page
limit_per_page = options.delete(:per_page) || per_page
args << options
with_scope :find => { :offset => (page - 1) * limit_per_page, :limit => limit_per_page } do
[send(method_id.to_s.sub(/^paginate/, 'find'), *args), page]
end
end
alias_method_chain :method_missing, :will_paginate
end
end
end

View file

@ -0,0 +1,43 @@
module WillPaginate
def will_paginate(total_count, per_page, page = @page)
adjacents = 2
prev_page = page - 1
next_page = page + 1
last_page = (total_count / per_page.to_f).ceil
lpm1 = last_page - 1
returning '' do |pgn|
if last_page > 1
pgn << %{<div class="pagination">}
# not enough pages to bother breaking
if last_page < 7 + (adjacents * 2)
1.upto(last_page) { |ctr| pgn << (ctr == page ? content_tag(:span, ctr, :class => 'current') : link_to(ctr, params.merge(:page => ctr))) }
# enough pages to hide some
elsif last_page > 5 + (adjacents * 2)
# close to beginning, only hide later pages
if page < 1 + (adjacents * 2)
1.upto(3 + (adjacents * 2)) { |ctr| pgn << (ctr == page ? content_tag(:span, ctr, :class => 'current') : link_to(ctr, :page => ctr)) }
pgn << "..." + link_to(lpm1, params.merge(:page => lpm1)) + link_to(last_page, params.merge(:page => last_page))
# in middle, hide some from both sides
elsif last_page - (adjacents * 2) > page && page > (adjacents * 2)
pgn << link_to('1', params.merge(:page => 1)) + link_to('2', params.merge(:page => 2)) + "..."
(page - adjacents).upto(page + adjacents) { |ctr| pgn << (ctr == page ? content_tag(:span, ctr, :class => 'current') : link_to(ctr, params.merge(:page => ctr))) }
pgn << "..." + link_to(lpm1, params.merge(:page => lpm1)) + link_to(last_page, params.merge(:page => last_page))
# close to end, only hide early pages
else
pgn << link_to('1', params.merge(:page => 1)) + link_to('2', params.merge(:page => 2)) + "..."
(last_page - (2 + (adjacents * 2))).upto(last_page) { |ctr| pgn << (ctr == page ? content_tag(:span, ctr, :class => 'current') : link_to(ctr, params.merge(:page => ctr))) }
end
end
pgn << (page > 1 ? link_to("&laquo; Previous", params.merge(:page => prev_page)) : content_tag(:span, "&laquo; Previous", :class => 'disabled'))
pgn << (page < last_page ? link_to("Next &raquo;", params.merge(:page => next_page)) : content_tag(:span, "Next &raquo;", :class => 'disabled'))
pgn << '</div>'
end
end
end
end