From 66b96e0c0cab62eee768faaa7343e7ee4c9e88de Mon Sep 17 00:00:00 2001 From: Matt Rogers Date: Fri, 4 Nov 2011 10:29:09 -0500 Subject: [PATCH 01/42] Properly update the project page for hidden projects Immediately after hiding a project, if a user marks a todo as done, they would erroneously see a "There are no incomplete actions in this project" banner under the open actions section. This commit uses the active_or_hidden scope for the project todos in order to fix this, since the todos are active but hidden due to the project. Fixes #1202 --- app/controllers/todos_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 61aee981..405a44c6 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -1103,7 +1103,7 @@ class TodosController < ApplicationController if @todo_was_completed_from_deferred_or_blocked_state @remaining_in_context = @remaining_deferred_or_pending_count else - @remaining_in_context = current_user.projects.find(project_id).todos.active.count + @remaining_in_context = current_user.projects.find(project_id).todos.active_or_hidden.count end @target_context_count = current_user.projects.find(project_id).todos.active.count From 15c742b418e4f398c3bb13dc2a512893e8380efc Mon Sep 17 00:00:00 2001 From: Matt Rogers Date: Mon, 24 Oct 2011 12:34:11 -0500 Subject: [PATCH 02/42] Update the README for developers. We're using bundler now so update the piece about installing the dependencies. Also do some reformatting at the 80 column boundary in order to avoid having really long lines that don't look quite so nice in a smaller editor window. Add information about running the Test::Unit tests. Remove the reference to the forum since it's not accepting new registrations anymore and users are advised to use the mailing list elsewhere. --- doc/README_DEVELOPERS | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/doc/README_DEVELOPERS b/doc/README_DEVELOPERS index b0a98d3e..e63c7e44 100644 --- a/doc/README_DEVELOPERS +++ b/doc/README_DEVELOPERS @@ -3,8 +3,7 @@ Tracks is using * github to host the git repository. * Assembla to manage bugs and enhancement request. -* the mailing list to discuss features and development -* the forum to discuss with users +* the mailing list to discuss features and development and interact with users See README for links to the respective sites @@ -12,35 +11,39 @@ Also see the Development pages on the wiki for details on installing, upgrading, 2. Dependencies -The dependencies are maintained by Tracks. For development we try not to vendor them - -Install them using - - rake gems:install RAILS_ENV=development - rake gems:install RAILS_ENV=test - rake gems:install RAILS_ENV=selenium +The dependencies for Tracks are maintained using bundler. Before starting your +tracks instance, you'll need to run 'bundle install' to fetch all the +dependencies 3. Wiki -There are some pointers for setting up your Tracks copy for testing at https://github.com/TracksApp/tracks/wiki/Testing/ +There are some pointers for setting up your Tracks copy for testing at +https://github.com/TracksApp/tracks/wiki/Testing/ 4. SQLITE3 FOR TESTING -By default, tests are configured to run using sqlite3 in memory mode to increase speed. You will need the sqlite3-ruby gem for this. +By default, tests are configured to run using sqlite3 in memory mode to +increase speed. You will need the sqlite3-ruby gem for this. -To avoid showing the migrations as tests are run, add the following to your database.yml below 'database: ":memory:"': +To avoid showing the migrations as tests are run, add the following to your +database.yml below 'database: ":memory:"': verbosity: quiet -If you want to run tests using another database, that's fine, too. Just change your database.yml accordingly. +If you want to run tests using another database, that's fine, too. Just change +your database.yml accordingly. -5. SELENIUM TESTS (Selenium on Rails) +5. Test::Unit tests -This testing style is deprecated and tests are being moved over to Selenium via Cucumber. +To run the Test::Unit tests run -See the wiki for more information to run the tests that are not yet migrated: https://github.com/TracksApp/tracks/wiki/Testing + rake test -6. RSPEC tests +Running the above command will run through the unit, functional, and +integration tests for Tracks. Use 'rake -T' to see a list of all rake tasks to +determine how to run each section of tests separately. + +6. RSpec tests To run the RSpec tests run @@ -56,4 +59,7 @@ and for those using javascript/ajax use rake cucumber:selenium -See the wiki for more information on testing: https://github.com/TracksApp/tracks/wiki/Testing \ No newline at end of file +In order to run the selenium tests, you'll need to have Firefox 3.x installed. +Newer versions won't work. + +See the wiki for more information on testing: https://github.com/TracksApp/tracks/wiki/Testing From fcc974d87868cedd93c1c1106a85bc63daf081c7 Mon Sep 17 00:00:00 2001 From: Matt Rogers Date: Mon, 24 Oct 2011 12:35:10 -0500 Subject: [PATCH 03/42] Small capitalization and grammar fixes The capitalization style is now consistent throughout the whole file. --- doc/CHANGELOG | 64 +++++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/CHANGELOG b/doc/CHANGELOG index 7aad734b..ae0944eb 100644 --- a/doc/CHANGELOG +++ b/doc/CHANGELOG @@ -15,32 +15,32 @@ == Version 2.1devel -NOTE: to use this version you need to migrate your database. Not migrating will +NOTE: To use this version you need to migrate your database. Not migrating will cause new actions not to appear! -This version of tracks has moved to a new place on github. Also the wiki moved -to github, see the changed URLs above. +This version of tracks has moved to a new place on GitHub. Also the wiki moved +to GitHub, see the changed URLs above. New and changed features: -1. redesign of the completed todos: a new overview page. Also all context and +1. Redesign of the completed todos: a new overview page. Also all context and project pages have a link to their completed actions 2. New locales (es and fr) and updated locales (de, nl) 3. You can star an action right from the form to add a new action -4. redesign of preferences page +4. Redesign of preferences page. 5. You can now mark an action complete from the tickler -6. project names can now contain comma (',') in it name +6. Project names can now contain comma (',') in it name 7. There are two example ruby scripts in /doc to use the REST API to add a todo or a project template with todos from the command line Under the hood: 1. Upgraded rails to 2.3.12, jquery to 1.6.2 and jquery-ui to 1.8.14 -2. fixed several issues with the REST API -3. upgraded the act_as_statemachine plugin. This change requires a - migration, see note above! -4. migated to bundler for gem dependencies +2. Fixed several issues with the REST API +3. Upgraded the act_as_statemachine plugin. This change requires a + migration. See note above! +4. Migated to bundler for gem dependencies See https://github.com/tracksapp/tracks/compare/v2.0...master for all -detailled changes +detailed changes == Version 2.0 @@ -50,23 +50,23 @@ New features: to a new todo in that project if no tags are supplied 3. Tracks now includes support of dependencies. Making an action dependent on another action will hide it until the dependency is completed -4. you can now drag an action from one context to another +4. You can now drag an action from one context to another 5. Support for entering multiple actions in one form 6. You can now promote an action to a project 7. It is easier to view notes on the mobile interface and other interface fixes 8. The project description supports markup -9. support for Mail.app (message://) and OneNote (onenote://) links in notes -10.The email receiver is now able to receive email from several email adresses. - In site.yml this could be set to the previous behavior (receive from one - address per user) -11.You can enable open signup (like in tracks.tra.in) -12.Cleanup of context page -13.Support for CAS for login -14.Support for adding Tracks as a GMail Widget with instructions on the - Integrations page -15.Tracks now support internationalization. First translations are German and - Dutch. See http://www.getontracks.org/wiki/Translating-Tracks it you like to - help translating Tracks to other languages +9. Support for Mail.app (message://) and OneNote (onenote://) links in notes +10. The email receiver is now able to receive email from several email adresses. + In site.yml this could be set to the previous behavior (receive from one + address per user) +11. You can enable open signup (like in tracks.tra.in) +12. Cleanup of context page +13. Support for CAS for login +14. Support for adding Tracks as a GMail Widget with instructions on the + Integrations page +15. Tracks now supports internationalization. First translations are German and + Dutch. See http://www.getontracks.org/wiki/Translating-Tracks if you'd like to + help translate Tracks to other languages Under the hood 1. All js is migrated to jQuery and most ui-widgets are migrated to jQuery-UI @@ -93,9 +93,9 @@ Under the hood: 5. Bugfixes, including fixing OpenID == Version 1.6 -1. upgrade to rails 2.0.2 -2. new mobile interface (with some iPhone compatibility fixes) -3. new search functionality to search on todos, projects, contexts and notes +1. Upgrade to rails 2.0.2 +2. New mobile interface (with some iPhone compatibility fixes) +3. New search functionality to search on todos, projects, contexts and notes 4. Bugfixes == Version 1.5 @@ -173,11 +173,6 @@ Under the hood: == Version 1.03 -13. All the adding, updating, deleting and marking actions done is performed using Ajax, so happens without needing to refresh the page. -14. There's a new setting in settings.yml ('staleness_starts') which defines the number of days before which actions get marked as stale. Let's say you set it to 7 days. Actions created between 7 and 14 days ago get marked pale yellow, those created between 14 and 28 days ago (staleness_starts x 2) get marked darker yellow, and those created more than 28 days ago (staleness_starts x 3) are fluorescent yellow! This is only applied to items without a due date, so you can add items to be done some time in the future without getting yellow splashed all over the place (thanks to Nicholas for the patch to restrict to items with no due date). -15. Contexts and projects can now be sorted in any order you like. Arrow buttons on the /contexts and /projects pages let you move an item to the top, up, down or to the bottom. For contexts, this affects the order in which they sort on the home page. Position is also used to sort the listings of active/completed/hidden contexts and projects in the 'sidebar', and in the dropdown lists on forms. -16. You can mark projects as completed (by editing the project on /projects). In the 'sidebar' active and completed projects are shown separately, but you can still view the completed project. -17. New images (from eclipse.org) for the edit, delete, notes and up, down, top and bottom buttons. I've made a greyscale version for the default, then the coloured version gets loaded when the mouse is hovering over the button. 1. Added back border="0" to images which I had mistakenly taken out. This should fix the ugly red border around images that appears in Firefox (thanks, Adam Hughes). 2. Removed the section in config/environment.rb which requires Redcloth. This was causing errors because Rails now requires Redcloth itself. This means that you now need to have Redcloth installed as a gem (gem install redcloth) (thanks, Jim). 3. SQL dumps are now available for each of the available database formats (MySQL, PostgreSQL and SQLite), as well as a separate file containing some example contents, which should work for all of the formats. These are in the tracks/db directory (thanks, Jim) @@ -190,6 +185,11 @@ Under the hood: 10. Patch by lolindrath: Sorting by date is now much smarter on the home page. Actions are sorted by ascending due date then ascending creation date, but non-due dated items sort to the bottom. This means that the most urgent items float to the top of each context list. 11. You can now uncheck actions from the completed actions list, so that they dynamically appear back in the uncompleted actions area. 12. A tiny improvement: the toggling of the individual notes now uses Element.toggle from prototype.js, so it doesn't have to be an onLoad property of the body tag. This means that you don't get the notes flashing before they are hidden when you load or reload a page. +13. All the adding, updating, deleting and marking actions done is performed using Ajax, so happens without needing to refresh the page. +14. There's a new setting in settings.yml ('staleness_starts') which defines the number of days before which actions get marked as stale. Let's say you set it to 7 days. Actions created between 7 and 14 days ago get marked pale yellow, those created between 14 and 28 days ago (staleness_starts x 2) get marked darker yellow, and those created more than 28 days ago (staleness_starts x 3) are fluorescent yellow! This is only applied to items without a due date, so you can add items to be done some time in the future without getting yellow splashed all over the place (thanks to Nicholas for the patch to restrict to items with no due date). +15. Contexts and projects can now be sorted in any order you like. Arrow buttons on the /contexts and /projects pages let you move an item to the top, up, down or to the bottom. For contexts, this affects the order in which they sort on the home page. Position is also used to sort the listings of active/completed/hidden contexts and projects in the 'sidebar', and in the dropdown lists on forms. +16. You can mark projects as completed (by editing the project on /projects). In the 'sidebar' active and completed projects are shown separately, but you can still view the completed project. +17. New images (from eclipse.org) for the edit, delete, notes and up, down, top and bottom buttons. I've made a greyscale version for the default, then the coloured version gets loaded when the mouse is hovering over the button. == Version 1.02 From d73d1efefa4533d9992a7544b42acab0d0f5f7e6 Mon Sep 17 00:00:00 2001 From: Matt Rogers Date: Mon, 24 Oct 2011 12:36:14 -0500 Subject: [PATCH 04/42] Reformat some really long lines in the README The larger paragraphs now fit into 80 columns on the screen. --- README | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/README b/README index 3ba3299b..3b9a58bc 100644 --- a/README +++ b/README @@ -19,13 +19,30 @@ * Copyright: (cc) 2004-2011 rousette.org.uk. * License: GNU GPL -All the documentation for Tracks can be found within the /doc directory and at http://bsag.github.com/tracks/ -The latter includes full instructions for both new installations and upgrades from older installations of Tracks. -The instructions might appear long and intimidatingly complex, but that is mostly because of the number of different platforms supported, and the different configurations which can be used (e.g. running Tracks on your local computer or on a remote server). If you choose the appropriate section for your situation (installation vs. upgrade), and use the easiest (recommended) method, you should find the instructions easy to follow. If you encounter problems, try searching the wiki, forum or mailing list (URLs above), and ask a question if you cannot find a solution to your problem. +All the documentation for Tracks can be found within the /doc directory and at +http://bsag.github.com/tracks/ + +The latter includes full instructions for both new installations and upgrades +from older installations of Tracks. + +The instructions might appear long and intimidatingly complex, but that is +mostly because of the number of different platforms supported, and the +different configurations which can be used (e.g. running Tracks on your local +computer or on a remote server). If you choose the appropriate section for your +situation (installation vs. upgrade), and use the easiest (recommended) method, +you should find the instructions easy to follow. If you encounter problems, try +searching the wiki, forum or mailing list (URLs above), and ask a question if +you cannot find a solution to your problem. + The wiki has a lot of user contributed installation HOWTOs for various webhosts, specific OS's and more. -If you are thinking about contributing towards the development of Tracks, please read /doc/README_DEVELOPERS for general information, or /doc/tracks_api_wrapper.rb for information on Tracks' API. Also you can find some information on development on the wiki. +If you are thinking about contributing towards the development of Tracks, +please read /doc/README_DEVELOPERS for general information, or +/doc/tracks_api_wrapper.rb for information on Tracks' API. Also you can find +some information on development on the wiki. -While fully usable for everyday use, Tracks is still a work in progress. Make sure that you take sensible precautions and back up all your data frequently, taking particular care when you are upgrading. +While fully usable for everyday use, Tracks is still a work in progress. Make +sure that you take sensible precautions and back up all your data frequently, +taking particular care when you are upgrading. Enjoy being productive! From ab753007324fbbebdbd28d4147ffc5236a30f41a Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Sun, 13 Nov 2011 20:59:50 +0100 Subject: [PATCH 05/42] reducing number of groups in the Gemfile to make it easier digestible for Heroku et. al. --- Gemfile | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index 5163e0eb..a4a5320d 100644 --- a/Gemfile +++ b/Gemfile @@ -25,10 +25,6 @@ else gem "soap4r", "~>1.5.8" end -gem "webrat", ">=0.7.0", :groups => [:cucumber, :test] -gem "database_cleaner", ">=0.5.0", :groups => [:cucumber, :selenium] -gem "cucumber-rails", "~>0.3.0", :groups => :cucumber - group :development do if RUBY_VERSION.to_f >= 1.9 gem "ruby-debug19" @@ -49,8 +45,8 @@ group :test do gem "rspec-rails", "~>1.3.3" gem "thoughtbot-factory_girl" gem 'memory_test_fix', '~>0.1.3' -end - -group :selenium do gem "selenium-client" + gem "webrat", ">=0.7.0" + gem "database_cleaner", ">=0.5.0" + gem "cucumber-rails", "~>0.3.0" end From 5ff315dac6d354ee5508c95aab1607e0ce4a1b82 Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Thu, 6 Oct 2011 20:28:58 +0200 Subject: [PATCH 06/42] adding tests for creating todos via REST --- test/integration/todo_xml_api_test.rb | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/test/integration/todo_xml_api_test.rb b/test/integration/todo_xml_api_test.rb index c7892490..f1128c5e 100644 --- a/test/integration/todo_xml_api_test.rb +++ b/test/integration/todo_xml_api_test.rb @@ -49,6 +49,42 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_response :success assert_equal @user.todos.count, old_count + 1 end + + def test_post_create_todo_with_dependencies + old_count = @user.todos.count + authenticated_post_xml_to_todo_create " + + this will succeed 2 + 10 + 4 + + 12 + + + 12 + +" + + assert_response :success + assert_equal @user.todos.count, old_count + 1 + end + + def test_post_create_todo_with_tags + old_count = @user.todos.count + authenticated_post_xml_to_todo_create " + + this will succeed 2 + 10 + 4 + + starred + + " + + puts @response.body + assert_response :success + assert_equal @user.todos.count, old_count + 1 + end def test_post_create_todo_with_wrong_project_and_context_id authenticated_post_xml_to_todo_create "this will fail-16-11" From f08e73c81972fefc562dd9b710639cda11dd9c0c Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Thu, 6 Oct 2011 20:29:49 +0200 Subject: [PATCH 07/42] fixing todo creation when supplying dependencies --- app/models/todo.rb | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/models/todo.rb b/app/models/todo.rb index 06b23699..db5a7453 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -143,6 +143,19 @@ class Todo < ActiveRecord::Base end end + def predecessor_dependencies=(params) + value = params[:predecessor_dependencies] + if !value.nil? + if value.class == Array + value.each do |attrs| + predecessor_dependencies.build(attrs) + end + else + predecessor_dependencies.build(value) + end + end + end + def save_predecessors unless @predecessor_array.nil? # Only save predecessors if they changed current_array = self.predecessors @@ -177,6 +190,19 @@ class Todo < ActiveRecord::Base self.activate! end + def successor_dependencies=(params) + value = params[:successor_dependencies] + if !value.nil? + if value.class == Array + value.each do |attrs| + successor_dependencies.build(attrs) + end + else + successor_dependencies.build(value) + end + end + end + # Returns true if t is equal to self or a successor of self def is_successor?(todo) if self == todo From 3180164ed044061d26ba50348df3a5c7252631b7 Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Sun, 9 Oct 2011 20:35:50 +0200 Subject: [PATCH 08/42] indentation fix --- test/integration/todo_xml_api_test.rb | 30 +++++++++++++-------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/integration/todo_xml_api_test.rb b/test/integration/todo_xml_api_test.rb index f1128c5e..20171f99 100644 --- a/test/integration/todo_xml_api_test.rb +++ b/test/integration/todo_xml_api_test.rb @@ -69,22 +69,22 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_equal @user.todos.count, old_count + 1 end - def test_post_create_todo_with_tags - old_count = @user.todos.count - authenticated_post_xml_to_todo_create " - - this will succeed 2 - 10 - 4 - - starred - - " + def test_post_create_todo_with_tags + old_count = @user.todos.count + authenticated_post_xml_to_todo_create " + + this will succeed 2 + 10 + 4 + + starred + +" - puts @response.body - assert_response :success - assert_equal @user.todos.count, old_count + 1 - end + puts @response.body + assert_response :success + assert_equal @user.todos.count, old_count + 1 + end def test_post_create_todo_with_wrong_project_and_context_id authenticated_post_xml_to_todo_create "this will fail-16-11" From 9eae8a7068f156b64fa00a60ac994fc986887d27 Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Mon, 10 Oct 2011 22:25:51 +0200 Subject: [PATCH 09/42] improving todo creation tests and implement context, project and tag setting/creation --- app/models/todo.rb | 86 ++++++++++++++++++--------- test/integration/todo_xml_api_test.rb | 68 +++++++++++++++------ 2 files changed, 110 insertions(+), 44 deletions(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index db5a7453..fe31417c 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -143,19 +143,6 @@ class Todo < ActiveRecord::Base end end - def predecessor_dependencies=(params) - value = params[:predecessor_dependencies] - if !value.nil? - if value.class == Array - value.each do |attrs| - predecessor_dependencies.build(attrs) - end - else - predecessor_dependencies.build(value) - end - end - end - def save_predecessors unless @predecessor_array.nil? # Only save predecessors if they changed current_array = self.predecessors @@ -190,19 +177,6 @@ class Todo < ActiveRecord::Base self.activate! end - def successor_dependencies=(params) - value = params[:successor_dependencies] - if !value.nil? - if value.class == Array - value.each do |attrs| - successor_dependencies.build(attrs) - end - else - successor_dependencies.build(value) - end - end - end - # Returns true if t is equal to self or a successor of self def is_successor?(todo) if self == todo @@ -314,6 +288,8 @@ class Todo < ActiveRecord::Base end def add_predecessor(t) + return if t.nil? + @predecessor_array = predecessors @predecessor_array << t end @@ -335,7 +311,63 @@ class Todo < ActiveRecord::Base def raw_notes=(value) self[:notes] = value end - + + # XML API fixups + def predecessor_dependencies=(params) + value = params[:predecessor] + + if !value.nil? + if value.class == Array + value.each do |ele| + if ele.is_a? String + add_predecessor(self.user.todos.find_by_id(ele.to_i)) unless ele.blank? + else + predecessor_dependencies.build(value) + end + end + else + if ele.is_a? String + add_predecessor(self.user.todos.find_by_id(ele.to_i)) unless ele.blank? + else + predecessor_dependencies.build(value) + end + end + end + end + + alias_method :original_context=, :context= + def context=(value) + if value.is_a? Context + self.original_context=(value) + else + self.original_context=(Context.create(value)) + end + end + + alias_method :original_project=, :project= + def project=(value) + if value.is_a? Project + self.original_project=(value) + else + self.original_project=(Project.create(value)) + end + end + + alias_method :original_tags=, :tags= + def tags=(params) + value = params[:tag] + + if !value.nil? + if value.class == Array + value.each do |attrs| + tags.build(attrs) + end + else + tags.build(value) + end + end + end + # Rich Todo API def self.from_rich_message(user, default_context_id, description, notes) diff --git a/test/integration/todo_xml_api_test.rb b/test/integration/todo_xml_api_test.rb index 20171f99..21da3fdb 100644 --- a/test/integration/todo_xml_api_test.rb +++ b/test/integration/todo_xml_api_test.rb @@ -8,8 +8,8 @@ class TodoXmlApiTest < ActionController::IntegrationTest def setup assert_test_environment_ok - @user = users(:other_user) - @password = 'sesame' + @user = users(:admin_user) + @password = 'abracadabra' end def test_get_tickler_succeeds @@ -51,39 +51,73 @@ class TodoXmlApiTest < ActionController::IntegrationTest end def test_post_create_todo_with_dependencies - old_count = @user.todos.count authenticated_post_xml_to_todo_create " this will succeed 2 - 10 - 4 + 8 + 1 - 12 + 5 + 6 - - 12 - " assert_response :success - assert_equal @user.todos.count, old_count + 1 + todo = @user.todos.find_by_description("this will succeed 2") + assert_not_nil todo + assert !todo.uncompleted_predecessors.empty? end def test_post_create_todo_with_tags - old_count = @user.todos.count authenticated_post_xml_to_todo_create " - this will succeed 2 - 10 - 4 + this will succeed 3 + 8 + 1 starred + starred2 " - puts @response.body assert_response :success - assert_equal @user.todos.count, old_count + 1 + todo = @user.todos.find_by_description("this will succeed 3") + assert_not_nil todo + assert !todo.starred? + end + + def test_post_create_todo_with_new_context + authenticated_post_xml_to_todo_create " + + this will succeed 4 + 1 + + @SomeNewContext + +" + + assert_response :success + todo = @user.todos.find_by_description("this will succeed 4") + assert_not_nil todo + assert_not_nil todo.context + assert_equal todo.context.name, "@SomeNewContext" + end + + def test_post_create_todo_with_new_context + authenticated_post_xml_to_todo_create " + + this will succeed 5 + 8 + + Make even more money + +" + + assert_response :success + todo = @user.todos.find_by_description("this will succeed 5") + assert_not_nil todo + assert_not_nil todo.project + assert_equal todo.project.name, "Make even more money" end def test_post_create_todo_with_wrong_project_and_context_id @@ -101,7 +135,7 @@ class TodoXmlApiTest < ActionController::IntegrationTest private - def authenticated_post_xml_to_todo_create(postdata = @@valid_postdata, user = users(:other_user).login, password = 'sesame') + def authenticated_post_xml_to_todo_create(postdata = @@valid_postdata, user = @user.login, password = @password) authenticated_post_xml "/todos", user, password, postdata end From c5dd35de80f0e4f7b3132115d8bc4bdde26cec4b Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Wed, 12 Oct 2011 14:55:05 +0200 Subject: [PATCH 10/42] remove alias_method for tags= ... tags now broken again, but no longer the whole todo model --- app/models/todo.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/models/todo.rb b/app/models/todo.rb index fe31417c..dd309d94 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -353,7 +353,6 @@ class Todo < ActiveRecord::Base end end - alias_method :original_tags=, :tags= def tags=(params) value = params[:tag] From 081a55d6ac18bab5d117ec396365cd9013881c6e Mon Sep 17 00:00:00 2001 From: Stefan Richter Date: Sun, 13 Nov 2011 21:39:04 +0100 Subject: [PATCH 11/42] Fixup tests --- test/integration/todo_xml_api_test.rb | 33 +++++++++++++++------------ 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/test/integration/todo_xml_api_test.rb b/test/integration/todo_xml_api_test.rb index 21da3fdb..6b080a41 100644 --- a/test/integration/todo_xml_api_test.rb +++ b/test/integration/todo_xml_api_test.rb @@ -25,10 +25,10 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_response 401 end - def test_get_tickler_returns_all_deferred_todos - number = @user.todos.deferred.count + def test_get_tickler_returns_all_deferred_and_pending_todos + number = @user.todos.deferred.count + @user.todos.pending.count authenticated_get_xml "/tickler", @user.login, @password, {} - assert_tag :tag => "todos", :children => { :count => number, :only => { :tag => "todo" } } + assert_tag :tag => "todos", :children => { :count => number } end def test_get_tickler_omits_user_id @@ -36,13 +36,13 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_no_tag :tag => "user_id" end - def test_create_todo_via_xml_show_from + def test_create_todo_with_show_from old_count = @user.todos.count authenticated_post_xml_to_todo_create " Call Warren Buffet to find out how much he makes per day - #{projects(:attendrailsconf).id} - #{contexts(:office_otheruser).id} + #{contexts(:office).id} + #{projects(:timemachine).id} #{1.week.from_now.xmlschema} " @@ -54,8 +54,8 @@ class TodoXmlApiTest < ActionController::IntegrationTest authenticated_post_xml_to_todo_create " this will succeed 2 - 8 - 1 + #{contexts(:office).id} + #{projects(:timemachine).id} 5 6 @@ -72,8 +72,8 @@ class TodoXmlApiTest < ActionController::IntegrationTest authenticated_post_xml_to_todo_create " this will succeed 3 - 8 - 1 + #{contexts(:office).id} + #{projects(:timemachine).id} starred starred2 @@ -90,7 +90,7 @@ class TodoXmlApiTest < ActionController::IntegrationTest authenticated_post_xml_to_todo_create " this will succeed 4 - 1 + #{projects(:timemachine).id} @SomeNewContext @@ -103,11 +103,11 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_equal todo.context.name, "@SomeNewContext" end - def test_post_create_todo_with_new_context + def test_post_create_todo_with_new_project authenticated_post_xml_to_todo_create " this will succeed 5 - 8 + #{contexts(:office).id} Make even more money @@ -121,7 +121,12 @@ class TodoXmlApiTest < ActionController::IntegrationTest end def test_post_create_todo_with_wrong_project_and_context_id - authenticated_post_xml_to_todo_create "this will fail-16-11" + authenticated_post_xml_to_todo_create " + + this will fail + -16 + -11 +" assert_response 422 assert_xml_select 'errors' do assert_select 'error', 2 From 906ff11633aa13124bb7544dae455ca22084a613 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Wed, 16 Nov 2011 16:37:04 +0100 Subject: [PATCH 12/42] fix failing test and make sure that you can supply 'starred' tag. Refactor todo model --- app/controllers/todos_controller.rb | 9 ++- app/models/todo.rb | 34 ++++-------- lib/tagging_extensions.rb | 80 +++++++++++++-------------- test/integration/todo_xml_api_test.rb | 20 ++++--- 4 files changed, 71 insertions(+), 72 deletions(-) diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 405a44c6..efcba0f3 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -90,7 +90,7 @@ class TodosController < ApplicationController end if @todo.errors.empty? - @todo.starred= (params[:new_todo_starred]||"").include? "true" + @todo.starred= (params[:new_todo_starred]||"").include? "true" if params[:new_todo_starred] @todo.add_predecessor_list(predecessor_list) @@ -1516,6 +1516,13 @@ class TodosController < ApplicationController @params = params['request'] || params @prefs = prefs @attributes = params['request'] && params['request']['todo'] || params['todo'] + + if @attributes[:tags] + # the REST api may use which will collide with tags association, so rename tags to add_tags + add_tags = @attributes[:tags] + @attributes.delete :tags + @attributes[:add_tags] = add_tags + end end def attributes diff --git a/app/models/todo.rb b/app/models/todo.rb index dd309d94..cbe6d162 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -289,7 +289,7 @@ class Todo < ActiveRecord::Base def add_predecessor(t) return if t.nil? - + @predecessor_array = predecessors @predecessor_array << t end @@ -311,11 +311,11 @@ class Todo < ActiveRecord::Base def raw_notes=(value) self[:notes] = value end - + # XML API fixups def predecessor_dependencies=(params) value = params[:predecessor] - + if !value.nil? if value.class == Array value.each do |ele| @@ -325,7 +325,7 @@ class Todo < ActiveRecord::Base predecessor_dependencies.build(value) end end - else + else if ele.is_a? String add_predecessor(self.user.todos.find_by_id(ele.to_i)) unless ele.blank? else @@ -334,7 +334,7 @@ class Todo < ActiveRecord::Base end end end - + alias_method :original_context=, :context= def context=(value) if value.is_a? Context @@ -343,7 +343,7 @@ class Todo < ActiveRecord::Base self.original_context=(Context.create(value)) end end - + alias_method :original_project=, :project= def project=(value) if value.is_a? Project @@ -352,23 +352,13 @@ class Todo < ActiveRecord::Base self.original_project=(Project.create(value)) end end - - def tags=(params) - value = params[:tag] - - if !value.nil? - if value.class == Array - value.each do |attrs| - tags.build(attrs) - end - else - tags.build(value) - end - end - end - - # Rich Todo API + # used by the REST API. will also work, this is renamed to add_tags in TodosController::TodoCreateParamsHelper::initialize + def add_tags=(params) + tag_with params[:tag].inject([]) { |list, value| list << value[:name] } unless params[:tag].nil? + end + + # Rich Todo API def self.from_rich_message(user, default_context_id, description, notes) fields = description.match(/([^>@]*)@?([^>]*)>?(.*)/) description = fields[1].strip diff --git a/lib/tagging_extensions.rb b/lib/tagging_extensions.rb index 3a992efa..2808f42a 100644 --- a/lib/tagging_extensions.rb +++ b/lib/tagging_extensions.rb @@ -2,7 +2,7 @@ class ActiveRecord::Base #:nodoc: # These extensions make models taggable. This file is automatically generated and required by your app if you run the tagging generator included with has_many_polymorphs. module TaggingExtensions - + # Add tags to self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags. # # We need to avoid name conflicts with the built-in ActiveRecord association methods, thus the underscores. @@ -21,8 +21,8 @@ class ActiveRecord::Base #:nodoc: end end end - - # Removes tags from self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags. + + # Removes tags from self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags. def _remove_tags outgoing taggable?(true) outgoing = tag_cast_to_string(outgoing) @@ -35,20 +35,20 @@ class ActiveRecord::Base #:nodoc: def tag_list # Redefined later to avoid an RDoc parse error. end - + # Replace the existing tags on self. Accepts a string of tagnames, an array of tagnames, an array of ids, or an array of Tags. - def tag_with list + def tag_with list #:stopdoc: taggable?(true) list = tag_cast_to_string(list) - + # Transactions may not be ideal for you here; be aware. - Tag.transaction do + Tag.transaction do current = tags.map(&:name) _add_tags(list - current) _remove_tags(current - list) end - + self #:startdoc: end @@ -64,10 +64,10 @@ class ActiveRecord::Base #:nodoc: def tag_list=(value) tag_with(value) - end + end + + private - private - def tag_cast_to_string obj #:nodoc: case obj when Array @@ -88,9 +88,9 @@ class ActiveRecord::Base #:nodoc: else raise "Invalid object of class #{obj.class} as tagging method parameter" end.flatten.compact.map(&:downcase).uniq - end - - # Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model. + end + + # Check if a model is in the :taggables target list. The alternative to this check is to explicitly include a TaggingMethods module (which you would create) in each target model. def taggable?(should_raise = false) #:nodoc: unless flag = respond_to?(:tags) raise "#{self.class} is not a taggable model" if should_raise @@ -99,69 +99,69 @@ class ActiveRecord::Base #:nodoc: end end - + module TaggingFinders # Find all the objects tagged with the supplied list of tags - # + # # Usage : Model.tagged_with("ruby") # Model.tagged_with("hello", "world") # Model.tagged_with("hello", "world", :limit => 10) # # XXX This query strategy is not performant, and needs to be rewritten as an inverted join or a series of unions - # + # def tagged_with(*tag_list) options = tag_list.last.is_a?(Hash) ? tag_list.pop : {} tag_list = parse_tags(tag_list) - + scope = scope(:find) options[:select] ||= "#{table_name}.*" options[:from] ||= "#{table_name}, tags, taggings" - + sql = "SELECT #{(scope && scope[:select]) || options[:select]} " sql << "FROM #{(scope && scope[:from]) || options[:from]} " add_joins!(sql, options[:joins], scope) - + sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' " sql << "AND taggings.tag_id = tags.id " - + tag_list_condition = tag_list.map {|name| "'#{name}'"}.join(", ") - + sql << "AND (tags.name IN (#{sanitize_sql(tag_list_condition)})) " sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions] - - columns = column_names.map do |column| + + columns = column_names.map do |column| "#{table_name}.#{column}" end.join(", ") - + sql << "GROUP BY #{columns} " sql << "HAVING COUNT(taggings.tag_id) = #{tag_list.size}" - + add_order!(sql, options[:order], scope) add_limit!(sql, options, scope) add_lock!(sql, options, scope) - + find_by_sql(sql) end - + def self.tagged_with_any(*tag_list) options = tag_list.last.is_a?(Hash) ? tag_list.pop : {} tag_list = parse_tags(tag_list) - + scope = scope(:find) options[:select] ||= "#{table_name}.*" options[:from] ||= "#{table_name}, meta_tags, taggings" - + sql = "SELECT #{(scope && scope[:select]) || options[:select]} " sql << "FROM #{(scope && scope[:from]) || options[:from]} " add_joins!(sql, options, scope) - + sql << "WHERE #{table_name}.#{primary_key} = taggings.taggable_id " sql << "AND taggings.taggable_type = '#{ActiveRecord::Base.send(:class_name_of_active_record_descendant, self).to_s}' " sql << "AND taggings.meta_tag_id = meta_tags.id " - + sql << "AND (" or_options = [] tag_list.each do |name| @@ -169,30 +169,30 @@ class ActiveRecord::Base #:nodoc: end or_options_joined = or_options.join(" OR ") sql << "#{or_options_joined}) " - - + + sql << "AND #{sanitize_sql(options[:conditions])} " if options[:conditions] - + columns = column_names.map do |column| "#{table_name}.#{column}" end.join(", ") - + sql << "GROUP BY #{columns} " - + add_order!(sql, options[:order], scope) add_limit!(sql, options, scope) add_lock!(sql, options, scope) - + find_by_sql(sql) end - + def parse_tags(tags) return [] if tags.blank? tags = Array(tags).first tags = tags.respond_to?(:flatten) ? tags.flatten : tags.split(Tag::DELIMITER) tags.map { |tag| tag.strip.squeeze(" ") }.flatten.compact.map(&:downcase).uniq end - + end include TaggingExtensions diff --git a/test/integration/todo_xml_api_test.rb b/test/integration/todo_xml_api_test.rb index 6b080a41..2a5ea739 100644 --- a/test/integration/todo_xml_api_test.rb +++ b/test/integration/todo_xml_api_test.rb @@ -35,7 +35,7 @@ class TodoXmlApiTest < ActionController::IntegrationTest authenticated_get_xml "/tickler", @user.login, @password, {} assert_no_tag :tag => "user_id" end - + def test_create_todo_with_show_from old_count = @user.todos.count authenticated_post_xml_to_todo_create " @@ -49,7 +49,7 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_response :success assert_equal @user.todos.count, old_count + 1 end - + def test_post_create_todo_with_dependencies authenticated_post_xml_to_todo_create " @@ -67,7 +67,7 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_not_nil todo assert !todo.uncompleted_predecessors.empty? end - + def test_post_create_todo_with_tags authenticated_post_xml_to_todo_create " @@ -76,6 +76,7 @@ class TodoXmlApiTest < ActionController::IntegrationTest #{projects(:timemachine).id} starred + starred1 starred2 " @@ -83,9 +84,10 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_response :success todo = @user.todos.find_by_description("this will succeed 3") assert_not_nil todo - assert !todo.starred? + assert_equal "starred, starred1, starred2", todo.tag_list + assert todo.starred? end - + def test_post_create_todo_with_new_context authenticated_post_xml_to_todo_create " @@ -95,14 +97,14 @@ class TodoXmlApiTest < ActionController::IntegrationTest @SomeNewContext " - + assert_response :success todo = @user.todos.find_by_description("this will succeed 4") assert_not_nil todo assert_not_nil todo.context assert_equal todo.context.name, "@SomeNewContext" end - + def test_post_create_todo_with_new_project authenticated_post_xml_to_todo_create " @@ -132,8 +134,8 @@ class TodoXmlApiTest < ActionController::IntegrationTest assert_select 'error', 2 end end - - def test_fails_with_401_if_not_authorized_user + + def test_fails_with_401_if_not_authorized_user authenticated_post_xml_to_todo_create '', 'nobody', 'nohow' assert_response 401 end From 833297b355ab6ba866aa2328af20dc7db1fc6122 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Wed, 16 Nov 2011 19:36:09 +0100 Subject: [PATCH 13/42] fix regression and further cleanups of todo model --- app/controllers/todos_controller.rb | 2 +- app/models/todo.rb | 41 +++++++++++------------------ 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index efcba0f3..b01d1dc5 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -1517,7 +1517,7 @@ class TodosController < ApplicationController @prefs = prefs @attributes = params['request'] && params['request']['todo'] || params['todo'] - if @attributes[:tags] + if @attributes && @attributes[:tags] # the REST api may use which will collide with tags association, so rename tags to add_tags add_tags = @attributes[:tags] @attributes.delete :tags diff --git a/app/models/todo.rb b/app/models/todo.rb index cbe6d162..67c6db17 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -47,13 +47,6 @@ class Todo < ActiveRecord::Base STARRED_TAG_NAME = "starred" DEFAULT_INCLUDES = [ :project, :context, :tags, :taggings, :pending_successors, :uncompleted_predecessors, :recurring_todo ] - # regular expressions for dependencies. TODO: are these still used? - RE_TODO = /[^']+/ - RE_CONTEXT = /[^']+/ - RE_PROJECT = /[^']+/ - RE_PARTS = /'(#{RE_TODO})'\s<'(#{RE_CONTEXT})';\s'(#{RE_PROJECT})'>/ # results in array - RE_SPEC = /'#{RE_TODO}'\s<'#{RE_CONTEXT}';\s'#{RE_PROJECT}'>/ # results in string - # state machine include AASM aasm_column :state @@ -113,12 +106,11 @@ class Todo < ActiveRecord::Base def no_uncompleted_predecessors_or_deferral? no_deferral = show_from.blank? or Time.zone.now > show_from - no_uncompleted_predecessors = uncompleted_predecessors.all(true).empty? - return (no_deferral && no_uncompleted_predecessors) + return (no_deferral && no_uncompleted_predecessors?) end def no_uncompleted_predecessors? - return uncompleted_predecessors.all(true).empty? + return !uncompleted_predecessors? end def uncompleted_predecessors? @@ -197,8 +189,8 @@ class Todo < ActiveRecord::Base return !pending_successors.empty? end - def has_tag?(tag) - return self.tags.select{|t| t.name==tag }.size > 0 + def has_tag?(tag_name) + return self.tags.any? {|tag| tag.name == tag_name} end def hidden? @@ -239,12 +231,6 @@ class Todo < ActiveRecord::Base defer! if active? && !date.blank? && date > user.date end - alias_method :original_project, :project - - def project - original_project.nil? ? Project.null_object : original_project - end - def self.feed_options(user) { :title => 'Tracks Actions', @@ -253,7 +239,7 @@ class Todo < ActiveRecord::Base end def starred? - tags.any? {|tag| tag.name == STARRED_TAG_NAME} + return has_tag?(STARRED_TAG_NAME) end def toggle_star! @@ -276,12 +262,10 @@ class Todo < ActiveRecord::Base def add_predecessor_list(predecessor_list) return unless predecessor_list.kind_of? String - @predecessor_array=[] - - predecessor_ids_array = predecessor_list.split(",") - predecessor_ids_array.each do |todo_id| + @predecessor_array=predecessor_list.split(",").inject([]) do |list, todo_id| predecessor = self.user.todos.find_by_id( todo_id.to_i ) unless todo_id.blank? - @predecessor_array << predecessor unless predecessor.nil? + list << predecessor unless predecessor.nil? + list end return @predecessor_array @@ -344,12 +328,19 @@ class Todo < ActiveRecord::Base end end + alias_method :original_project, :project + def project + original_project.nil? ? Project.null_object : original_project + end + alias_method :original_project=, :project= def project=(value) if value.is_a? Project self.original_project=(value) - else + elsif !value.nil? self.original_project=(Project.create(value)) + else + self.original_project=value end end From 4b6aff55020abcafc1f95ba3e728a78ab36f44bd Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Wed, 16 Nov 2011 22:05:06 +0100 Subject: [PATCH 14/42] hopefully fix failing recurring todos test. Timezones are a pain to get right --- app/controllers/application_controller.rb | 2 +- app/models/recurring_todo.rb | 186 +++++++++++----------- test/functional/todos_controller_test.rb | 4 +- 3 files changed, 97 insertions(+), 95 deletions(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6f75ea61..f2c55bb3 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -177,9 +177,9 @@ class ApplicationController < ActionController::Base def create_todo_from_recurring_todo(rt, date=nil) # create todo and initialize with data from recurring_todo rt todo = current_user.todos.build( { :description => rt.description, :notes => rt.notes, :project_id => rt.project_id, :context_id => rt.context_id}) + todo.recurring_todo_id = rt.id # set dates - todo.recurring_todo_id = rt.id todo.due = rt.get_due_date(date) show_from_date = rt.get_show_from_date(date) diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index e1ff6003..913ee2ff 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -1,5 +1,5 @@ class RecurringTodo < ActiveRecord::Base - + belongs_to :context belongs_to :project belongs_to :user @@ -10,11 +10,11 @@ class RecurringTodo < ActiveRecord::Base named_scope :completed, :conditions => { :state => 'completed'} attr_protected :user - + include AASM aasm_column :state aasm_initial_state :active - + aasm_state :active, :enter => Proc.new { |t| t.occurences_count = 0 } aasm_state :completed, :enter => Proc.new { |t| t.completed_at = Time.zone.now }, :exit => Proc.new { |t| t.completed_at = nil } @@ -25,7 +25,7 @@ class RecurringTodo < ActiveRecord::Base aasm_event :activate do transitions :to => :active, :from => [:completed] end - + validates_presence_of :description validates_presence_of :recurring_period validates_presence_of :target @@ -33,12 +33,12 @@ class RecurringTodo < ActiveRecord::Base validates_presence_of :context validates_length_of :description, :maximum => 100 - validates_length_of :notes, :maximum => 60000, :allow_nil => true + validates_length_of :notes, :maximum => 60000, :allow_nil => true validate :period_specific_validations validate :starts_and_ends_on_validations validate :set_recurrence_on_validations - + def period_specific_validations if %W[daily weekly monthly yearly].include?(recurring_period) self.send("validate_#{recurring_period}") @@ -118,7 +118,7 @@ class RecurringTodo < ActiveRecord::Base raise Exception.new, "unexpected value of recurrence target selector '#{self.recurrence_target}'" end end - + # the following recurrence patterns can be stored: # # daily todos - recurrence_period = 'daily' @@ -140,9 +140,9 @@ class RecurringTodo < ActiveRecord::Base # x is stored in every_other3, y is stored in every_count, z is stored in every_other2 # choosing between both options is done on recurrence_selector where 0 is # for first type and 1 for second type - + # DAILY - + def daily_selector=(selector) case selector when 'daily_every_x_day' @@ -153,34 +153,34 @@ class RecurringTodo < ActiveRecord::Base raise Exception.new, "unknown daily recurrence pattern: '#{selector}'" end end - + def daily_every_x_days=(x) if recurring_period=='daily' self.every_other1 = x end end - + def daily_every_x_days return self.every_other1 end - + # WEEKLY - + def weekly_every_x_week=(x) self.every_other1 = x if recurring_period=='weekly' end - + def weekly_every_x_week return self.every_other1 end - + def switch_week_day (day, position) if self.every_day.nil? self.every_day=' ' end self.every_day = self.every_day[0,position] + day + self.every_day[position+1,self.every_day.length] end - + def weekly_return_monday=(selector) switch_week_day(selector,1) if recurring_period=='weekly' end @@ -188,15 +188,15 @@ class RecurringTodo < ActiveRecord::Base def weekly_return_tuesday=(selector) switch_week_day(selector,2) if recurring_period=='weekly' end - + def weekly_return_wednesday=(selector) switch_week_day(selector,3) if recurring_period=='weekly' end - + def weekly_return_thursday=(selector) switch_week_day(selector,4) if recurring_period=='weekly' end - + def weekly_return_friday=(selector) switch_week_day(selector,5) if recurring_period=='weekly' end @@ -204,11 +204,11 @@ class RecurringTodo < ActiveRecord::Base def weekly_return_saturday=(selector) switch_week_day(selector,6) if recurring_period=='weekly' end - + def weekly_return_sunday=(selector) switch_week_day(selector,0) if recurring_period=='weekly' end - + def on_xday(n) unless self.every_day.nil? return self.every_day[n,1] == ' ' ? false : true @@ -216,51 +216,51 @@ class RecurringTodo < ActiveRecord::Base return false end end - + def on_monday return on_xday(1) end - + def on_tuesday return on_xday(2) end - + def on_wednesday return on_xday(3) end - + def on_thursday return on_xday(4) end - + def on_friday return on_xday(5) end - + def on_saturday return on_xday(6) end - + def on_sunday return on_xday(0) end # MONTHLY - + def monthly_selector=(selector) if recurring_period=='monthly' self.recurrence_selector= (selector=='monthly_every_x_day')? 0 : 1 end end - + def monthly_every_x_day=(x) self.every_other1 = x if recurring_period=='monthly' end - + def monthly_every_x_day return self.every_other1 end - + def is_monthly_every_x_day return self.recurrence_selector == 0 if recurring_period == 'monthly' return false @@ -270,11 +270,11 @@ class RecurringTodo < ActiveRecord::Base return self.recurrence_selector == 1 if recurring_period == 'monthly' return false end - + def monthly_every_x_month=(x) self.every_other2 = x if recurring_period=='monthly' && recurrence_selector == 0 end - + def monthly_every_x_month # in case monthly pattern is every day x, return every_other2 otherwise # return a default value @@ -298,36 +298,36 @@ class RecurringTodo < ActiveRecord::Base return 1 end end - + def monthly_every_xth_day=(x) self.every_other3 = x if recurring_period=='monthly' end - + def monthly_every_xth_day(default=nil) return self.every_other3 unless self.every_other3.nil? return default end - + def monthly_day_of_week=(dow) self.every_count = dow if recurring_period=='monthly' end - + def monthly_day_of_week return self.every_count end - + # YEARLY - + def yearly_selector=(selector) if recurring_period=='yearly' self.recurrence_selector = (selector=='yearly_every_x_day') ? 0 : 1 end end - + def yearly_month_of_year=(moy) self.every_other2 = moy if self.recurring_period=='yearly' && self.recurrence_selector == 0 end - + def yearly_month_of_year # if recurrence pattern is every x day in a month, return month otherwise # return a default value @@ -341,7 +341,7 @@ class RecurringTodo < ActiveRecord::Base def yearly_month_of_year2=(moy) self.every_other2 = moy if self.recurring_period=='yearly' && self.recurrence_selector == 1 end - + def yearly_month_of_year2 # if recurrence pattern is every xth day in a month, return month otherwise # return a default value @@ -351,33 +351,33 @@ class RecurringTodo < ActiveRecord::Base return Time.zone.now.month end end - + def yearly_every_x_day=(x) self.every_other1 = x if recurring_period=='yearly' end - + def yearly_every_x_day return self.every_other1 end - + def yearly_every_xth_day=(x) self.every_other3 = x if recurring_period=='yearly' end - + def yearly_every_xth_day return self.every_other3 end - + def yearly_day_of_week=(dow) self.every_count=dow if recurring_period=='yearly' end - + def yearly_day_of_week return self.every_count end - + # target - + def recurring_target=(t) self.target = t end @@ -392,11 +392,11 @@ class RecurringTodo < ActiveRecord::Base raise Exception.new, "unexpected value of recurrence target '#{self.target}'" end end - + def recurring_show_days_before=(days) self.show_from_delta=days end - + def recurring_show_always=(value) self.show_always=value end @@ -435,41 +435,41 @@ class RecurringTodo < ActiveRecord::Base else n_months = I18n.t('common.month') end - return I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months', + return I18n.t('todos.recurrence.pattern.every_xth_day_of_every_n_months', :x => self.xth, :day => self.day_of_week, :n_months => n_months) end when 'yearly' if self.recurrence_selector == 0 - return I18n.t("todos.recurrence.pattern.every_year_on", + return I18n.t("todos.recurrence.pattern.every_year_on", :date => I18n.l(DateTime.new(Time.zone.now.year, self.every_other2, self.every_other1), :format => :month_day)) else - return I18n.t("todos.recurrence.pattern.every_year_on", + return I18n.t("todos.recurrence.pattern.every_year_on", :date => I18n.t("todos.recurrence.pattern.the_xth_day_of_month", :x => self.xth, :day => self.day_of_week, :month => self.month_of_year)) end else return 'unknown recurrence pattern: period unknown' end end - + def xth xth_day = [ I18n.t('todos.recurrence.pattern.first'),I18n.t('todos.recurrence.pattern.second'),I18n.t('todos.recurrence.pattern.third'), I18n.t('todos.recurrence.pattern.fourth'),I18n.t('todos.recurrence.pattern.last')] return self.every_other3.nil? ? '??' : xth_day[self.every_other3-1] end - + def day_of_week return (self.every_count.nil? ? '??' : I18n.t('todos.recurrence.pattern.day_names')[self.every_count]) end - + def month_of_year return self.every_other2.nil? ? '??' : I18n.t('todos.recurrence.pattern.month_names')[self.every_other2] end - + def starred? tags.any? {|tag| tag.name == Todo::STARRED_TAG_NAME } end - + def get_due_date(previous) case self.target when 'due_date' @@ -481,7 +481,7 @@ class RecurringTodo < ActiveRecord::Base raise Exception.new, "unexpected value of recurrence target '#{self.target}'" end end - + def get_show_from_date(previous) case self.target when 'due_date' @@ -498,7 +498,7 @@ class RecurringTodo < ActiveRecord::Base raise Exception.new, "unexpected value of recurrence target '#{self.target}'" end end - + def get_next_date(previous) case self.recurring_period when 'daily' @@ -513,14 +513,14 @@ class RecurringTodo < ActiveRecord::Base raise Exception.new, "unknown recurrence pattern: '#{self.recurring_period}'" end end - + def get_daily_date(previous) # previous is the due date of the previous todo or it is the completed_at # date when the completed_at date is after due_date (i.e. you did not make # the due date in time) # # assumes self.recurring_period == 'daily' - + start = determine_start(previous, 1.day) if self.only_work_days @@ -533,7 +533,7 @@ class RecurringTodo < ActiveRecord::Base return previous == nil ? start : start+every_other1.day-1.day end end - + def get_weekly_date(previous) # determine start if previous == nil @@ -551,12 +551,12 @@ class RecurringTodo < ActiveRecord::Base start = self.start_from if self.start_from > previous end end - + # check if there are any days left this week for the next todo start.wday().upto 6 do |i| return start + (i-start.wday()).days unless self.every_day[i,1] == ' ' end - + # we did not find anything this week, so check the nth next, starting from # sunday start = start + self.every_other1.week - (start.wday()).days @@ -568,9 +568,9 @@ class RecurringTodo < ActiveRecord::Base raise Exception.new, "unable to find next weekly date (#{self.every_day})" end - + def get_monthly_date(previous) - + start = determine_start(previous) day = self.every_other1 n = self.every_other2 @@ -594,7 +594,7 @@ class RecurringTodo < ActiveRecord::Base # go back to day end return Time.zone.local(start.year, start.month, day) - + when 1 # relative weekday of a month the_next = get_xth_day_of_month(self.every_other3, self.every_count, start.month, start.year) if the_next.nil? || the_next <= start @@ -618,7 +618,7 @@ class RecurringTodo < ActiveRecord::Base end return nil end - + def get_xth_day_of_month(x, weekday, month, year) if x == 5 # last -> count backwards. use UTC to avoid strange timezone oddities @@ -646,12 +646,12 @@ class RecurringTodo < ActiveRecord::Base return Time.zone.local(start.year, start.month, start.day) end end - + def get_yearly_date(previous) start = determine_start(previous) day = self.every_other1 month = self.every_other2 - + case self.recurrence_selector when 0 # specific day of a specific month if start.month > month || (start.month == month && start.day >= day) @@ -663,25 +663,25 @@ class RecurringTodo < ActiveRecord::Base start = Time.zone.local(start.year, month, 1) end return Time.zone.local(start.year, month, day) - + when 1 # relative weekday of a specific month # if there is no next month n in this year, search in next year the_next = start.month > month ? Time.zone.local(start.year+1, month, 1) : start - + # get the xth day of the month the_next = get_xth_day_of_month(self.every_other3, self.every_count, month, the_next.year) - + # if the_next is before previous, we went back into the past, so try next # year the_next = get_xth_day_of_month(self.every_other3, self.every_count, month, start.year+1) if the_next <= start - + return the_next else raise Exception.new, "unknown monthly recurrence selection (#{self.recurrence_selector})" end return nil end - + def has_next_todo(previous) unless self.number_of_occurences.nil? return self.occurences_count < self.number_of_occurences @@ -700,11 +700,11 @@ class RecurringTodo < ActiveRecord::Base end end end - + def toggle_completion! return completed? ? activate! : complete! end - + def toggle_star! if starred? _remove_tags Todo::STARRED_TAG_NAME @@ -715,7 +715,7 @@ class RecurringTodo < ActiveRecord::Base end starred? end - + def remove_from_project! self.project = nil self.save @@ -729,17 +729,18 @@ class RecurringTodo < ActiveRecord::Base end end end - + def inc_occurences self.occurences_count += 1 self.save end - + protected - + + # Determine start date to calculate next date for recurring todo + # offset needs to be 1.day for daily patterns def determine_start(previous, offset=0.day) - # offset needs to be 1.day for daily patterns - + if previous.nil? start = self.start_from.nil? ? Time.zone.now : self.start_from # skip to present @@ -747,13 +748,12 @@ class RecurringTodo < ActiveRecord::Base else start = previous + offset - unless self.start_from.nil? - # check if the start_from date is later than previous. If so, use - # start_from as start to search for next date - start = self.start_from if self.start_from > previous - end + # check if the start_from date is later than previous. If so, use + # start_from as start to search for next date + start = self.start_from if ( self.start_from && self.start_from > previous ) end + return start end - + end diff --git a/test/functional/todos_controller_test.rb b/test/functional/todos_controller_test.rb index e8003eac..e100ac1e 100644 --- a/test/functional/todos_controller_test.rb +++ b/test/functional/todos_controller_test.rb @@ -530,7 +530,9 @@ class TodosControllerTest < ActionController::TestCase # check that the new_todo is in the tickler to show next month assert !new_todo.show_from.nil? - next_month = today + 1.month + # do not use today here. It somehow gets messed up with the timezone calculation. + next_month = (Time.zone.now + 1.month).at_midnight + assert_equal next_month.utc.to_date.to_s(:db), new_todo.show_from.utc.to_date.to_s(:db) end From 6aa8b8d2f9814a0e767b1d7c5504f2576ce430b6 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Thu, 17 Nov 2011 17:07:55 +0100 Subject: [PATCH 15/42] replace new repeating todo form with jquery dialog. And some refactoring --- app/controllers/recurring_todos_controller.rb | 81 ++++++----- app/models/recurring_todo.rb | 6 +- .../recurring_todos/_recurring_todo.html.erb | 7 +- .../recurring_todos/_recurring_todo_form.erb | 23 +-- app/views/recurring_todos/create.js.erb | 2 +- app/views/recurring_todos/index.html.erb | 15 +- public/javascripts/application.js | 131 ++++++++++------- public/stylesheets/standard.css | 24 +--- .../recurring_todos_controller_test.rb | 134 +++++++++++------- 9 files changed, 228 insertions(+), 195 deletions(-) diff --git a/app/controllers/recurring_todos_controller.rb b/app/controllers/recurring_todos_controller.rb index dd34bb1b..69517597 100644 --- a/app/controllers/recurring_todos_controller.rb +++ b/app/controllers/recurring_todos_controller.rb @@ -9,8 +9,8 @@ class RecurringTodosController < ApplicationController @page_title = t('todos.recurring_actions_title') @source_view = params['_source_view'] || 'recurring_todo' find_and_inactivate - @recurring_todos = current_user.recurring_todos.active - @completed_recurring_todos = current_user.recurring_todos.completed.find(:all, :limit => 10) + @recurring_todos = current_user.recurring_todos.active.find(:all, :include => [:tags, :taggings]) + @completed_recurring_todos = current_user.recurring_todos.completed.find(:all, :limit => 10, :include => [:tags, :taggings]) @no_recurring_todos = @recurring_todos.size == 0 @no_completed_recurring_todos = @completed_recurring_todos.size == 0 @@ -21,15 +21,15 @@ class RecurringTodosController < ApplicationController def new end - + def show end - + def done @page_title = t('todos.completed_recurring_actions_title') @source_view = params['_source_view'] || 'recurring_todo' items_per_page = 20 - page = params[:page] || 1 + page = params[:page] || 1 @completed_recurring_todos = current_user.recurring_todos.completed.paginate :page => params[:page], :per_page => items_per_page @total = @count = current_user.recurring_todos.completed.count @range_low = (page.to_i-1) * items_per_page + 1 @@ -54,7 +54,7 @@ class RecurringTodosController < ApplicationController params['recurring_todo']['recurring_period']=params['recurring_edit_todo']['recurring_period'] params['recurring_todo']['end_date']=parse_date_per_user_prefs(params['recurring_todo_edit_end_date']) params['recurring_todo']['start_from']=parse_date_per_user_prefs(params['recurring_todo_edit_start_from']) - + # update project if params['recurring_todo']['project_id'].blank? && !params['project_name'].nil? if params['project_name'] == 'None' @@ -70,7 +70,7 @@ class RecurringTodosController < ApplicationController end params["recurring_todo"]["project_id"] = project.id end - + # update context if params['recurring_todo']['context_id'].blank? && !params['context_name'].blank? context = current_user.contexts.find_by_name(params['context_name'].strip) @@ -88,14 +88,14 @@ class RecurringTodosController < ApplicationController %w{monday tuesday wednesday thursday friday saturday sunday}.each do |day| params["recurring_todo"]["weekly_return_"+day]=' ' if params["recurring_todo"]["weekly_return_"+day].nil? end - + @saved = @recurring_todo.update_attributes params["recurring_todo"] respond_to do |format| format.js end end - + def create p = RecurringTodoCreateParamsHelper.new(params) p.attributes['end_date']=parse_date_per_user_prefs(p.attributes['end_date']) @@ -109,7 +109,7 @@ class RecurringTodosController < ApplicationController @new_project_created = project.new_record_before_save? @recurring_todo.project_id = project.id end - + if p.context_specified_by_name? context = current_user.contexts.find_or_create_by_name(p.context_name) @new_context_created = context.new_record_before_save? @@ -134,13 +134,13 @@ class RecurringTodosController < ApplicationController @new_recurring_todo = RecurringTodo.new else @status_message = t('todos.error_saving_recurring') - end - + end + respond_to do |format| - format.js + format.js end end - + def destroy # remove all references to this recurring todo @todos = @recurring_todo.todos @@ -149,16 +149,16 @@ class RecurringTodosController < ApplicationController t.recurring_todo_id = nil t.save end - + # delete the recurring todo @saved = @recurring_todo.destroy # count remaining recurring todos @active_remaining = current_user.recurring_todos.active.count @completed_remaining = current_user.recurring_todos.completed.count - + respond_to do |format| - + format.html do if @saved notify :notice, t('todos.recurring_deleted_success'), 2.0 @@ -168,10 +168,10 @@ class RecurringTodosController < ApplicationController redirect_to :action => 'index' end end - + format.js do render - end + end end end @@ -184,19 +184,19 @@ class RecurringTodosController < ApplicationController if @recurring_todo.active? @completed_remaining = current_user.recurring_todos.completed.count - + # from completed back to active -> check if there is an active todo @active_todos = @recurring_todo.todos.active.count # create todo if there is no active todo belonging to the activated # recurring_todo @new_recurring_todo = create_todo_from_recurring_todo(@recurring_todo) if @active_todos == 0 end - + respond_to do |format| - format.js + format.js end end - + def toggle_star @recurring_todo.toggle_star! @saved = @recurring_todo.save! @@ -204,13 +204,13 @@ class RecurringTodosController < ApplicationController format.js end end - + class RecurringTodoCreateParamsHelper def initialize(params) @params = params['request'] || params @attributes = params['request'] && params['request']['recurring_todo'] || params['recurring_todo'] - + # make sure all selectors (recurring_period, recurrence_selector, # daily_selector, monthly_selector and yearly_selector) are first in hash # so that they are processed first by the model @@ -221,47 +221,47 @@ class RecurringTodosController < ApplicationController 'yearly_selector' => @attributes['yearly_selector'] } end - + def attributes @attributes end - + def selector_attributes return @selector_attributes end - + def project_name @params['project_name'].strip unless @params['project_name'].nil? end - + def context_name @params['context_name'].strip unless @params['context_name'].nil? end - + def tag_list @params['tag_list'] end - + def project_specified_by_name? return false unless @attributes['project_id'].blank? return false if project_name.blank? return false if project_name == 'None' true end - + def context_specified_by_name? return false unless @attributes['context_id'].blank? return false if context_name.blank? true end - + end private - + def init @days_of_week = [] - 0.upto 6 do |i| + 0.upto 6 do |i| @days_of_week << [t('date.day_names')[i], i] end @@ -274,15 +274,18 @@ class RecurringTodosController < ApplicationController @projects = current_user.projects.find(:all, :include => [:default_context]) @contexts = current_user.contexts.find(:all) end - + def get_recurring_todo_from_param @recurring_todo = current_user.recurring_todos.find(params[:id]) end def find_and_inactivate # find active recurring todos without active todos and inactivate them - recurring_todos = current_user.recurring_todos.active - recurring_todos.each { |rt| rt.toggle_completion! if rt.todos.not_completed.count == 0} + + current_user.recurring_todos.active.all( + :select => "recurring_todos.id, recurring_todos.state", + :joins => "LEFT JOIN todos fai_todos ON (recurring_todos.id = fai_todos.recurring_todo_id) AND (NOT fai_todos.state='completed')", + :conditions => "fai_todos.id IS NULL").each { |rt| current_user.recurring_todos.find(rt.id).toggle_completion! } end - + end diff --git a/app/models/recurring_todo.rb b/app/models/recurring_todo.rb index 913ee2ff..634b242b 100644 --- a/app/models/recurring_todo.rb +++ b/app/models/recurring_todo.rb @@ -467,7 +467,11 @@ class RecurringTodo < ActiveRecord::Base end def starred? - tags.any? {|tag| tag.name == Todo::STARRED_TAG_NAME } + return has_tag?(Todo::STARRED_TAG_NAME) + end + + def has_tag?(tag_name) + return self.tags.any? {|tag| tag.name == tag_name} end def get_due_date(previous) diff --git a/app/views/recurring_todos/_recurring_todo.html.erb b/app/views/recurring_todos/_recurring_todo.html.erb index 3abafc4a..2bff4883 100644 --- a/app/views/recurring_todos/_recurring_todo.html.erb +++ b/app/views/recurring_todos/_recurring_todo.html.erb @@ -1,8 +1,9 @@ <% @recurring_todo = recurring_todo -%>
- <%= recurring_todo_remote_delete_icon %> <%= recurring_todo_remote_edit_icon -%> - - <%= recurring_todo_remote_star_icon %> <%= recurring_todo_remote_toggle_checkbox -%> + <%= recurring_todo_remote_delete_icon %> + <%= recurring_todo_remote_edit_icon -%> + <%= recurring_todo_remote_star_icon %> + <%= recurring_todo_remote_toggle_checkbox -%>
<%= sanitize(recurring_todo.description) %> <%= recurring_todo_tag_list %> diff --git a/app/views/recurring_todos/_recurring_todo_form.erb b/app/views/recurring_todos/_recurring_todo_form.erb index d6ffed83..a2f60355 100644 --- a/app/views/recurring_todos/_recurring_todo_form.erb +++ b/app/views/recurring_todos/_recurring_todo_form.erb @@ -1,10 +1,10 @@ <%- reset_tab_index %>
- <% + <% form_for(@new_recurring_todo, :html=> { :id=>'recurring-todo-form-new-action', :name=>'recurring_todo', :class => 'inline-form' }) do -%>
<%= error_messages_for("item", :object_name => 'action') %>
- +
<%= @@ -14,12 +14,12 @@ - + - + - <%= text_field_tag "tag_list", nil, :size => 30, :tabindex => next_tab_index -%> + <%= text_field_tag "tag_list", nil, :size => 30, :tabindex => next_tab_index -%>
@@ -88,17 +88,6 @@ <%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', false, {:tabindex => next_tab_index})%> <%= t('todos.recurrence.from_tickler') %>

-
-
- - -
-
+ <% end %>
diff --git a/app/views/recurring_todos/create.js.erb b/app/views/recurring_todos/create.js.erb index e2c9428d..29fa9e32 100644 --- a/app/views/recurring_todos/create.js.erb +++ b/app/views/recurring_todos/create.js.erb @@ -1,8 +1,8 @@ <% if @saved -%> TracksPages.page_notify('notice', "<%=@status_message%>", 5); - RecurringTodosPage.toggle_overlay(); add_recurring_todo_to_active_container(); replace_form_with_empty_form(); + $( "#new-recurring-todo" ).dialog( "close" ); TracksPages.set_page_badge(<%= @down_count %>); <% else -%> TracksPages.show_errors(html_for_error_messages()); diff --git a/app/views/recurring_todos/index.html.erb b/app/views/recurring_todos/index.html.erb index 14ced9b0..8e6c932d 100644 --- a/app/views/recurring_todos/index.html.erb +++ b/app/views/recurring_todos/index.html.erb @@ -8,7 +8,7 @@ <%= render :partial => @recurring_todos %>
- +

<%= t('common.last') %> <%= t('todos.completed_recurring') %>

@@ -26,12 +26,9 @@
-
-
-
- <%= render :partial => "recurring_todo_form" %> -
- +
+ <%= render :partial => "recurring_todo_form" %> +
+ \ No newline at end of file diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 7e5ec824..a6d027d3 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -991,64 +991,93 @@ var RecurringTodosPage = { $('#recurring_edit_'+this).hide(); }); }, + reset_radio: function () { + $('input:radio[name="recurring_todo[recurring_period]"]')[0].checked = true; + }, toggle_overlay: function () { var overlay_element = document.getElementById("overlay"); overlay_element.style.visibility = (overlay_element.style.visibility == "visible") ? "hidden" : "visible"; }, setup_behavior: function() { - /* cancel button on new recurring todo form */ - $("#recurring_todo_new_action_cancel").live('click', function(){ - $('#recurring-todo-form-new-action input:text:first').focus(); - RecurringTodosPage.hide_all_recurring(); - $('#recurring_daily').show(); - RecurringTodosPage.toggle_overlay(); - }); - /* cancel button on edit recurring todo form */ - $("#recurring_todo_edit_action_cancel").live('click', function(){ - $('#recurring-todo-form-edit-action input:text:first').focus(); - RecurringTodosPage.hide_all_recurring(); - $('#recurring_daily').show(); - RecurringTodosPage.toggle_overlay(); - }); - /* change recurring period radio input on edit form */ - $("#recurring_edit_period input").live('click', function(){ - RecurringTodosPage.hide_all_edit_recurring(); - $('#recurring_edit_'+this.id.split('_')[5]).show(); - }); - /* change recurring period radio input on new form */ - $("#recurring_period input").live('click', function(){ - RecurringTodosPage.hide_all_recurring(); - $('#recurring_'+this.id.split('_')[4]).show(); - }); - /* add new recurring todo plus-button in sidebar */ - $("#add-new-recurring-todo").live('click', function(){ - $('#new-recurring-todo').show(); - $('#edit-recurring-todo').hide(); - RecurringTodosPage.toggle_overlay(); - }); - /* submit form when editing a recurring todo */ - $("#recurring_todo_edit_action_submit").live('click', function (ev) { + /* add new recurring todo plus-button in sidebar */ + $("#add-new-recurring-todo").live('click', function(){ + $( "#new-recurring-todo" ).dialog( "open" ); + }); + + /* setup dialog for new repeating action */ + $( "#new-recurring-todo" ).dialog({ + autoOpen: false, + height: 690, + width: 750, + modal: true, + buttons: { + "Create": function() { + submit_with_ajax_and_block_element('form.#recurring-todo-form-new-action', $(this)); + }, + Cancel: function() { + $( this ).dialog( "close" ); + } + }, + show: "fade", + hide: "fade", + close: function() { + $('#recurring-todo-form-new-action input:text:first').focus(); + RecurringTodosPage.hide_all_recurring(); + RecurringTodosPage.reset_radio(); + $('#recurring_daily').show(); + } + }); + + /* change recurring period radio input on new form */ + $("#recurring_period input").live('click', function(){ + RecurringTodosPage.hide_all_recurring(); + $('#recurring_'+this.id.split('_')[4]).show(); + }); + + /* setup dialog for new repeating action */ + $( "#edit-recurring-todo" ).dialog({ + autoOpen: false, + height: 690, + width: 750, + modal: true, + buttons: { + "Create": function() { submit_with_ajax_and_block_element('form#recurring-todo-form-edit-action', $(this)); - return false; - }); - /* submit form for new recurring todo */ - $("#recurring_todo_new_action_submit").live('click', function (ev) { - submit_with_ajax_and_block_element('form.#recurring-todo-form-new-action', $(this)); - return false; - }); - /* set behavior for edit recurring todo */ - $(".item-container a.edit_icon").live('click', function (ev){ - get_with_ajax_and_block_element(this.href, $(this).parents(".item-container")); - return false; - }); - /* delete button to delete a todo from the list */ - $('.item-container a.delete_icon').live('click', function(evt){ - var confirm_message = $(this).attr("x_confirm_message") - if(confirm(confirm_message)){ - delete_with_ajax_and_block_element(this.href, $(this).parents('.project')); + }, + Cancel: function() { + $( this ).dialog( "close" ); + } + }, + show: "fade", + hide: "fade", + close: function() { + $('#recurring-todo-form-edit-action input:text:first').focus(); + RecurringTodosPage.hide_all_recurring(); + RecurringTodosPage.reset_radio(); + $('#recurring_daily').show(); + } + }); + + /* change recurring period radio input on edit form */ + $("#recurring_edit_period input").live('click', function(){ + RecurringTodosPage.hide_all_edit_recurring(); + $('#recurring_edit_'+this.id.split('_')[5]).show(); + }); + + /* set behavior for edit recurring todo */ + $(".item-container a.edit_icon").live('click', function (ev){ + get_with_ajax_and_block_element(this.href, $(this).parents(".item-container")); + return false; + }); + + /* delete button to delete a todo from the list */ + $('.item-container a.delete_icon').live('click', function(evt){ + var confirm_message = $(this).attr("x_confirm_message") + if(confirm(confirm_message)){ + delete_with_ajax_and_block_element(this.href, $(this).parents('.project')); } - return false; - }); + return false; + }); } } diff --git a/public/stylesheets/standard.css b/public/stylesheets/standard.css index fa8f893a..ff92ce9a 100644 --- a/public/stylesheets/standard.css +++ b/public/stylesheets/standard.css @@ -203,26 +203,6 @@ a.show_successors:hover, a.link_to_successors:hover {background-image: url(../im width: 100%; } -#overlay { - visibility: hidden; - position: absolute; - left: 0px; - top: 0px; - width: 100%; - height: 100%; - z-index: 102; - text-align: center; - background-image:url("../images/trans70.png"); -} - -#overlay #new-recurring-todo, #overlay #edit-recurring-todo { - width:750px; - background-color: #fff; - border:1px solid #000; - padding: 15px; - margin: 70px auto; -} - .recurring_container { padding: 0px 5px 0px 5px; border: 1px solid #999; @@ -278,8 +258,8 @@ a.show_successors:hover, a.link_to_successors:hover {background-image: url(../im #navlist a:hover { color: #CCC; } #develop-notify-bar { - line-height:0.5; - background-image: url(/images/construction.gif); + line-height:0.5; + background-image: url(/images/construction.gif); background-repeat: repeat-x; } diff --git a/test/functional/recurring_todos_controller_test.rb b/test/functional/recurring_todos_controller_test.rb index 725c79ce..bdfeef65 100644 --- a/test/functional/recurring_todos_controller_test.rb +++ b/test/functional/recurring_todos_controller_test.rb @@ -2,7 +2,7 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper') class RecurringTodosControllerTest < ActionController::TestCase fixtures :users, :preferences, :projects, :contexts, :todos, :tags, :taggings, :recurring_todos - + def setup @controller = RecurringTodosController.new @request, @response = ActionController::TestRequest.new, ActionController::TestResponse.new @@ -12,30 +12,30 @@ class RecurringTodosControllerTest < ActionController::TestCase get :index assert_redirected_to :controller => 'login', :action => 'login' end - + def test_destroy_recurring_todo login_as(:admin_user) xhr :post, :destroy, :id => 1, :_source_view => 'todo' - begin + begin rc = RecurringTodo.find(1) rescue - rc = nil + rc = nil end assert_nil rc end - + def test_new_recurring_todo login_as(:admin_user) orig_rt_count = RecurringTodo.count orig_todo_count = Todo.count - put :create, - "context_name"=>"library", - "project_name"=>"Build a working time machine", - "recurring_todo" => + put :create, + "context_name"=>"library", + "project_name"=>"Build a working time machine", + "recurring_todo" => { - "daily_every_x_days"=>"1", - "daily_selector"=>"daily_every_x_day", - "description"=>"new recurring pattern", + "daily_every_x_days"=>"1", + "daily_selector"=>"daily_every_x_day", + "description"=>"new recurring pattern", "end_date" => "31/08/2010", "ends_on" => "ends_on_end_date", "monthly_day_of_week" => "1", @@ -59,15 +59,15 @@ class RecurringTodosControllerTest < ActionController::TestCase "yearly_month_of_year2"=>"8", "yearly_month_of_year"=>"6", "yearly_selector"=>"yearly_every_x_day" - }, + }, "tag_list"=>"one, two, three, four" - + # check new recurring todo added - assert_equal orig_rt_count+1, RecurringTodo.count + assert_equal orig_rt_count+1, RecurringTodo.count # check new todo added assert_equal orig_todo_count+1, Todo.count end - + def test_recurring_todo_toggle_check # the test fixtures did add recurring_todos but not the corresponding todos, # so we check complete and uncheck to force creation of a todo from the @@ -78,22 +78,22 @@ class RecurringTodosControllerTest < ActionController::TestCase xhr :post, :toggle_check, :id=>1, :_source_view=>"" recurring_todo_1 = RecurringTodo.find(1) assert recurring_todo_1.completed? - + # remove remaining todo todo = Todo.find_by_recurring_todo_id(1) todo.recurring_todo_id = 2 todo.save - + todo_count = Todo.count - + # mark as active - xhr :post, :toggle_check, :id=>1, :_source_view=>"" + xhr :post, :toggle_check, :id=>1, :_source_view=>"" recurring_todo_1.reload assert recurring_todo_1.active? - + # by making active, a new todo should be created from the pattern assert_equal todo_count+1, Todo.count - + # find the new todo and check its description new_todo = Todo.find_by_recurring_todo_id 1 assert_equal "Call Bill Gates every day", new_todo.description @@ -101,9 +101,9 @@ class RecurringTodosControllerTest < ActionController::TestCase def test_creating_recurring_todo_with_show_from_in_past login_as(:admin_user) - + @yearly = RecurringTodo.find(5) # yearly on june 8th - + # change due date in four days from now and show from 10 days before, i.e. 6 # days ago target_date = Time.now.utc + 4.days @@ -114,37 +114,37 @@ class RecurringTodosControllerTest < ActionController::TestCase # @yearly.errors.each {|obj, error| puts error} # end assert @yearly.save - + # toggle twice to force generation of new todo xhr :post, :toggle_check, :id=>5, :_source_view=>"" xhr :post, :toggle_check, :id=>5, :_source_view=>"" new_todo = Todo.find_by_recurring_todo_id 5 - + # due date should be the target_date assert_equal users(:admin_user).at_midnight(Date.new(target_date.year, target_date.month, target_date.day)), new_todo.due - + # show_from should be nil since now+4.days-10.days is in the past assert_equal nil, new_todo.show_from end - + def test_last_sunday_of_march # this test is a duplicate of the unit test. Only this test covers the # codepath in the controllers - + login_as(:admin_user) orig_rt_count = RecurringTodo.count orig_todo_count = Todo.count - put :create, - "context_name"=>"library", - "project_name"=>"Build a working time machine", - "recurring_todo" => + put :create, + "context_name"=>"library", + "project_name"=>"Build a working time machine", + "recurring_todo" => { - "daily_every_x_days"=>"1", - "daily_selector"=>"daily_every_x_day", - "description"=>"new recurring pattern", + "daily_every_x_days"=>"1", + "daily_selector"=>"daily_every_x_day", + "description"=>"new recurring pattern", "end_date" => "", "ends_on" => "no_end_date", "monthly_day_of_week" => "1", @@ -168,36 +168,36 @@ class RecurringTodosControllerTest < ActionController::TestCase "yearly_month_of_year2"=>"3", "yearly_month_of_year"=>"10", "yearly_selector"=>"yearly_every_xth_day" - }, + }, "tag_list"=>"one, two, three, four" - + # check new recurring todo added - assert_equal orig_rt_count+1, RecurringTodo.count + assert_equal orig_rt_count+1, RecurringTodo.count # check new todo added assert_equal orig_todo_count+1, Todo.count # find the newly created todo new_todo = Todo.find_by_description("new recurring pattern") assert !new_todo.nil? - + # the date should be 31 march 2013 assert_equal Time.zone.local(2013,3,31), new_todo.due end - + def test_recurring_todo_with_due_date_and_show_always login_as(:admin_user) orig_rt_count = RecurringTodo.count orig_todo_count = Todo.count - put :create, - "context_name"=>"library", - "project_name"=>"Build a working time machine", - "recurring_todo" => + put :create, + "context_name"=>"library", + "project_name"=>"Build a working time machine", + "recurring_todo" => { - "daily_every_x_days"=>"1", - "daily_selector"=>"daily_every_x_day", - "description"=>"new recurring pattern", + "daily_every_x_days"=>"1", + "daily_selector"=>"daily_every_x_day", + "description"=>"new recurring pattern", "end_date" => "", "ends_on" => "no_end_date", "monthly_day_of_week" => "1", @@ -221,20 +221,50 @@ class RecurringTodosControllerTest < ActionController::TestCase "yearly_month_of_year2"=>"3", "yearly_month_of_year"=>"10", "yearly_selector"=>"yearly_every_xth_day" - }, + }, "tag_list"=>"one, two, three, four" - + # check new recurring todo added - assert_equal orig_rt_count+1, RecurringTodo.count + assert_equal orig_rt_count+1, RecurringTodo.count # check new todo added assert_equal orig_todo_count+1, Todo.count # find the newly created recurring todo recurring_todo = RecurringTodo.find_by_description("new recurring pattern") assert !recurring_todo.nil? - + assert_equal "due_date", recurring_todo.target assert_equal true, recurring_todo.show_always? end + def test_find_and_inactivate + login_as(:admin_user) + + rt = RecurringTodo.find(recurring_todos(:call_bill_gates_every_day).id) + todo = Todo.find_by_recurring_todo_id(rt.id) + + assert_not_nil todo + assert_equal "active", todo.state, "todo should be active" + assert_equal "active", rt.state, "repeat pattern should be active" + + get :index # will call find_and_inactivate + + rt.reload + assert_equal "active", rt.state, "repeat pattern should still be active" + + # disconnect todo from pattern thus leaving the pattern without + # any active todos, but in active state + todo.reload + todo.recurring_todo_id=nil + todo.save! + + todo.reload + rt.reload + assert_equal "active", rt.state, "repeat pattern should still be active and not changed" + + get :index + rt.reload + assert_equal "completed", rt.state, "repeat pattern should be completed" + end + end From eb2f071c80b5c1a832c7c4f968fe9d6244e5749c Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Fri, 18 Nov 2011 11:43:06 +0100 Subject: [PATCH 16/42] fix #1114. also update locales for last merges/commits --- app/views/recurring_todos/_edit_form.html.erb | 13 - app/views/recurring_todos/edit.js.erb | 7 +- app/views/recurring_todos/index.html.erb | 2 +- app/views/recurring_todos/update.js.erb | 2 +- config/locales/de.yml | 709 ++++++++--------- config/locales/en.yml | 1 + config/locales/es.yml | 735 +++++++++--------- config/locales/fr.yml | 735 +++++++++--------- config/locales/nl.yml | 711 ++++++++--------- .../step_definitions/recurring_todo_steps.rb | 5 +- public/javascripts/application.js | 18 +- public/stylesheets/standard.css | 8 - spec/factories/factories.rb | 1 + 13 files changed, 1494 insertions(+), 1453 deletions(-) diff --git a/app/views/recurring_todos/_edit_form.html.erb b/app/views/recurring_todos/_edit_form.html.erb index 7193ef1a..798752d6 100644 --- a/app/views/recurring_todos/_edit_form.html.erb +++ b/app/views/recurring_todos/_edit_form.html.erb @@ -1,4 +1,3 @@ -
<% #form_remote_tag( #url => recurring_todo_path(@recurring_todo), :method => :put, @@ -99,17 +98,5 @@ <%= radio_button_tag('recurring_todo[recurring_target]', 'show_from_date', @recurring_todo.target == 'show_from_date', {:tabindex => next_tab_index})%> <%= t('todos.recurrence.from_tickler') %>

-
-
- - -
-
<% end %>
diff --git a/app/views/recurring_todos/edit.js.erb b/app/views/recurring_todos/edit.js.erb index 05d4a9b4..29ddc5f6 100644 --- a/app/views/recurring_todos/edit.js.erb +++ b/app/views/recurring_todos/edit.js.erb @@ -1,9 +1,6 @@ -$('#new-recurring-todo').hide(); $('#edit-recurring-todo').html(html_for_edit_form()); -$('#edit-recurring-todo').show(); -RecurringTodosPage.toggle_overlay(); -enable_rich_interaction(); +$('#edit-recurring-todo').dialog( "open" ); function html_for_edit_form() { - return "<%= escape_javascript(render(:partial => 'edit_form')) %>" + return "<%= escape_javascript(render(:partial => 'edit_form')) %>"; } diff --git a/app/views/recurring_todos/index.html.erb b/app/views/recurring_todos/index.html.erb index 8e6c932d..7f7da84c 100644 --- a/app/views/recurring_todos/index.html.erb +++ b/app/views/recurring_todos/index.html.erb @@ -29,6 +29,6 @@
<%= render :partial => "recurring_todo_form" %>
- +
<%= render_flash -%><%= yield -%>

<% if current_user && !current_user.prefs.nil? -%> \ No newline at end of file + diff --git a/public/stylesheets/mobile.css b/public/stylesheets/mobile.css index 9ee9973f..f4795a55 100644 --- a/public/stylesheets/mobile.css +++ b/public/stylesheets/mobile.css @@ -3,36 +3,41 @@ body { font-size: smaller; } +#content { + margin-top: 50px; +} + div.footer { font-size: XX-small; color: #999999; text-align: center; } +a, a:link, a:active, a:visited { + color: #CC3334; + padding-left: 1px; + padding-right: 1px; + text-decoration: none; +} + +a:hover { + background-color: #CC3334; + color: #FFFFFF; +} + div.footer a { text-decoration: underline; color: #999999; } -.m_t_d a { - text-decoration: none; - color: #000000; -} - -.m_t_d a:hover { - text-decoration: underline; - color: #0000FF; -} - -.m_t_d .red, .m_t_d .amber, .m_t_d .orange, .m_t_d .green{ - background-color: #999999; -} - h1 { - color: #f00; + color: #fff; font-size: small; - margin-top:.3em; - margin-bottom:.3em; + padding-top: 0.2em; + padding-bottom: 0.2em; + padding-left:8px; + margin-top:0; + margin-bottom:0; } h2 { @@ -43,6 +48,17 @@ h2 { border-top: 1px solid #777777; } +h2 a, h2 a:link, h2 a:active, h2 a:visited { + color: #666666; + text-decoration: none; +} + +h2 a:hover { + background-color: transparent; + color: #CC3334; + text-decoration: none; +} + h4.alert { border: 1px solid #666666; text-align: center; @@ -79,6 +95,15 @@ span.r { span.prj, span.ctx{ font-size: X-small; } + +#ctx, #pjr { + margin: 0.5em 0; +} + +#ctx a, #pjr a { + padding: 0.1em 0; +} + /* Draw attention to some text Same format as traffic lights */ .red { @@ -118,8 +143,8 @@ span.prj, span.ctx{ .count { color: #fff; - background: #000; - font-size: medium; + background: #f00; + padding: 0.2em; } .errors { @@ -149,7 +174,44 @@ span.r { display:none; } +#topbar { + background-color: #000000; + clear: both; + color: #EEEEEE; + height: 45px; + left: 0; + margin-bottom: 5px; + position: fixed; + top: 0; + width: 100%; + z-index: 501; +} + .nav { + color: #fff; + background: #000; + padding-top: 0.2em; + padding-bottom: 0.2em; +} + +#topbar .nav { + padding-left:8px; + margin-bottom:0.3em; +} + +.nav a, .nav a:link, .nav a:active, .nav a:visited { + color: #fff; + padding-top: 1.0em; + padding-bottom: 0.5em; +} + +.nav a:focus, .nav a:hover, .nav a:active { + background: transparent; + text-decoration: underline; +} + +.nav li:hover, .nav a:focus, .nav a:hover, .nav a:active { + color: #CCCCCC; } #database_auth_form table td { @@ -162,3 +224,7 @@ table.c { .mobile-done { display:inline; } + +input#todo_description, input#tag_list, textarea#todo_notes, select#todo_project_id, select#todo_context_id { + width: 95%; +} From c7cd46ee1d4dd9665f35fde48dedd3584775af2b Mon Sep 17 00:00:00 2001 From: Tim Madden Date: Tue, 22 Nov 2011 09:46:44 -0600 Subject: [PATCH 40/42] update to mobile todo edit view removing the done checkout (with new intermediate mobile todo view with done button, this is needed) rearranging fields to put the most important fields first --- app/views/todos/_edit_mobile_form.rhtml | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/app/views/todos/_edit_mobile_form.rhtml b/app/views/todos/_edit_mobile_form.rhtml index 63ede714..df130e65 100644 --- a/app/views/todos/_edit_mobile_form.rhtml +++ b/app/views/todos/_edit_mobile_form.rhtml @@ -4,37 +4,34 @@ <%= error_messages_for("todo") %> <% this_year = current_user.time.to_date.strftime("%Y").to_i -%> -<% if parent_container_type == 'show_mobile' -%> -

 <%= check_box_tag("done", 1, @todo && @todo.completed?, "tabindex" => 1, "onClick" => "document.mobileEdit.submit()") %>

-<% end -%>

-<%= text_field( "todo", "description", "tabindex" => 2, "maxlength" => 100, "size" => 50) %> -

-<%= text_area( "todo", "notes", "cols" => 40, "rows" => 3, "tabindex" => 3) %> +<%= text_field( "todo", "description", "tabindex" => 1, "maxlength" => 100, "size" => 50) %> +

+<%= text_field_tag "tag_list", @tag_list_text, :size => 50, :tabindex => 2 %>

<%= unless @mobile_from_context - collection_select( "todo", "context_id", @contexts, "id", "name", {}, {"tabindex" => 4} ) + collection_select( "todo", "context_id", @contexts, "id", "name", {}, {"tabindex" => 3} ) else select_tag("todo[context_id]", options_from_collection_for_select( @contexts, "id", "name", @mobile_from_context.id), - {"id" => :todo_context_id, :tabindex => 4} ) + {"id" => :todo_context_id, :tabindex => 3} ) end %>

<%= unless @mobile_from_project collection_select( "todo", "project_id", @projects, "id", "name", - {:include_blank => t('todos.no_project')}, {"tabindex" => 5} ) + {:include_blank => t('todos.no_project')}, {"tabindex" => 4} ) else # manually add blank option since :include_blank does not work # with options_from_collection_for_select select_tag("todo[project_id]", ""+options_from_collection_for_select( @projects, "id", "name", @mobile_from_project.id), - {"id" => :todo_project_id, :tabindex => 5} ) + {"id" => :todo_project_id, :tabindex => 4} ) end %> -

-<%= text_field_tag "tag_list", @tag_list_text, :size => 50, :tabindex => 6 %> +

+<%= text_area( "todo", "notes", "cols" => 40, "rows" => 3, "tabindex" => 5) %>

<%= date_select("todo", "due", {:order => [:day, :month, :year], - :start_year => this_year, :include_blank => '--'}, :tabindex => 7) %> + :start_year => this_year, :include_blank => '--'}, :tabindex => 6) %>

<%= date_select("todo", "show_from", {:order => [:day, :month, :year], - :start_year => this_year, :include_blank => true}, :tabindex => 8) %> + :start_year => this_year, :include_blank => true}, :tabindex => 7) %> From e9277676b2a7627b1fa5ecb5710ffdd5700db1bf Mon Sep 17 00:00:00 2001 From: tim madden Date: Tue, 20 Dec 2011 10:35:21 -0600 Subject: [PATCH 41/42] updates to tests for changes to mobile interface Updated mobile_add_action.feature with new navigation text Updated modile_edit_a_todo.feature to not look for removed 'done' checkbox in the edit form, but to use the previously created "mark complete" button. Added completed_container to mobile_actions so the test could find the completed todo that is no longer not visible --- app/views/todos/_mobile_actions.rhtml | 2 ++ features/mobile_add_action.feature | 4 ++-- features/mobile_edit_a_todo.feature | 7 +++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/views/todos/_mobile_actions.rhtml b/app/views/todos/_mobile_actions.rhtml index 794b055e..2d45c0ee 100644 --- a/app/views/todos/_mobile_actions.rhtml +++ b/app/views/todos/_mobile_actions.rhtml @@ -4,8 +4,10 @@ <%= render :partial => "contexts/mobile_context", :collection => @contexts_to_show -%> <% end -%> <% unless @done.nil? -%> +

<%= t('todos.completed_actions') %>

    <%= render :partial => "todos/mobile_todo", :collection => @done %>
+
<% end %> diff --git a/features/mobile_add_action.feature b/features/mobile_add_action.feature index 80d9939d..86537d9f 100644 --- a/features/mobile_add_action.feature +++ b/features/mobile_add_action.feature @@ -14,7 +14,7 @@ Feature: Add new next action from mobile page Scenario Outline: The new action form is prefilled with context and project Given I am on the - When I follow "0-New action" + When I follow "New" Then the selected project should be "" And the selected context should be "" @@ -29,7 +29,7 @@ Feature: Add new next action from mobile page Scenario: I can add a new todo using the mobile interface Given I am on the home page Then the badge should show 0 - When I follow "0-New action" + When I follow "New" And I fill in "Description" with "test me" And I press "Create" Then I should see "test me" diff --git a/features/mobile_edit_a_todo.feature b/features/mobile_edit_a_todo.feature index 5726d99a..3589d072 100644 --- a/features/mobile_edit_a_todo.feature +++ b/features/mobile_edit_a_todo.feature @@ -14,6 +14,7 @@ Feature: Edit a next action from the mobile view | context | description | | @mobile | test action | + @selenium Scenario: I can edit an action on the mobile page When I am on the home page Then the badge should show 1 @@ -27,10 +28,8 @@ Feature: Edit a next action from the mobile view Then I should see "changed action" And I should not see "test action" When I follow "changed action" - And I press "Edit this action" - And I check "done" - And I press "Update" - Then I should not see "changed action" + And I press "Mark complete" + Then I should see "changed action" in the completed container Scenario: Navigate from home page move this to separate features when other scenarios are created for these features From f7faef3c91dc1e3e7eb08c6b1ace7e7d1dc3cb68 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Wed, 4 Jan 2012 16:05:11 +0100 Subject: [PATCH 42/42] fix #1203. The fix of #1152 works for IE7 too. --- public/javascripts/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 232ae12c..5c2302f8 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1318,7 +1318,7 @@ function enable_rich_interaction(){ $(document).ready(function() { // fix for IE8. Without this checkboxes don't work AJAXy. See #1152 - if($.browser.msie && ($.browser.version.substring(0, 2) == "8.")) { + if($.browser.msie && ( ($.browser.version.substring(0, 2) == "8.") || ($.browser.version.substring(0, 2) == "7.") ) ) { $('body').bind('change', function() { return true; });