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

View file

@ -41,9 +41,9 @@ class ContextsController < ApplicationController
end
@saved = @context.save
@context_not_done_counts = { @context.id => 0 }
respond_to do |wants|
wants.js
wants.xml do
respond_to do |format|
format.js
format.xml do
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>."
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
@all_notes = @user.notes
@page_title = "TRACKS::All notes"
respond_to do |wants|
wants.html
wants.xml { render :xml => @all_notes.to_xml( :except => :user_id ) }
respond_to do |format|
format.html
format.xml { render :xml => @all_notes.to_xml( :except => :user_id ) }
end
end

View file

@ -53,9 +53,9 @@ class ProjectsController < ApplicationController
@project_not_done_counts = { @project.id => 0 }
@active_projects_count = @user.projects.count(:conditions => "state = 'active'")
@contexts = @user.contexts
respond_to do |wants|
wants.js
wants.xml do
respond_to do |format|
format.js
format.xml do
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>."
elsif @project.new_record?

View file

@ -2,13 +2,17 @@ class TodosController < ApplicationController
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]
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) }
layout 'standard'
layout proc{ |controller| controller.mobile? ? "mobile" : "standard" }
def index
@projects = @user.projects.find(:all, :include => [ :todos ])
@ -17,20 +21,29 @@ class TodosController < ApplicationController
@contexts_to_show = @contexts.reject {|x| x.hide? }
respond_to do |format|
format.html &render_todos_html
format.xml { render :action => 'list.rxml', :layout => false }
format.html &render_todos_html
format.m &render_todos_mobile
format.xml { render :action => 'list.rxml', :layout => false }
format.rss &render_rss_feed
format.atom &render_atom_feed
format.text &render_text_feed
format.ics &render_ical_feed
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
@todo = @user.todos.build
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'])
end
@ -60,34 +73,46 @@ class TodosController < ApplicationController
end
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
@todo.due = ""
end
@saved = @todo.save
if @saved
@todo.tag_with(params[:tag_list],@user)
@todo.tag_with(params[:tag_list],@user) if params[:tag_list]
@todo.reload
end
respond_to do |wants|
wants.html { redirect_to :action => "index" }
wants.js do
respond_to do |format|
format.html { redirect_to :action => "index" }
format.m do
if @saved
determine_down_count
redirect_to :action => "index", :format => :m
else
render :action => "new", :format => :m
end
end
format.js do
determine_down_count if @saved
render :action => 'create'
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
def edit
@projects = @user.projects.find(:all)
@contexts = @user.contexts.find(:all)
end
def show
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 ) }
end
end
@ -120,7 +145,7 @@ class TodosController < ApplicationController
end
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_project_id = @todo.project_id
@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'])
end
if params['done'] == '1' && !@todo.completed?
@todo.complete!
end
@saved = @todo.update_attributes params["todo"]
@context_changed = @original_item_context_id != @todo.context_id
@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
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
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
def destroy
@ -175,9 +214,9 @@ class TodosController < ApplicationController
@project_id = @todo.project_id
@saved = @todo.destroy
respond_to do |wants|
respond_to do |format|
wants.html do
format.html do
if @saved
notify :notice, "Successfully deleted next action", 2.0
redirect_to :action => 'index'
@ -185,9 +224,9 @@ class TodosController < ApplicationController
notify :error, "Failed to delete the action", 2.0
redirect_to :action => 'index'
end
end
end
wants.js do
format.js do
if @saved
determine_down_count
source_view do |from|
@ -199,7 +238,7 @@ class TodosController < ApplicationController
render
end
wants.xml { render :text => '200 OK. Action deleted.', :status => 200 }
format.xml { render :text => '200 OK. Action deleted.', :status => 200 }
end
end
@ -236,6 +275,16 @@ class TodosController < ApplicationController
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
#
def tag
@ -266,15 +315,47 @@ class TodosController < ApplicationController
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
@todo = @user.todos.find(params['id'])
end
def init
@source_view = params['_source_view'] || 'todo'
init_data_for_sidebar
init_data_for_sidebar unless mobile?
init_todos
end
@ -321,13 +402,13 @@ class TodosController < ApplicationController
def with_parent_resource_scope(&block)
if (params[:context_id])
context = @user.contexts.find_by_params(params)
Todo.with_scope :find => {:conditions => ['todos.context_id = ?', context.id]} do
@context = @user.contexts.find_by_params(params)
Todo.with_scope :find => {:conditions => ['todos.context_id = ?', @context.id]} do
yield
end
elsif (params[:project_id])
project = @user.projects.find_by_params(params)
Todo.with_scope :find => {:conditions => ['todos.project_id = ?', project.id]} do
@project = @user.projects.find_by_params(params)
Todo.with_scope :find => {:conditions => ['todos.project_id = ?', @project.id]} do
yield
end
else
@ -350,12 +431,26 @@ class TodosController < ApplicationController
with_feed_query_scope do
with_parent_resource_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
@todos = @user.todos.find(:all, :conditions => ['todos.state = ? or todos.state = ?', 'active', 'complete'], :include => [ :project, :context, :tags ])
# Exclude hidden projects from the home page
@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
@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
@ -415,6 +510,23 @@ class TodosController < ApplicationController
render
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
lambda do

View file

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

View file

@ -2,8 +2,8 @@
<%= error_messages_for("todo") %>
</span>
<% this_year = user_time.to_date.strftime("%Y").to_i -%>
<p><label for="todo_state">Done?</label></p>
<p><%= check_box( "todo", "state", "tabindex" => 1) %></p>
<p><label for="done">Done?</label></p>
<p><%= check_box_tag("done", 1, @todo && @todo.completed?, "tabindex" => 1) %></p>
<p><label for="todo_description">Next action</label></p>
<p><%= text_field( "todo", "description", "tabindex" => 2) %></p>
<p><label for="todo_notes">Notes</label></p>
@ -18,4 +18,3 @@
<p><label for="todo_show_from">Show from</label></p>
<p><%= date_select("todo", "show_from", :order => [:day, :month, :year],
: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
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|
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
map.connect 'login', :controller => 'login', :action => 'login'
map.connect 'logout', :controller => 'login', :action => 'logout'
@ -13,30 +9,33 @@ ActionController::Routing::Routes.draw do |map|
:member => {:change_password => :get, :update_password => :post,
:change_auth_type => :get, :update_auth_type => :post, :complete => :get,
:refresh_token => :post }
map.with_options :controller => "users" do |users|
users.signup 'signup', :action => "new"
end
map.with_options :controller => "users" do |users|
users.signup 'signup', :action => "new"
end
# Context Routes
map.resources :contexts, :collection => {:order => :post} do |contexts|
contexts.resources :todos
contexts.resources :todos, :name_prefix => "context_"
end
# Projects Routes
map.resources :projects, :collection => {:order => :post} do |projects|
projects.resources :todos
projects.resources :todos, :name_prefix => "project_"
end
# ToDo Routes
map.resources :todos,
: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|
todos.home '', :action => "index"
todos.tickler 'tickler', :action => "list_deferred"
todos.done 'done', :action => "completed"
todos.done_archive 'done/archive', :action => "completed_archive"
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
# Notes Routes

View file

@ -6,7 +6,7 @@ ActiveRecord::Schema.define(:version => 31) do
create_table "contexts", :force => true do |t|
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 "user_id", :integer, :default => 0, :null => false
t.column "created_at", :datetime

View file

@ -114,6 +114,7 @@ module LoginSystem
def access_denied
respond_to do |format|
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.xml { basic_auth_denied }
format.rss { basic_auth_denied }

View file

@ -27,7 +27,7 @@ module Tracks
end
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
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))
#puts @response.body
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

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