From svn at explainpmt.com Sun Mar 12 18:34:42 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Sun, 12 Mar 2006 23:34:42 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [200] Creating release tag for 20060311.0 Message-ID: <20060312233442.3685DC97C4@thurlow.textdrive.com> Revision: 200 Author: jwilger Date: 2006-03-12 23:34:41 +0000 (Sun, 12 Mar 2006) Log Message: ----------- Creating release tag for 20060311.0 Added Paths: ----------- tags/release_20060311.0/ Copied: tags/release_20060311.0 (from rev 199, trunk) From svn at explainpmt.com Sun Mar 12 18:36:04 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Sun, 12 Mar 2006 23:36:04 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [201] Updated version tag for release Message-ID: <20060312233604.333BFC98A6@thurlow.textdrive.com> Revision: 201 Author: jwilger Date: 2006-03-12 23:36:03 +0000 (Sun, 12 Mar 2006) Log Message: ----------- Updated version tag for release Modified Paths: -------------- tags/release_20060311.0/app/helpers/application_helper.rb Modified: tags/release_20060311.0/app/helpers/application_helper.rb =================================================================== --- tags/release_20060311.0/app/helpers/application_helper.rb 2006-03-12 23:34:41 UTC (rev 200) +++ tags/release_20060311.0/app/helpers/application_helper.rb 2006-03-12 23:36:03 UTC (rev 201) @@ -20,7 +20,7 @@ # The methods added to this helper will be available to all templates in the application. module ApplicationHelper - VERSION = 'dev trunk' + VERSION = '20060311.0' # Used to determine if the currently logged in user has administrative # privileges From svn at explainpmt.com Mon Mar 20 21:21:03 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Tue, 21 Mar 2006 02:21:03 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [202] Story listing on iteration pages now show an icon next to the titles of stories that have content in their description field. Message-ID: <20060321022103.5BBA7C9885@thurlow.textdrive.com> Revision: 202 Author: jwilger Date: 2006-03-21 02:21:01 +0000 (Tue, 21 Mar 2006) Log Message: ----------- Story listing on iteration pages now show an icon next to the titles of stories that have content in their description field. Modified Paths: -------------- trunk/app/views/iterations/show.rhtml trunk/public/stylesheets/layout.css Added Paths: ----------- trunk/public/images/info-icon.gif Modified: trunk/app/views/iterations/show.rhtml =================================================================== --- trunk/app/views/iterations/show.rhtml 2006-03-12 23:36:03 UTC (rev 201) +++ trunk/app/views/iterations/show.rhtml 2006-03-21 02:21:01 UTC (rev 202) @@ -193,9 +193,17 @@ "" end stories_table.column_modifier(:title) do |s| - link_to(s.title, - :controller => 'stories', :action => 'show', :id => s.id, - :project_id => s.project.id) + str = link_to(s.title, + :controller => 'stories', :action => 'show', :id => s.id, + :project_id => s.project.id) + if s.description? + str += ' ' + + link_to(image_tag('info-icon.gif', :alt => '[description]'), + {:controller => 'stories', :action => 'show', :id => s.id, + :project_id => s.project.id}, + :class => 'story_description_icon') + end + str end stories_table.column_modifier(:owner) do |s| if s.has_owner? Added: trunk/public/images/info-icon.gif =================================================================== (Binary files differ) Property changes on: trunk/public/images/info-icon.gif ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Modified: trunk/public/stylesheets/layout.css =================================================================== --- trunk/public/stylesheets/layout.css 2006-03-12 23:36:03 UTC (rev 201) +++ trunk/public/stylesheets/layout.css 2006-03-21 02:21:01 UTC (rev 202) @@ -161,6 +161,12 @@ border: none; } +a.story_description_icon, +a.story_description_icon img { + text-decoration: none; + border: none; +} + #Header { margin: 0; } From svn at explainpmt.com Mon Mar 20 21:23:15 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Tue, 21 Mar 2006 02:23:15 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [203] Icon is now displayed next to titles of stories that have descriptions when viewing the backlog. Message-ID: <20060321022315.A23D3C9886@thurlow.textdrive.com> Revision: 203 Author: jwilger Date: 2006-03-21 02:23:15 +0000 (Tue, 21 Mar 2006) Log Message: ----------- Icon is now displayed next to titles of stories that have descriptions when viewing the backlog. Modified Paths: -------------- trunk/app/views/stories/index.rhtml Modified: trunk/app/views/stories/index.rhtml =================================================================== --- trunk/app/views/stories/index.rhtml 2006-03-21 02:21:01 UTC (rev 202) +++ trunk/app/views/stories/index.rhtml 2006-03-21 02:23:15 UTC (rev 203) @@ -55,11 +55,17 @@ "" end stories_table.column_modifier(:title) do |s| - link_to(s.title, - :controller => 'stories', - :action => 'show', - :id => s.id, + str = link_to(s.title, + :controller => 'stories', :action => 'show', :id => s.id, :project_id => s.project.id) + if s.description? + str += ' ' + + link_to(image_tag('info-icon.gif', :alt => '[description]'), + {:controller => 'stories', :action => 'show', :id => s.id, + :project_id => s.project.id}, + :class => 'story_description_icon') + end + str end stories_table.column_modifier(:edit) do |s| link_to('Edit', From svn at explainpmt.com Wed Mar 22 21:25:28 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Thu, 23 Mar 2006 02:25:28 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [204] SC61 - added SubProject model and relationship between Project and SubProject Message-ID: <20060323022528.9F418C9674@thurlow.textdrive.com> Revision: 204 Author: jwilger Date: 2006-03-23 02:25:06 +0000 (Thu, 23 Mar 2006) Log Message: ----------- SC61 - added SubProject model and relationship between Project and SubProject Modified Paths: -------------- trunk/app/models/project.rb trunk/test/test_helper.rb trunk/test/unit/project_test.rb Added Paths: ----------- trunk/app/models/sub_project.rb trunk/db/migrate/002_add_sub_projects_table.rb trunk/test/fixtures/sub_projects.yml trunk/test/unit/sub_project_test.rb Modified: trunk/app/models/project.rb =================================================================== --- trunk/app/models/project.rb 2006-03-21 02:23:15 UTC (rev 203) +++ trunk/app/models/project.rb 2006-03-23 02:25:06 UTC (rev 204) @@ -38,6 +38,8 @@ # validates_length_of :name, :maximum => 100 # class Project < ActiveRecord::Base + has_many :sub_projects, :order => 'name', :dependent => true + has_many :iterations, :order => 'start_date ASC', :dependent => true do def past self.reverse.select { |i| i.past? } Added: trunk/app/models/sub_project.rb =================================================================== --- trunk/app/models/sub_project.rb (rev 0) +++ trunk/app/models/sub_project.rb 2006-03-23 02:25:06 UTC (rev 204) @@ -0,0 +1,26 @@ +############################################################################## +# eXPlain Project Management Tool +# Copyright (C) 2005 John Wilger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## + + +class SubProject < ActiveRecord::Base + belongs_to :project + + validates_presence_of :name + validates_uniqueness_of :name, :scope => 'project_id' +end Added: trunk/db/migrate/002_add_sub_projects_table.rb =================================================================== --- trunk/db/migrate/002_add_sub_projects_table.rb (rev 0) +++ trunk/db/migrate/002_add_sub_projects_table.rb 2006-03-23 02:25:06 UTC (rev 204) @@ -0,0 +1,34 @@ +############################################################################## +# eXPlain Project Management Tool +# Copyright (C) 2005 John Wilger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## + + +class AddSubProjectsTable < ActiveRecord::Migration + def self.up + create_table "sub_projects" do |t| + t.column "project_id", :integer + t.column "name", :string + t.column "created_at", :datetime + t.column "updated_at", :datetime + end + end + + def self.down + drop_table "sub_projects" + end +end Added: trunk/test/fixtures/sub_projects.yml =================================================================== --- trunk/test/fixtures/sub_projects.yml (rev 0) +++ trunk/test/fixtures/sub_projects.yml 2006-03-23 02:25:06 UTC (rev 204) @@ -0,0 +1,9 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +first: + id: 1 + project_id: 1 + name: First Sub-Project +second: + id: 2 + project_id: 1 + name: Second Sub-Project \ No newline at end of file Modified: trunk/test/test_helper.rb =================================================================== --- trunk/test/test_helper.rb 2006-03-21 02:23:15 UTC (rev 203) +++ trunk/test/test_helper.rb 2006-03-23 02:25:06 UTC (rev 204) @@ -11,6 +11,43 @@ # Add more helper methods to be used by all tests here... - ALL_FIXTURES = [ :iterations, :milestones, :projects_users, :projects, - :stories, :users ] + ALL_FIXTURES = [ :iterations, + :milestones, + :projects_users, + :projects, + :stories, + :users, + :sub_projects ] + + class << self + def test_required_attributes( klass, *attributes ) + attributes.each do |attribute| + self.class_eval %Q{ + def test_#{attribute}_is_required + object = #{klass}.new + assert !object.valid? + assert object.errors.on( :#{attribute} ) + assert object.errors.on( :#{attribute} ).to_a. + include?( "can't be blank" ) + end + } + end + end + + def test_unique_attributes( klass, existing_id, *attributes ) + attributes.each do |attribute| + self.class_eval %Q{ + def test_#{attribute}_must_be_unique + existing = #{klass}.find #{existing_id} + object = #{klass}.new + object.#{attribute} = existing.#{attribute} + assert !object.valid? + assert object.errors.on( :#{attribute} ) + assert object.errors.on( :#{attribute} ).to_a. + include?( 'has already been taken' ) + end + } + end + end + end end Modified: trunk/test/unit/project_test.rb =================================================================== --- trunk/test/unit/project_test.rb 2006-03-21 02:23:15 UTC (rev 203) +++ trunk/test/unit/project_test.rb 2006-03-23 02:25:06 UTC (rev 204) @@ -40,6 +40,24 @@ @milestone_six = Milestone.find 6 @milestone_seven = Milestone.find 7 end + + def test_sub_projects_association + assert_equal SubProject.find( :all, + :conditions => [ 'project_id = ?', + @project_one.id ], + :order => 'name' ), + @project_one.sub_projects + end + + def test_sub_projects_destroyed_with_project + sub_ids = @project_one.sub_projects.map { |s| s.id } + @project_one.destroy + sub_ids.each do |id| + assert_raises( ActiveRecord::RecordNotFound) do + SubProject.find( id ) + end + end + end def test_current_iteration_that_starts_today assert_equal @iteration_one, @project_one.iterations.current Added: trunk/test/unit/sub_project_test.rb =================================================================== --- trunk/test/unit/sub_project_test.rb (rev 0) +++ trunk/test/unit/sub_project_test.rb 2006-03-23 02:25:06 UTC (rev 204) @@ -0,0 +1,50 @@ +############################################################################## +# eXPlain Project Management Tool +# Copyright (C) 2005 John Wilger +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +############################################################################## + + +require File.dirname(__FILE__) + '/../test_helper' + +class SubProjectTest < Test::Unit::TestCase + fixtures ALL_FIXTURES + + def setup + @project_one = projects( :first ) + @project_two = projects( :second ) + @sub_project_one = sub_projects( :first ) + end + + test_required_attributes SubProject, :name + + def test_name_unique_within_same_project + new_sub_proj = @sub_project_one.clone + assert !new_sub_proj.valid? + assert new_sub_proj.errors.on( :name ) + assert_equal 'has already been taken', new_sub_proj.errors.on( :name ) + end + + def test_name_not_unique_across_projects + new_sub_proj = @sub_project_one.clone + new_sub_proj.project = @project_two + assert new_sub_proj.valid? + end + + def test_project_association + assert_equal @project_one, @sub_project_one.project + end +end From svn at explainpmt.com Thu Mar 23 01:04:27 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Thu, 23 Mar 2006 06:04:27 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [205] SC61 - admins can now create sub-projects and view the list of sub-projects for each project Message-ID: <20060323060427.49936C988C@thurlow.textdrive.com> Revision: 205 Author: jwilger Date: 2006-03-23 06:04:11 +0000 (Thu, 23 Mar 2006) Log Message: ----------- SC61 - admins can now create sub-projects and view the list of sub-projects for each project Modified Paths: -------------- trunk/app/controllers/projects_controller.rb trunk/app/views/projects/index.rhtml trunk/test/functional/projects_controller_test.rb Added Paths: ----------- trunk/app/views/projects/_sub_project.rhtml trunk/app/views/projects/_sub_project_error.rhtml trunk/app/views/projects/_sub_project_new.rhtml trunk/public/images/spinner.gif Modified: trunk/app/controllers/projects_controller.rb =================================================================== --- trunk/app/controllers/projects_controller.rb 2006-03-23 02:25:06 UTC (rev 204) +++ trunk/app/controllers/projects_controller.rb 2006-03-23 06:04:11 UTC (rev 205) @@ -151,5 +151,14 @@ @projects = session[:current_user].projects render_partial 'my_projects_list' end -end - + + def create_sub_project + project = Project.find params[ :project_id ] + @sub_project = project.sub_projects.create params[ :sub_project ] + if @sub_project.new_record? + render :partial => 'sub_project_error', :status => 500 + else + render :partial => 'sub_project_new' + end + end +end \ No newline at end of file Added: trunk/app/views/projects/_sub_project.rhtml =================================================================== --- trunk/app/views/projects/_sub_project.rhtml (rev 0) +++ trunk/app/views/projects/_sub_project.rhtml 2006-03-23 06:04:11 UTC (rev 205) @@ -0,0 +1,5 @@ + + <%= sub_project.name %> + delete +   + \ No newline at end of file Added: trunk/app/views/projects/_sub_project_error.rhtml =================================================================== --- trunk/app/views/projects/_sub_project_error.rhtml (rev 0) +++ trunk/app/views/projects/_sub_project_error.rhtml 2006-03-23 06:04:11 UTC (rev 205) @@ -0,0 +1,4 @@ +alert('<%= escape_javascript %Q{ +Unable to create project: +#{@sub_project.errors.full_messages.join("\n")} +} -%>'); \ No newline at end of file Added: trunk/app/views/projects/_sub_project_new.rhtml =================================================================== --- trunk/app/views/projects/_sub_project_new.rhtml (rev 0) +++ trunk/app/views/projects/_sub_project_new.rhtml 2006-03-23 06:04:11 UTC (rev 205) @@ -0,0 +1,5 @@ +<% update_element_function( "SubProjectList_#{@sub_project.project.id}", + :position => :bottom, :binding => binding ) do %> + <%= render :partial => 'sub_project' %> +<% end %> +new Effect.Highlight('SubProject_<%= @sub_project.id -%>'); \ No newline at end of file Modified: trunk/app/views/projects/index.rhtml =================================================================== --- trunk/app/views/projects/index.rhtml 2006-03-23 02:25:06 UTC (rev 204) +++ trunk/app/views/projects/index.rhtml 2006-03-23 06:04:11 UTC (rev 205) @@ -26,7 +26,38 @@ 'the project and can not be undone!') %>) -
<%= textilize(p.description) %>
+
+

+ Sub-Projects: + <%= link_to_function 'show/hide', "Element.toggle('SubProjectForm_#{p.id}')" %> +

+ + <%= form_remote_tag :url => { :controller => 'projects', :action => 'create_sub_project' }, + :html => { :id => "SubProjectForm_#{p.id}", :style => 'display: none;' }, + :loading => "Element.toggle('SubProjectFormSpinner_#{p.id}')", + :complete => "Element.toggle('SubProjectFormSpinner_#{p.id}');" + + "$('SubProjectForm_#{p.id}').reset();", + :success => evaluate_remote_response, + :failure => evaluate_remote_response %> + + + + <%= render :partial => 'sub_project', :collection => p.sub_projects %> + + + + + + + + +
<%= submit_tag '+' %><%= image_tag 'spinner.gif', :id => "SubProjectFormSpinner_#{p.id}", + :style => 'display: none' -%>
+ + + + <%= textilize(p.description) %> +
<% end %> <% else %> Added: trunk/public/images/spinner.gif =================================================================== (Binary files differ) Property changes on: trunk/public/images/spinner.gif ___________________________________________________________________ Name: svn:mime-type + application/octet-stream Modified: trunk/test/functional/projects_controller_test.rb =================================================================== --- trunk/test/functional/projects_controller_test.rb 2006-03-23 02:25:06 UTC (rev 204) +++ trunk/test/functional/projects_controller_test.rb 2006-03-23 06:04:11 UTC (rev 205) @@ -191,6 +191,22 @@ assert assigns( :projects ).include?( @project_one ) assert assigns( :projects ).include?( @project_two ) end + + def test_create_sub_project_success + sub_count = @project_one.sub_projects.size + xhr :post, :create_sub_project, :project_id => 1, + :sub_project => { :name => 'Test Create' } + assert_response :success + assert_template '_sub_project_new' + assert_equal sub_count + 1, @project_one.sub_projects( true ).size + end + + def test_create_sub_project_failure + sub_count = @project_one.sub_projects.size + xhr :post, :create_sub_project, :project_id => 1 + assert_response :error + assert_template '_sub_project_error' + end private From svn at explainpmt.com Thu Mar 23 01:51:01 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Thu, 23 Mar 2006 06:51:01 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [206] SC64 - user can assign a card to a sub-project when editing the card, and the sub-project (if any) is displayed when viewign the Message-ID: <20060323065101.5B3F7C977B@thurlow.textdrive.com> Revision: 206 Author: jwilger Date: 2006-03-23 06:50:49 +0000 (Thu, 23 Mar 2006) Log Message: ----------- SC64 - user can assign a card to a sub-project when editing the card, and the sub-project (if any) is displayed when viewign the card's details. Modified Paths: -------------- trunk/app/models/story.rb trunk/app/models/sub_project.rb trunk/app/views/stories/_story_form.rhtml trunk/app/views/stories/show.rhtml trunk/test/fixtures/stories.yml trunk/test/fixtures/sub_projects.yml trunk/test/unit/story_test.rb Added Paths: ----------- trunk/db/migrate/003_add_sub_project_id_to_stories.rb Modified: trunk/app/models/story.rb =================================================================== --- trunk/app/models/story.rb 2006-03-23 06:04:11 UTC (rev 205) +++ trunk/app/models/story.rb 2006-03-23 06:50:49 UTC (rev 206) @@ -39,6 +39,8 @@ # class Story < ActiveRecord::Base belongs_to :project + belongs_to :sub_project + before_save :check_project_and_sub_project_match belongs_to :iteration belongs_to :owner, :class_name => 'User' @@ -60,7 +62,7 @@ composed_of :status, :mapping => %w(status order) composed_of :priority, :mapping => %w(priority order) composed_of :risk, :mapping => %w(risk order) - + class RankedValue class InvalidOrder < Exception;end @@ -246,4 +248,10 @@ self.status = Status::Defined end end + + def check_project_and_sub_project_match + unless sub_project.nil? or sub_project.project == project + raise SubProject::ProjectMismatchError + end + end end Modified: trunk/app/models/sub_project.rb =================================================================== --- trunk/app/models/sub_project.rb 2006-03-23 06:04:11 UTC (rev 205) +++ trunk/app/models/sub_project.rb 2006-03-23 06:50:49 UTC (rev 206) @@ -19,6 +19,8 @@ class SubProject < ActiveRecord::Base + class ProjectMismatchError < StandardError; end + belongs_to :project validates_presence_of :name Modified: trunk/app/views/stories/_story_form.rhtml =================================================================== --- trunk/app/views/stories/_story_form.rhtml 2006-03-23 06:04:11 UTC (rev 205) +++ trunk/app/views/stories/_story_form.rhtml 2006-03-23 06:50:49 UTC (rev 206) @@ -7,6 +7,12 @@ <%= input('story', 'title') %> + + <% sub_collection = [ SubProject.new ] + @story.project.sub_projects %> + <%= collection_select( 'story', 'sub_project_id', sub_collection, + 'id', 'name' ) %> + + <%= collection_select('story', 'status', Story::Statuses, 'order', 'name') %> Modified: trunk/app/views/stories/show.rhtml =================================================================== --- trunk/app/views/stories/show.rhtml 2006-03-23 06:04:11 UTC (rev 205) +++ trunk/app/views/stories/show.rhtml 2006-03-23 06:50:49 UTC (rev 206) @@ -14,6 +14,12 @@

+ <% if @story.sub_project %> + + + + + <% end %> Added: trunk/db/migrate/003_add_sub_project_id_to_stories.rb =================================================================== --- trunk/db/migrate/003_add_sub_project_id_to_stories.rb (rev 0) +++ trunk/db/migrate/003_add_sub_project_id_to_stories.rb 2006-03-23 06:50:49 UTC (rev 206) @@ -0,0 +1,9 @@ +class AddSubProjectIdToStories < ActiveRecord::Migration + def self.up + add_column "stories", "sub_project_id", :integer + end + + def self.down + remove_column "stories", "sub_project_id" + end +end Modified: trunk/test/fixtures/stories.yml =================================================================== --- trunk/test/fixtures/stories.yml 2006-03-23 06:04:11 UTC (rev 205) +++ trunk/test/fixtures/stories.yml 2006-03-23 06:50:49 UTC (rev 206) @@ -2,6 +2,7 @@ id: 1 scid: 1 project_id: 1 + sub_project_id: 1 iteration_id: 1 user_id: 1 title: 'First Story' @@ -13,6 +14,7 @@ id: 2 scid: 2 project_id: 1 + sub_project_id: 1 iteration_id: 1 title: 'Second Story' status: 6 Modified: trunk/test/fixtures/sub_projects.yml =================================================================== --- trunk/test/fixtures/sub_projects.yml 2006-03-23 06:04:11 UTC (rev 205) +++ trunk/test/fixtures/sub_projects.yml 2006-03-23 06:50:49 UTC (rev 206) @@ -6,4 +6,8 @@ second: id: 2 project_id: 1 - name: Second Sub-Project \ No newline at end of file + name: Second Sub-Project +third: + id: 3 + project_id: 2 + name: Third Sub-Project \ No newline at end of file Modified: trunk/test/unit/story_test.rb =================================================================== --- trunk/test/unit/story_test.rb 2006-03-23 06:04:11 UTC (rev 205) +++ trunk/test/unit/story_test.rb 2006-03-23 06:50:49 UTC (rev 206) @@ -25,10 +25,30 @@ def setup @user_one = User.find 1 + @story_one = stories( :first ) @project_one = Project.find 1 + @sub_project_one = sub_projects( :first ) + @sub_project_two = sub_projects( :second ) + @sub_project_three = sub_projects( :third ) @iteration_one = Iteration.find 1 end + def test_sub_project_association + assert_equal @sub_project_one, @story_one.sub_project + end + + def test_sub_project_must_be_part_of_storys_main_project + assert_raises( SubProject::ProjectMismatchError ) do + @story_one.sub_project = @sub_project_three + @story_one.save + end + + assert_nothing_raised do + @story_one.sub_project = @sub_project_two + @story_one.save + end + end + def test_status_collection assert_equal( [ Story::Status::New, Story::Status::Defined, Story::Status::InProgress, From svn at explainpmt.com Thu Mar 23 02:25:04 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Thu, 23 Mar 2006 07:25:04 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [207] SC73 - a sub-project column was added to story card listings and is sortable Message-ID: <20060323072504.544E5C9871@thurlow.textdrive.com> Revision: 207 Author: jwilger Date: 2006-03-23 07:24:47 +0000 (Thu, 23 Mar 2006) Log Message: ----------- SC73 - a sub-project column was added to story card listings and is sortable Modified Paths: -------------- trunk/app/controllers/dashboard_controller.rb trunk/app/controllers/iterations_controller.rb trunk/app/controllers/stories_controller.rb trunk/app/helpers/sort_helper.rb trunk/app/views/dashboard/index.rhtml trunk/app/views/dashboard/project.rhtml trunk/app/views/iterations/show.rhtml trunk/app/views/stories/index.rhtml Modified: trunk/app/controllers/dashboard_controller.rb =================================================================== --- trunk/app/controllers/dashboard_controller.rb 2006-03-23 06:50:49 UTC (rev 206) +++ trunk/app/controllers/dashboard_controller.rb 2006-03-23 07:24:47 UTC (rev 207) @@ -53,10 +53,11 @@ # Uses SortHelper to sort the list of story cards def sort_stories(stories) if @project.nil? - SortHelper.columns = %w( project.name scid title points priority risk - status ) + SortHelper.columns = %w( project.name sub_project.name scid title points + priority risk status ) else - SortHelper.columns = %w( scid title points priority risk status ) + SortHelper.columns = %w( sub_project.name scid title points priority + risk status ) end SortHelper.default_order = %w( status priority risk ) stories = stories.sort do |a,b| Modified: trunk/app/controllers/iterations_controller.rb =================================================================== --- trunk/app/controllers/iterations_controller.rb 2006-03-23 06:50:49 UTC (rev 206) +++ trunk/app/controllers/iterations_controller.rb 2006-03-23 07:24:47 UTC (rev 207) @@ -116,7 +116,8 @@ def show @iteration = Iteration.find(params['id']) @page_title = "Iteration: #{@iteration.start_date} - #{@iteration.stop_date}" - SortHelper.columns = %w(scid title points priority risk status) + SortHelper.columns = %w(scid sub_project.name title points priority risk + status) SortHelper.default_order = %w(status priority risk) @stories = @iteration.stories.sort do |a,b| SortHelper.sort(a,b,params) Modified: trunk/app/controllers/stories_controller.rb =================================================================== --- trunk/app/controllers/stories_controller.rb 2006-03-23 06:50:49 UTC (rev 206) +++ trunk/app/controllers/stories_controller.rb 2006-03-23 07:24:47 UTC (rev 207) @@ -27,7 +27,8 @@ # non-blank value). def index @page_title = "Backlog" - SortHelper.columns = %w( scid title points priority risk status ) + SortHelper.columns = %w( scid sub_project.name title points priority risk + status ) SortHelper.default_order = %w( status priority risk ) if params['show_cancelled'] @stories = @project.stories.backlog Modified: trunk/app/helpers/sort_helper.rb =================================================================== --- trunk/app/helpers/sort_helper.rb 2006-03-23 06:50:49 UTC (rev 206) +++ trunk/app/helpers/sort_helper.rb 2006-03-23 07:24:47 UTC (rev 207) @@ -131,8 +131,8 @@ a_col = a b_col = b @@sort_columns[column_index.abs].split('.').each do |meth| - a_col = a_col.send(meth) - b_col = b_col.send(meth) + a_col = a_col.send(meth) unless a_col.nil? + b_col = b_col.send(meth) unless b_col.nil? end reverse = (column_index < 0) case a_col && a_col <=> b_col Modified: trunk/app/views/dashboard/index.rhtml =================================================================== --- trunk/app/views/dashboard/index.rhtml 2006-03-23 06:50:49 UTC (rev 206) +++ trunk/app/views/dashboard/index.rhtml 2006-03-23 07:24:47 UTC (rev 207) @@ -15,6 +15,10 @@ link_to_sort_by('Project', 'project.name', sort_header_params)], + [:sub_project, + link_to_sort_by('Sub-Project', + 'sub_project.name', + sort_header_params)], [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], @@ -41,6 +45,13 @@ link_to(s.project.name, :controller => 'dashboard', :action => 'index', :project_id => s.project.id) end + stories_table.column_modifier( :sub_project ) do |s| + if s.sub_project.nil? + '' + else + s.sub_project.name + end + end stories_table.column_modifier(:scid) { |s| "SC#{s.scid}" } stories_table.column_modifier(:title) do |s| link_to(s.title, Modified: trunk/app/views/dashboard/project.rhtml =================================================================== --- trunk/app/views/dashboard/project.rhtml 2006-03-23 06:50:49 UTC (rev 206) +++ trunk/app/views/dashboard/project.rhtml 2006-03-23 07:24:47 UTC (rev 207) @@ -15,6 +15,10 @@ [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], + [:sub_project, + link_to_sort_by('Sub-Project', + 'sub_project.name', + sort_header_params)], [:title, link_to_sort_by('Title', 'title', sort_header_params)], @@ -35,6 +39,13 @@ stories_table.column_align(:edit, 'right') stories_table.data_row_class = ['odd_row','even_row'] stories_table.column_modifier(:scid) { |s| "SC#{s.scid}" } +stories_table.column_modifier( :sub_project ) do |s| + if s.sub_project.nil? + '' + else + s.sub_project.name + end +end stories_table.column_modifier(:title) do |s| link_to(s.title, :controller => 'stories', Modified: trunk/app/views/iterations/show.rhtml =================================================================== --- trunk/app/views/iterations/show.rhtml 2006-03-23 06:50:49 UTC (rev 206) +++ trunk/app/views/iterations/show.rhtml 2006-03-23 07:24:47 UTC (rev 207) @@ -167,6 +167,10 @@ [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], + [:sub_project, + link_to_sort_by('Sub-Project', + 'sub_project.name', + sort_header_params)], [:title, link_to_sort_by('Title', 'title', sort_header_params)], @@ -230,6 +234,13 @@ }, :confirm => "Are you sure you want to delete SC#{s.scid}?") end +stories_table.column_modifier( :sub_project ) do |s| + if s.sub_project.nil? + '' + else + s.sub_project.name + end +end %> <%= stories_table.build_table %> <%= end_form_tag %> Modified: trunk/app/views/stories/index.rhtml =================================================================== --- trunk/app/views/stories/index.rhtml 2006-03-23 06:50:49 UTC (rev 206) +++ trunk/app/views/stories/index.rhtml 2006-03-23 07:24:47 UTC (rev 207) @@ -39,6 +39,8 @@ } stories_table = CollectionTableHelper::CollectionTable.new(@stories, [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], + [:sub_project, link_to_sort_by('Sub-Project', 'sub_project.name', + sort_header_params)], [:title, link_to_sort_by('Title', 'title', sort_header_params)], [:points, link_to_sort_by('Points', 'points', sort_header_params)], [:priority, link_to_sort_by('Priority', 'priority', sort_header_params)], @@ -79,6 +81,13 @@ :id => s.id, :project_id => s.project.id }, :confirm => "Are you sure you want to delete SC#{s.scid}?") end + stories_table.column_modifier( :sub_project ) do |s| + if s.sub_project.nil? + '' + else + s.sub_project.name + end + end %> <%= stories_table.build_table %> From svn at explainpmt.com Thu Mar 23 15:17:44 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Thu, 23 Mar 2006 20:17:44 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [208] SC62 - when sub-projects are deleted, any cards that are associated will be associated with the top-level project Message-ID: <20060323201744.EBDEBC98D5@thurlow.textdrive.com> Revision: 208 Author: jwilger Date: 2006-03-23 20:17:43 +0000 (Thu, 23 Mar 2006) Log Message: ----------- SC62 - when sub-projects are deleted, any cards that are associated will be associated with the top-level project Modified Paths: -------------- trunk/app/models/sub_project.rb trunk/test/unit/sub_project_test.rb Modified: trunk/app/models/sub_project.rb =================================================================== --- trunk/app/models/sub_project.rb 2006-03-23 07:24:47 UTC (rev 207) +++ trunk/app/models/sub_project.rb 2006-03-23 20:17:43 UTC (rev 208) @@ -22,6 +22,7 @@ class ProjectMismatchError < StandardError; end belongs_to :project + has_many :stories, :order => 'scid', :dependent => :nullify validates_presence_of :name validates_uniqueness_of :name, :scope => 'project_id' Modified: trunk/test/unit/sub_project_test.rb =================================================================== --- trunk/test/unit/sub_project_test.rb 2006-03-23 07:24:47 UTC (rev 207) +++ trunk/test/unit/sub_project_test.rb 2006-03-23 20:17:43 UTC (rev 208) @@ -27,6 +27,8 @@ @project_one = projects( :first ) @project_two = projects( :second ) @sub_project_one = sub_projects( :first ) + @story_one = stories( :first ) + @story_two = stories( :second ) end test_required_attributes SubProject, :name @@ -47,4 +49,18 @@ def test_project_association assert_equal @project_one, @sub_project_one.project end -end + + def test_associated_stories_are_disassociated_when_sub_project_destroyed + assert_equal @sub_project_one, @story_one.sub_project + assert_equal @sub_project_one, @story_two.sub_project + @sub_project_one.destroy + @story_one.reload + @story_two.reload + assert_nil @story_one.sub_project_id + assert_nil @story_two.sub_project_id + end + + def test_stories_association + assert_equal [ @story_one, @story_two ], @sub_project_one.stories + end +end \ No newline at end of file From svn at explainpmt.com Thu Mar 23 15:45:13 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Thu, 23 Mar 2006 20:45:13 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [209] SC62 - user can now delete sub-projects Message-ID: <20060323204513.A56F4C98A4@thurlow.textdrive.com> Revision: 209 Author: jwilger Date: 2006-03-23 20:45:11 +0000 (Thu, 23 Mar 2006) Log Message: ----------- SC62 - user can now delete sub-projects Modified Paths: -------------- trunk/app/controllers/projects_controller.rb trunk/app/views/projects/_sub_project.rhtml trunk/test/functional/projects_controller_test.rb Added Paths: ----------- trunk/app/views/projects/_destroy_sub_project_error.rhtml trunk/app/views/projects/_destroy_sub_project_success.rhtml Modified: trunk/app/controllers/projects_controller.rb =================================================================== --- trunk/app/controllers/projects_controller.rb 2006-03-23 20:17:43 UTC (rev 208) +++ trunk/app/controllers/projects_controller.rb 2006-03-23 20:45:11 UTC (rev 209) @@ -161,4 +161,13 @@ render :partial => 'sub_project_new' end end + + def destroy_sub_project + @sub_project = SubProject.find params[ :id ] + @sub_project.destroy + render :partial => 'destroy_sub_project_success' + rescue Exception => e + @message = e.message + render :partial => 'destroy_sub_project_error', :status => 500 + end end \ No newline at end of file Added: trunk/app/views/projects/_destroy_sub_project_error.rhtml =================================================================== --- trunk/app/views/projects/_destroy_sub_project_error.rhtml (rev 0) +++ trunk/app/views/projects/_destroy_sub_project_error.rhtml 2006-03-23 20:45:11 UTC (rev 209) @@ -0,0 +1 @@ +alert( "Unable to remove the sub-project. Server said:\n\t<%= @message %>"); \ No newline at end of file Added: trunk/app/views/projects/_destroy_sub_project_success.rhtml =================================================================== --- trunk/app/views/projects/_destroy_sub_project_success.rhtml (rev 0) +++ trunk/app/views/projects/_destroy_sub_project_success.rhtml 2006-03-23 20:45:11 UTC (rev 209) @@ -0,0 +1,2 @@ +<%= update_element_function( "SubProject_#{@sub_project.id}", + :action => :remove ) %> \ No newline at end of file Modified: trunk/app/views/projects/_sub_project.rhtml =================================================================== --- trunk/app/views/projects/_sub_project.rhtml 2006-03-23 20:17:43 UTC (rev 208) +++ trunk/app/views/projects/_sub_project.rhtml 2006-03-23 20:45:11 UTC (rev 209) @@ -1,5 +1,15 @@ - - + + \ No newline at end of file Modified: trunk/test/functional/projects_controller_test.rb =================================================================== --- trunk/test/functional/projects_controller_test.rb 2006-03-23 20:17:43 UTC (rev 208) +++ trunk/test/functional/projects_controller_test.rb 2006-03-23 20:45:11 UTC (rev 209) @@ -207,6 +207,19 @@ assert_response :error assert_template '_sub_project_error' end + + def test_destroy_sub_project_success + xhr :post, :destroy_sub_project, :id => 1 + assert_response :success + assert_template '_destroy_sub_project_success' + assert_raises( ActiveRecord::RecordNotFound) { SubProject.find 1 } + end + + def test_destroy_sub_project_failure + xhr :post, :destroy_sub_project, :id => 'foo' + assert_response :error + assert_template '_destroy_sub_project_error' + end private From svn at explainpmt.com Thu Mar 23 15:48:53 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Thu, 23 Mar 2006 20:48:53 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [210] Tagging release 20060325.0 Message-ID: <20060323204853.2472FC98E3@thurlow.textdrive.com> Revision: 210 Author: jwilger Date: 2006-03-23 20:48:52 +0000 (Thu, 23 Mar 2006) Log Message: ----------- Tagging release 20060325.0 Added Paths: ----------- tags/release_20060325.0/ Copied: tags/release_20060325.0 (from rev 209, trunk) From svn at explainpmt.com Thu Mar 23 15:50:20 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Thu, 23 Mar 2006 20:50:20 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [211] Updated version stamp to 20060325.0 Message-ID: <20060323205020.72CBDC98C5@thurlow.textdrive.com> Revision: 211 Author: jwilger Date: 2006-03-23 20:50:19 +0000 (Thu, 23 Mar 2006) Log Message: ----------- Updated version stamp to 20060325.0 Modified Paths: -------------- tags/release_20060325.0/app/helpers/application_helper.rb Modified: tags/release_20060325.0/app/helpers/application_helper.rb =================================================================== --- tags/release_20060325.0/app/helpers/application_helper.rb 2006-03-23 20:48:52 UTC (rev 210) +++ tags/release_20060325.0/app/helpers/application_helper.rb 2006-03-23 20:50:19 UTC (rev 211) @@ -20,7 +20,7 @@ # The methods added to this helper will be available to all templates in the application. module ApplicationHelper - VERSION = 'dev trunk' + VERSION = '20060325.0' # Used to determine if the currently logged in user has administrative # privileges From svn at explainpmt.com Sun Mar 26 12:06:52 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Sun, 26 Mar 2006 17:06:52 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [212] User can now specify a sub project for story cards when creating new story cards. Message-ID: <20060326170652.12B96C9883@thurlow.textdrive.com> Revision: 212 Author: jwilger Date: 2006-03-26 17:06:50 +0000 (Sun, 26 Mar 2006) Log Message: ----------- User can now specify a sub project for story cards when creating new story cards. Modified Paths: -------------- trunk/app/controllers/stories_controller.rb trunk/app/views/stories/new.rhtml trunk/test/functional/stories_controller_test.rb Modified: trunk/app/controllers/stories_controller.rb =================================================================== --- trunk/app/controllers/stories_controller.rb 2006-03-23 20:50:19 UTC (rev 211) +++ trunk/app/controllers/stories_controller.rb 2006-03-26 17:06:50 UTC (rev 212) @@ -55,8 +55,12 @@ flash[:error] = 'Please enter at least one story card title.' redirect_to :controller => 'stories', :action => 'new', :project_id => @project else + unless params[ :sub_project ].empty? + sub_project = SubProject.find params[ :sub_project ] + end params[:story_card_titles].each_line do |title| - @project.stories.create(:title => title) + story = @project.stories.create(:title => title) + sub_project.stories << story if sub_project end flash[:status] = 'New story cards created.' redirect_to :controller => 'stories', :action => 'index', :project_id => @project Modified: trunk/app/views/stories/new.rhtml =================================================================== --- trunk/app/views/stories/new.rhtml 2006-03-23 20:50:19 UTC (rev 211) +++ trunk/app/views/stories/new.rhtml 2006-03-26 17:06:50 UTC (rev 212) @@ -2,6 +2,12 @@ <%= start_form_tag(:controller => 'stories', :action => 'create', :project_id => @project.id) %> +

+

Modified: trunk/test/functional/stories_controller_test.rb =================================================================== --- trunk/test/functional/stories_controller_test.rb 2006-03-23 20:50:19 UTC (rev 211) +++ trunk/test/functional/stories_controller_test.rb 2006-03-26 17:06:50 UTC (rev 212) @@ -25,6 +25,8 @@ class StoriesController; def rescue_action(e) raise e end; end class StoriesControllerTest < Test::Unit::TestCase + fixtures ALL_FIXTURES + def setup @user_one = User.find 2 @project_one = Project.find 1 @@ -33,6 +35,7 @@ @story_two = Story.find 2 @story_three = Story.find 3 @story_six = Story.find 6 + @sub_project_one = sub_projects( :first ) @controller = StoriesController.new @request = ActionController::TestRequest.new @@ -91,15 +94,30 @@ assert_equal 'Create new story cards', assigns(:page_title) end - def test_create + def test_create_with_sub_project + num_a = @project_one.stories.backlog.size + num_b = @sub_project_one.stories.size + post :create, :project_id => @project_one.id, + :story_card_titles => "New Story One\nNew Story Two\nNew Story Three", + :sub_project => @sub_project_one.id + assert_redirected_to :controller => 'stories', :action => 'index', + :project_id => @project_one.id + assert_equal num_a + 3, @project_one.stories( true ).backlog.size + assert_equal num_b + 3, @sub_project_one.stories( true ).size + assert_equal "New story cards created.", flash[:status] + end + + def test_create_without_sub_project num = @project_one.stories.backlog.size post :create, :project_id => @project_one.id, - :story_card_titles => "New Story One\nNew Story Two\nNew Story Three" - assert_redirected_to :controller => 'stories', :action => 'index', :project_id => @project_one.id + :story_card_titles => "New Story One\nNew Story Two\nNew Story Three", + :sub_project => '' + assert_redirected_to :controller => 'stories', :action => 'index', + :project_id => @project_one.id assert_equal num + 3, @project_one.stories( true ).backlog.size assert_equal "New story cards created.", flash[:status] end - + def test_create_empty num = Story.count post :create, :project_id => @project_one.id, :story_card_titles => '' From svn at explainpmt.com Mon Mar 27 11:04:36 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Mon, 27 Mar 2006 16:04:36 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [213] Added "thead" and "tbody" tag sets to output of the CollectionTableHelper-generated tables, and added Message-ID: <20060327160436.D5F4BC987F@thurlow.textdrive.com> Revision: 213 Author: jwilger Date: 2006-03-27 16:04:35 +0000 (Mon, 27 Mar 2006) Log Message: ----------- Added "thead" and "tbody" tag sets to output of the CollectionTableHelper-generated tables, and added CollectionTableHelper::CollectionTable#table_id and #tbody_id attribute writers to allow setting the html id attributes of those elements. Modified Paths: -------------- trunk/app/helpers/collection_table_helper.rb trunk/test/unit/collection_table_helper_test.rb Modified: trunk/app/helpers/collection_table_helper.rb =================================================================== --- trunk/app/helpers/collection_table_helper.rb 2006-03-26 17:06:50 UTC (rev 212) +++ trunk/app/helpers/collection_table_helper.rb 2006-03-27 16:04:35 UTC (rev 213) @@ -47,30 +47,34 @@ # build_table might return the following HTML: # #
Sub-Project:<%= @story.sub_project.name %>
Priority: <%= @story.priority.name %>
<%= sub_project.name %>delete  + <%= link_to_remote 'delete', + :url => { :controller => 'projects', :action => 'destroy_sub_project', + :id => sub_project.id }, + :loading => "Element.toggle('SubProjectSpinner_#{sub_project.id}')", + :complete => "Element.toggle('SubProjectSpinner_#{sub_project.id}')", + :success => evaluate_remote_response, + :failure => evaluate_remote_response + %> + <%= image_tag 'spinner.gif', :id => "SubProjectSpinner_#{sub_project.id}", + :style => 'display: none' -%>
- # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # #
IDLast NameFirst NameEmail
2DoeJanejanedoe at example.comEditDelete
3BodySomesomebody at example.comEditDelete
IDLast NameFirst NameEmail
2DoeJanejanedoe at example.comEditDelete
3BodySomesomebody at example.comEditDelete
class CollectionTable # The attribute of each item in the collection that can be used as a unique @@ -80,6 +84,10 @@ # Takes an array of CSS class names. The class names will be applied in # sequence to the +tr+ tag of each data row. attr_writer :data_row_class + + attr_writer :table_id + + attr_writer :tbody_id # Returns a new CollectionTable object that will operate on the +items+ (any # enumerable) and will display a column for each of the specified +columns+. @@ -116,9 +124,12 @@ # Returns the complete HTML for the table based on the options set on the # object with a data row for each item in the collection. def build_table - output = "\n" + output = "
\n" output += header_row + output += "\n \n" output += @items.inject('') { |txt,item| txt + data_row(item) } + output += " \n" output += "
\n" end @@ -178,18 +189,18 @@ else td = "" end - txt + " #{td + cell_content}\n" + txt + " #{td + cell_content}\n" end - " \n" + - data_cells + " \n" + " \n" + + data_cells + " \n" end # Returns the complete table header row def header_row header_cells = @column_order.inject('') do |txt,col| - txt + " #{heading_for(col)}\n" + txt + " #{heading_for(col)}\n" end - " \n" + header_cells + " \n" + " \n \n" + header_cells + " \n " end Modified: trunk/test/unit/collection_table_helper_test.rb =================================================================== --- trunk/test/unit/collection_table_helper_test.rb 2006-03-26 17:06:50 UTC (rev 212) +++ trunk/test/unit/collection_table_helper_test.rb 2006-03-27 16:04:35 UTC (rev 213) @@ -63,17 +63,24 @@ def test_header_row ct = CollectionTableHelper::CollectionTable.new(@items, [:id,'ID'], :name, :phone) - expected = " \n ID\n Name\n" + - " Phone\n \n" + expected = " \n" + + " \n" + + " ID\n" + + " Name\n" + + " Phone\n" + + " \n" + + " " assert_equal expected, ct.send(:header_row) end def test_data_row ct = CollectionTableHelper::CollectionTable.new(@items, :id, :name, :phone) item = @items.first - expected = " \n #{item.id}\n" + - " #{item.name}\n #{item.phone}\n" + - " \n" + expected = " \n" + + " #{item.id}\n" + + " #{item.name}\n" + + " #{item.phone}\n" + + " \n" assert_equal expected, ct.send(:data_row, item) end @@ -87,11 +94,12 @@ "Edit" } item = @items.first - expected = " \n #{item.id}\n" + - " #{item.name}\n" + - " #{item.phone.gsub('-','#')}\n" + - " Edit\n" + - " \n" + expected = " \n" + + " #{item.id}\n" + + " #{item.name}\n" + + " #{item.phone.gsub('-','#')}\n" + + " Edit\n" + + " \n" assert_equal expected, ct.send(:data_row, item) end @@ -111,7 +119,9 @@ ct = CollectionTableHelper::CollectionTable.new(@items, :id) ct.column_align(:id, 'right') item = @items.shift - expected = " \n #{item.id}\n \n" + expected = " \n" + + " #{item.id}\n" + + " \n" assert_equal expected, ct.send(:data_row, item) end @@ -121,23 +131,59 @@ ct.data_row_class = [ 'odd_row', 'even_row' ] expected = < - - ID - Name - Phone - - - 1 - John Doe - 555-5555 - - - 2 - Jane Doe - 666-6666 - + + + ID + Name + Phone + + + + + 1 + John Doe + 555-5555 + + + 2 + Jane Doe + 666-6666 + + EOF assert_equal expected, ct.build_table end + + def test_table_and_tbody_id + ct = CollectionTableHelper::CollectionTable.new(@items, [:id,'ID'], :name, + :phone) + ct.data_row_class = [ 'odd_row', 'even_row' ] + ct.table_id = 'MyTable' + ct.tbody_id = 'MyTBody' + expected = < + + + ID + Name + Phone + + + + + 1 + John Doe + 555-5555 + + + 2 + Jane Doe + 666-6666 + + + +EOF + assert_equal expected, ct.build_table + end end From svn at explainpmt.com Mon Mar 27 11:21:41 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Mon, 27 Mar 2006 16:21:41 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [214] Reverted last commit (changes to CollectionTableHelper). Message-ID: <20060327162141.9E0C6C976D@thurlow.textdrive.com> Revision: 214 Author: jwilger Date: 2006-03-27 16:21:40 +0000 (Mon, 27 Mar 2006) Log Message: ----------- Reverted last commit (changes to CollectionTableHelper). That module is going to go away, because it's just a big, fat code-smell. It's making it really hard to set up the dnd priority sorting, because it falls apart when you try to do anything more complex than a simple table. Modified Paths: -------------- trunk/app/helpers/collection_table_helper.rb trunk/test/unit/collection_table_helper_test.rb Modified: trunk/app/helpers/collection_table_helper.rb =================================================================== --- trunk/app/helpers/collection_table_helper.rb 2006-03-27 16:04:35 UTC (rev 213) +++ trunk/app/helpers/collection_table_helper.rb 2006-03-27 16:21:40 UTC (rev 214) @@ -47,34 +47,30 @@ # build_table might return the following HTML: # # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # + # #
IDLast NameFirst NameEmail
2DoeJanejanedoe at example.comEditDelete
3BodySomesomebody at example.comEditDelete
IDLast NameFirst NameEmail
2DoeJanejanedoe at example.comEditDelete
3BodySomesomebody at example.comEditDelete
class CollectionTable # The attribute of each item in the collection that can be used as a unique @@ -84,10 +80,6 @@ # Takes an array of CSS class names. The class names will be applied in # sequence to the +tr+ tag of each data row. attr_writer :data_row_class - - attr_writer :table_id - - attr_writer :tbody_id # Returns a new CollectionTable object that will operate on the +items+ (any # enumerable) and will display a column for each of the specified +columns+. @@ -124,12 +116,9 @@ # Returns the complete HTML for the table based on the options set on the # object with a data row for each item in the collection. def build_table - output = "\n" + output = "
\n" output += header_row - output += "\n \n" output += @items.inject('') { |txt,item| txt + data_row(item) } - output += " \n" output += "
\n" end @@ -189,18 +178,18 @@ else td = "" end - txt + " #{td + cell_content}\n" + txt + " #{td + cell_content}\n" end - " \n" + - data_cells + " \n" + " \n" + + data_cells + " \n" end # Returns the complete table header row def header_row header_cells = @column_order.inject('') do |txt,col| - txt + " #{heading_for(col)}\n" + txt + " #{heading_for(col)}\n" end - " \n \n" + header_cells + " \n " + " \n" + header_cells + " \n" end Modified: trunk/test/unit/collection_table_helper_test.rb =================================================================== --- trunk/test/unit/collection_table_helper_test.rb 2006-03-27 16:04:35 UTC (rev 213) +++ trunk/test/unit/collection_table_helper_test.rb 2006-03-27 16:21:40 UTC (rev 214) @@ -63,24 +63,17 @@ def test_header_row ct = CollectionTableHelper::CollectionTable.new(@items, [:id,'ID'], :name, :phone) - expected = " \n" + - " \n" + - " ID\n" + - " Name\n" + - " Phone\n" + - " \n" + - " " + expected = " \n ID\n Name\n" + + " Phone\n \n" assert_equal expected, ct.send(:header_row) end def test_data_row ct = CollectionTableHelper::CollectionTable.new(@items, :id, :name, :phone) item = @items.first - expected = " \n" + - " #{item.id}\n" + - " #{item.name}\n" + - " #{item.phone}\n" + - " \n" + expected = " \n #{item.id}\n" + + " #{item.name}\n #{item.phone}\n" + + " \n" assert_equal expected, ct.send(:data_row, item) end @@ -94,12 +87,11 @@ "Edit" } item = @items.first - expected = " \n" + - " #{item.id}\n" + - " #{item.name}\n" + - " #{item.phone.gsub('-','#')}\n" + - " Edit\n" + - " \n" + expected = " \n #{item.id}\n" + + " #{item.name}\n" + + " #{item.phone.gsub('-','#')}\n" + + " Edit\n" + + " \n" assert_equal expected, ct.send(:data_row, item) end @@ -119,9 +111,7 @@ ct = CollectionTableHelper::CollectionTable.new(@items, :id) ct.column_align(:id, 'right') item = @items.shift - expected = " \n" + - " #{item.id}\n" + - " \n" + expected = " \n #{item.id}\n \n" assert_equal expected, ct.send(:data_row, item) end @@ -131,59 +121,23 @@ ct.data_row_class = [ 'odd_row', 'even_row' ] expected = < - - - ID - Name - Phone - - - - - 1 - John Doe - 555-5555 - - - 2 - Jane Doe - 666-6666 - - + + ID + Name + Phone + + + 1 + John Doe + 555-5555 + + + 2 + Jane Doe + 666-6666 + EOF assert_equal expected, ct.build_table end - - def test_table_and_tbody_id - ct = CollectionTableHelper::CollectionTable.new(@items, [:id,'ID'], :name, - :phone) - ct.data_row_class = [ 'odd_row', 'even_row' ] - ct.table_id = 'MyTable' - ct.tbody_id = 'MyTBody' - expected = < - - - ID - Name - Phone - - - - - 1 - John Doe - 555-5555 - - - 2 - Jane Doe - 666-6666 - - - -EOF - assert_equal expected, ct.build_table - end end From svn at explainpmt.com Mon Mar 27 12:28:10 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Mon, 27 Mar 2006 17:28:10 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [215] Refactored project backlog view ('stories/index.rhtml') to _not_ use the CollectionTableHelper. Message-ID: <20060327172810.65189C9873@thurlow.textdrive.com> Revision: 215 Author: jwilger Date: 2006-03-27 17:28:09 +0000 (Mon, 27 Mar 2006) Log Message: ----------- Refactored project backlog view ('stories/index.rhtml') to _not_ use the CollectionTableHelper. Modified Paths: -------------- trunk/app/views/stories/index.rhtml Modified: trunk/app/views/stories/index.rhtml =================================================================== --- trunk/app/views/stories/index.rhtml 2006-03-27 16:21:40 UTC (rev 214) +++ trunk/app/views/stories/index.rhtml 2006-03-27 17:28:09 UTC (rev 215) @@ -34,63 +34,62 @@ <% end %> - <% sort_header_params = { - :controller => 'stories', :action => 'index', :project_id => @project.id - } - stories_table = CollectionTableHelper::CollectionTable.new(@stories, - [:scid, link_to_sort_by('ID', 'scid', sort_header_params)], - [:sub_project, link_to_sort_by('Sub-Project', 'sub_project.name', - sort_header_params)], - [:title, link_to_sort_by('Title', 'title', sort_header_params)], - [:points, link_to_sort_by('Points', 'points', sort_header_params)], - [:priority, link_to_sort_by('Priority', 'priority', sort_header_params)], - [:risk, link_to_sort_by('Risk', 'risk', sort_header_params)], - [:status, link_to_sort_by('Status', 'status', sort_header_params)], - [:edit,''], [:delete,'']) - stories_table.column_align(:points, 'center') - stories_table.column_align(:edit, 'right') - stories_table.column_align(:delete, 'right') - stories_table.data_row_class = ['odd_row','even_row'] - stories_table.column_modifier(:scid) do |s| - " " + - "" - end - stories_table.column_modifier(:title) do |s| - str = link_to(s.title, - :controller => 'stories', :action => 'show', :id => s.id, - :project_id => s.project.id) - if s.description? - str += ' ' + - link_to(image_tag('info-icon.gif', :alt => '[description]'), - {:controller => 'stories', :action => 'show', :id => s.id, - :project_id => s.project.id}, - :class => 'story_description_icon') - end - str - end - stories_table.column_modifier(:edit) do |s| - link_to('Edit', - :controller => 'stories', - :action => 'edit', - :id => s.id, - :project_id => s.project.id) - end - stories_table.column_modifier(:delete) do |s| - link_to('Delete', { :controller => 'stories', :action => 'delete', - :id => s.id, :project_id => s.project.id }, - :confirm => "Are you sure you want to delete SC#{s.scid}?") - end - stories_table.column_modifier( :sub_project ) do |s| - if s.sub_project.nil? - '' - else - s.sub_project.name - end - end - %> - <%= stories_table.build_table %> - + <% sort_header_params = { :controller => 'stories', + :action => 'index', + :project_id => @project.id } %> + + + + + + + + + + + + + + + <% @stories.each_with_index do |story, i| %> + + + + + + + + + + + + <% end %> + +
<%= link_to_sort_by( 'ID', 'scid', sort_header_params ) -%><%= link_to_sort_by( 'Sub-Project', 'sub_project.name', + sort_header_params ) -%><%= link_to_sort_by( 'Title', 'title', sort_header_params ) -%><%= link_to_sort_by( 'Points', 'points', sort_header_params ) -%><%= link_to_sort_by( 'Priority', 'priority', sort_header_params ) -%><%= link_to_sort_by( 'Risk', 'risk', sort_header_params ) -%><%= link_to_sort_by( 'Status', 'status', sort_header_params ) -%> 
+ + + <%= story.sub_project.name if story.sub_project -%> + <%= link_to( story.title, :controller => 'stories', + :action => 'show', :id => story.id, + :project_id => story.project.id ) %> + <% if story.description? %> + <%= link_to( image_tag( 'info-icon.gif', :alt => '[description]' ), + { :controller => 'stories', :action => 'show', + :id => story.id, :project_id => story.project.id }, + :class => 'story_description_icon' ) %> + <% end %> + <%= story.points -%><%= story.priority -%><%= story.risk -%><%= story.status -%> + <%= link_to( 'Edit', :controller => 'stories', + :action => 'edit', :id => story.id, + :project_id => story.project.id ) %> + + <%= link_to( 'Delete', + { :controller => 'stories', :action => 'delete', + :id => story.id, :project_id => story.project.id }, + :confirm => "Are you sure you want to delete SC#{story.scid}?" ) %> +
<%= end_form_tag %> <% else %>

No stories are in the project backlog.

From svn at explainpmt.com Mon Mar 27 12:53:18 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Mon, 27 Mar 2006 17:53:18 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [216] Refactored project backlog views to use partial templates for rendering of stories list table. Message-ID: <20060327175318.20144C98B2@thurlow.textdrive.com> Revision: 216 Author: jwilger Date: 2006-03-27 17:53:17 +0000 (Mon, 27 Mar 2006) Log Message: ----------- Refactored project backlog views to use partial templates for rendering of stories list table. Modified Paths: -------------- trunk/app/views/stories/index.rhtml Added Paths: ----------- trunk/app/views/stories/_story_list_item.rhtml trunk/app/views/stories/_story_list_table.rhtml Added: trunk/app/views/stories/_story_list_item.rhtml =================================================================== --- trunk/app/views/stories/_story_list_item.rhtml (rev 0) +++ trunk/app/views/stories/_story_list_item.rhtml 2006-03-27 17:53:17 UTC (rev 216) @@ -0,0 +1,35 @@ +<% story = story_list_item %> + + + + + + <%= story.sub_project.name if story.sub_project -%> + + <%= link_to( story.title, :controller => 'stories', + :action => 'show', :id => story.id, + :project_id => story.project.id ) %> + <% if story.description? %> + <%= link_to( image_tag( 'info-icon.gif', :alt => '[description]' ), + { :controller => 'stories', :action => 'show', + :id => story.id, :project_id => story.project.id }, + :class => 'story_description_icon' ) %> + <% end %> + + <%= story.points -%> + <%= story.priority -%> + <%= story.risk -%> + <%= story.status -%> + + <%= link_to( 'Edit', :controller => 'stories', + :action => 'edit', :id => story.id, + :project_id => story.project.id ) %> + + + <%= link_to( 'Delete', + { :controller => 'stories', :action => 'delete', + :id => story.id, :project_id => story.project.id }, + :confirm => "Are you sure you want to delete SC#{story.scid}?" ) %> + + Added: trunk/app/views/stories/_story_list_table.rhtml =================================================================== --- trunk/app/views/stories/_story_list_table.rhtml (rev 0) +++ trunk/app/views/stories/_story_list_table.rhtml 2006-03-27 17:53:17 UTC (rev 216) @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + <%= render :partial => 'story_list_item', :collection => stories %> + +
<%= link_to_sort_by( 'ID', 'scid', sort_header_params ) -%><%= link_to_sort_by( 'Sub-Project', 'sub_project.name', + sort_header_params ) -%><%= link_to_sort_by( 'Title', 'title', sort_header_params ) -%><%= link_to_sort_by( 'Points', 'points', sort_header_params ) -%><%= link_to_sort_by( 'Priority', 'priority', sort_header_params ) -%><%= link_to_sort_by( 'Risk', 'risk', sort_header_params ) -%><%= link_to_sort_by( 'Status', 'status', sort_header_params ) -%> 
Modified: trunk/app/views/stories/index.rhtml =================================================================== --- trunk/app/views/stories/index.rhtml 2006-03-27 17:28:09 UTC (rev 215) +++ trunk/app/views/stories/index.rhtml 2006-03-27 17:53:17 UTC (rev 216) @@ -37,59 +37,9 @@ <% sort_header_params = { :controller => 'stories', :action => 'index', :project_id => @project.id } %> - - - - - - - - - - - - - - - <% @stories.each_with_index do |story, i| %> - - - - - - - - - - - - <% end %> - -
<%= link_to_sort_by( 'ID', 'scid', sort_header_params ) -%><%= link_to_sort_by( 'Sub-Project', 'sub_project.name', - sort_header_params ) -%><%= link_to_sort_by( 'Title', 'title', sort_header_params ) -%><%= link_to_sort_by( 'Points', 'points', sort_header_params ) -%><%= link_to_sort_by( 'Priority', 'priority', sort_header_params ) -%><%= link_to_sort_by( 'Risk', 'risk', sort_header_params ) -%><%= link_to_sort_by( 'Status', 'status', sort_header_params ) -%> 
- - - <%= story.sub_project.name if story.sub_project -%> - <%= link_to( story.title, :controller => 'stories', - :action => 'show', :id => story.id, - :project_id => story.project.id ) %> - <% if story.description? %> - <%= link_to( image_tag( 'info-icon.gif', :alt => '[description]' ), - { :controller => 'stories', :action => 'show', - :id => story.id, :project_id => story.project.id }, - :class => 'story_description_icon' ) %> - <% end %> - <%= story.points -%><%= story.priority -%><%= story.risk -%><%= story.status -%> - <%= link_to( 'Edit', :controller => 'stories', - :action => 'edit', :id => story.id, - :project_id => story.project.id ) %> - - <%= link_to( 'Delete', - { :controller => 'stories', :action => 'delete', - :id => story.id, :project_id => story.project.id }, - :confirm => "Are you sure you want to delete SC#{story.scid}?" ) %> -
+ <%= render( :partial => 'story_list_table', + :locals => { :sort_header_params => sort_header_params, + :stories => @stories } ) %> <%= end_form_tag %> <% else %>

No stories are in the project backlog.

From svn at explainpmt.com Mon Mar 27 13:25:51 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Mon, 27 Mar 2006 18:25:51 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [217] Refactored IterationsController#show to use partials for story listing (shared form the StoriesController). Message-ID: <20060327182551.489B5C98C5@thurlow.textdrive.com> Revision: 217 Author: jwilger Date: 2006-03-27 18:25:50 +0000 (Mon, 27 Mar 2006) Log Message: ----------- Refactored IterationsController#show to use partials for story listing (shared form the StoriesController). Modified Paths: -------------- trunk/app/views/iterations/show.rhtml trunk/app/views/stories/_story_list_item.rhtml trunk/app/views/stories/_story_list_table.rhtml trunk/app/views/stories/index.rhtml Modified: trunk/app/views/iterations/show.rhtml =================================================================== --- trunk/app/views/iterations/show.rhtml 2006-03-27 17:53:17 UTC (rev 216) +++ trunk/app/views/iterations/show.rhtml 2006-03-27 18:25:50 UTC (rev 217) @@ -159,90 +159,12 @@ <% -sort_header_params = { - :controller => 'iterations', :action => 'show', :id => @iteration.id, - :project_id => @project.id -} -stories_table = CollectionTableHelper::CollectionTable.new(@stories, - [:scid, - link_to_sort_by('ID', 'scid', - sort_header_params)], - [:sub_project, - link_to_sort_by('Sub-Project', - 'sub_project.name', - sort_header_params)], - [:title, - link_to_sort_by('Title', 'title', - sort_header_params)], - [:points, - link_to_sort_by('Points', 'points', - sort_header_params)], - [:priority, - link_to_sort_by('Priority', 'priority', - sort_header_params)], - [:risk, - link_to_sort_by('Risk', 'risk', - sort_header_params)], - [:status, - link_to_sort_by('Status', 'status', - sort_header_params)], - :owner, [:edit,nil], [:delete,nil]) -stories_table.data_row_class = ['odd_row', 'even_row'] -stories_table.column_align(:points, 'center') -stories_table.column_align(:edit, 'right') -stories_table.column_align(:delete, 'right') -stories_table.column_modifier(:scid) do |s| - " " + - "" -end -stories_table.column_modifier(:title) do |s| - str = link_to(s.title, - :controller => 'stories', :action => 'show', :id => s.id, - :project_id => s.project.id) - if s.description? - str += ' ' + - link_to(image_tag('info-icon.gif', :alt => '[description]'), - {:controller => 'stories', :action => 'show', :id => s.id, - :project_id => s.project.id}, - :class => 'story_description_icon') - end - str -end -stories_table.column_modifier(:owner) do |s| - if s.has_owner? - s.owner.full_name + ' (' + link_to('release', :controller => 'stories', - :action => 'release_ownership', - :id => s.id, - :project_id => s.project.id) + ')' - else - 'None (' + link_to('take', :controller => 'stories', - :action => 'take_ownership', :id => s.id, - :project_id => s.project.id) + ')' - end -end -stories_table.column_modifier(:edit) do |s| - link_to('Edit', - :controller => 'stories', :action => 'edit', :id => s.id, - :project_id => s.project.id) -end -stories_table.column_modifier(:delete) do |s| - link_to('Delete', - { - :controller => 'stories', :action => 'delete', :id => s.id, - :project_id => s.project.id, :iteration_id => @iteration.id - }, - :confirm => "Are you sure you want to delete SC#{s.scid}?") -end -stories_table.column_modifier( :sub_project ) do |s| - if s.sub_project.nil? - '' - else - s.sub_project.name - end -end +sort_header_params = { :controller => 'iterations', :action => 'show', + :id => @iteration.id, :project_id => @project.id } %> -<%= stories_table.build_table %> +<%= render( :partial => 'stories/story_list_table', + :locals => { :stories => @stories, + :sort_header_params => sort_header_params } ) %> <%= end_form_tag %> <% else %>

No stories have been assigned to this iteration.

Modified: trunk/app/views/stories/_story_list_item.rhtml =================================================================== --- trunk/app/views/stories/_story_list_item.rhtml 2006-03-27 17:53:17 UTC (rev 216) +++ trunk/app/views/stories/_story_list_item.rhtml 2006-03-27 18:25:50 UTC (rev 217) @@ -21,6 +21,22 @@ <%= story.priority -%> <%= story.risk -%> <%= story.status -%> + <% unless hide_cols.include?( :owner ) %> + + <%= + if story.has_owner? + story.owner.full_name + ' (' + link_to('release', :controller => 'stories', + :action => 'release_ownership', + :id => story.id, + :project_id => story.project.id) + ')' + else + 'None (' + link_to('take', :controller => 'stories', + :action => 'take_ownership', :id => story.id, + :project_id => story.project.id) + ')' + end + %> + + <% end %> <%= link_to( 'Edit', :controller => 'stories', :action => 'edit', :id => story.id, Modified: trunk/app/views/stories/_story_list_table.rhtml =================================================================== --- trunk/app/views/stories/_story_list_table.rhtml 2006-03-27 17:53:17 UTC (rev 216) +++ trunk/app/views/stories/_story_list_table.rhtml 2006-03-27 18:25:50 UTC (rev 217) @@ -1,3 +1,4 @@ +<% hide_cols ||= [] %> @@ -9,10 +10,14 @@ + <% unless hide_cols.include?( :owner ) %> + + <% end %> - <%= render :partial => 'story_list_item', :collection => stories %> + <%= render( :partial => 'stories/story_list_item', :collection => stories, + :locals => { :hide_cols => hide_cols } ) %>
<%= link_to_sort_by( 'Priority', 'priority', sort_header_params ) -%> <%= link_to_sort_by( 'Risk', 'risk', sort_header_params ) -%> <%= link_to_sort_by( 'Status', 'status', sort_header_params ) -%><%= link_to_sort_by( 'Owner', 'owner', sort_header_params ) -%> 
Modified: trunk/app/views/stories/index.rhtml =================================================================== --- trunk/app/views/stories/index.rhtml 2006-03-27 17:53:17 UTC (rev 216) +++ trunk/app/views/stories/index.rhtml 2006-03-27 18:25:50 UTC (rev 217) @@ -39,7 +39,8 @@ :project_id => @project.id } %> <%= render( :partial => 'story_list_table', :locals => { :sort_header_params => sort_header_params, - :stories => @stories } ) %> + :stories => @stories, + :hide_cols => [ :owner ] } ) %> <%= end_form_tag %> <% else %>

No stories are in the project backlog.

From svn at explainpmt.com Mon Mar 27 13:51:14 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Mon, 27 Mar 2006 18:51:14 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [218] Fixed incorrect redirection when deleting a story card from an iteration view. Message-ID: <20060327185114.02995C976B@thurlow.textdrive.com> Revision: 218 Author: jwilger Date: 2006-03-27 18:51:12 +0000 (Mon, 27 Mar 2006) Log Message: ----------- Fixed incorrect redirection when deleting a story card from an iteration view. Modified Paths: -------------- trunk/app/controllers/stories_controller.rb trunk/test/functional/stories_controller_test.rb Modified: trunk/app/controllers/stories_controller.rb =================================================================== --- trunk/app/controllers/stories_controller.rb 2006-03-27 18:25:50 UTC (rev 217) +++ trunk/app/controllers/stories_controller.rb 2006-03-27 18:51:12 UTC (rev 218) @@ -105,13 +105,10 @@ # Destroys the story card identified by the 'id' request parameter. def delete + register_referer Story.destroy(params['id']) flash[:status] = 'The story card was deleted.' - if params['iteration_id'] - redirect_to :controller => 'iterations', :action => 'show', - :id => params['iteration_id'], - :project_id => @project.id.to_s - else + unless redirect_to_referer redirect_to :controller => 'stories', :action => 'index', :project_id => @project.id.to_s end Modified: trunk/test/functional/stories_controller_test.rb =================================================================== --- trunk/test/functional/stories_controller_test.rb 2006-03-27 18:25:50 UTC (rev 217) +++ trunk/test/functional/stories_controller_test.rb 2006-03-27 18:51:12 UTC (rev 218) @@ -80,10 +80,10 @@ end def test_delete_from_iteration + @request.session[:referer] = 'http://test.host/project/1/iterations/show/1' get :delete, 'id' => @story_one.id, 'project_id' => @project_one.id, :iteration_id => @iteration_one.id - assert_redirected_to :controller => 'iterations', :action => 'show', - :id => @iteration_one.id.to_s, :project_id => @project_one.id.to_s + assert_redirected_to 'http://test.host/project/1/iterations/show/1' assert_raise( ActiveRecord::RecordNotFound ) { Story.find @story_one.id } end From svn at explainpmt.com Mon Mar 27 14:11:11 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Mon, 27 Mar 2006 19:11:11 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [219] Refactored story lists on dashboard views to use shared partials. Message-ID: <20060327191111.BA072C988D@thurlow.textdrive.com> Revision: 219 Author: jwilger Date: 2006-03-27 19:11:10 +0000 (Mon, 27 Mar 2006) Log Message: ----------- Refactored story lists on dashboard views to use shared partials. Modified Paths: -------------- trunk/app/views/dashboard/index.rhtml trunk/app/views/dashboard/project.rhtml trunk/app/views/iterations/show.rhtml trunk/app/views/stories/_story_list_item.rhtml trunk/app/views/stories/_story_list_table.rhtml trunk/app/views/stories/index.rhtml Modified: trunk/app/views/dashboard/index.rhtml =================================================================== --- trunk/app/views/dashboard/index.rhtml 2006-03-27 18:51:12 UTC (rev 218) +++ trunk/app/views/dashboard/index.rhtml 2006-03-27 19:11:10 UTC (rev 219) @@ -8,67 +8,11 @@ <% unless @stories.empty? %>

My Stories (all projects):

- <% - sort_header_params = { :controller => 'dashboard', :action => 'index' } - stories_table = CollectionTableHelper::CollectionTable.new(@stories, - [:project, - link_to_sort_by('Project', - 'project.name', - sort_header_params)], - [:sub_project, - link_to_sort_by('Sub-Project', - 'sub_project.name', - sort_header_params)], - [:scid, - link_to_sort_by('ID', 'scid', - sort_header_params)], - [:title, - link_to_sort_by('Title', 'title', - sort_header_params)], - [:points, - link_to_sort_by('Points', 'points', - sort_header_params)], - [:priority, - link_to_sort_by('Priority', 'priority', - sort_header_params)], - [:risk, - link_to_sort_by('Risk', 'risk', - sort_header_params)], - [:status, - link_to_sort_by('Status', 'status', - sort_header_params)], - [:edit,'']) - stories_table.column_align(:points, 'center') - stories_table.column_align(:edit, 'right') - stories_table.data_row_class = ['odd_row','even_row'] - stories_table.column_modifier(:project) do |s| - link_to(s.project.name, :controller => 'dashboard', - :action => 'index', :project_id => s.project.id) - end - stories_table.column_modifier( :sub_project ) do |s| - if s.sub_project.nil? - '' - else - s.sub_project.name - end - end - stories_table.column_modifier(:scid) { |s| "SC#{s.scid}" } - stories_table.column_modifier(:title) do |s| - link_to(s.title, - :controller => 'stories', - :action => 'show', - :id => s.id, - :project_id => s.project.id) - end - stories_table.column_modifier(:edit) do |s| - link_to('Edit', - :controller => 'stories', - :action => 'edit', - :id => s.id, - :project_id => s.project.id) - end - %> - <%= stories_table.build_table %> + <% sort_header_params = { :controller => 'dashboard', :action => 'index' } %> + <%= render( :partial => 'stories/story_list_table', + :locals => { :stories => @stories, + :sort_header_params => sort_header_params, + :hide_cols => [ :owner, :delete ] } ) %> <% end %> Modified: trunk/app/views/dashboard/project.rhtml =================================================================== --- trunk/app/views/dashboard/project.rhtml 2006-03-27 18:51:12 UTC (rev 218) +++ trunk/app/views/dashboard/project.rhtml 2006-03-27 19:11:10 UTC (rev 219) @@ -5,61 +5,10 @@ :params => {:project_id => @project.id}) %> <% unless @stories.empty? %>

My Stories:

-<% -sort_header_params = { - :controller => 'dashboard', - :action => 'index', - :project_id => @project.id -} -stories_table = CollectionTableHelper::CollectionTable.new(@stories, - [:scid, - link_to_sort_by('ID', 'scid', - sort_header_params)], - [:sub_project, - link_to_sort_by('Sub-Project', - 'sub_project.name', - sort_header_params)], - [:title, - link_to_sort_by('Title', 'title', - sort_header_params)], - [:points, - link_to_sort_by('Points', 'points', - sort_header_params)], - [:priority, - link_to_sort_by('Priority', 'priority', - sort_header_params)], - [:risk, - link_to_sort_by('Risk', 'risk', - sort_header_params)], - [:status, - link_to_sort_by('Status', 'status', - sort_header_params)], - [:edit,'']) -stories_table.column_align(:points, 'center') -stories_table.column_align(:edit, 'right') -stories_table.data_row_class = ['odd_row','even_row'] -stories_table.column_modifier(:scid) { |s| "SC#{s.scid}" } -stories_table.column_modifier( :sub_project ) do |s| - if s.sub_project.nil? - '' - else - s.sub_project.name - end -end -stories_table.column_modifier(:title) do |s| - link_to(s.title, - :controller => 'stories', - :action => 'show', - :id => s.id, - :project_id => s.project.id) -end -stories_table.column_modifier(:edit) do |s| - link_to('Edit', - :controller => 'stories', - :action => 'edit', - :id => s.id, - :project_id => s.project.id) -end -%> -<%= stories_table.build_table %> +<% sort_header_params = { :controller => 'dashboard', :action => 'index', + :project_id => @project.id } %> +<%= render( :partial => 'stories/story_list_table', + :locals => { :stories => @stories, + :sort_header_params => sort_header_params, + :hide_cols => [ :owner, :delete, :project ] } ) %> <% end %> Modified: trunk/app/views/iterations/show.rhtml =================================================================== --- trunk/app/views/iterations/show.rhtml 2006-03-27 18:51:12 UTC (rev 218) +++ trunk/app/views/iterations/show.rhtml 2006-03-27 19:11:10 UTC (rev 219) @@ -164,7 +164,8 @@ %> <%= render( :partial => 'stories/story_list_table', :locals => { :stories => @stories, - :sort_header_params => sort_header_params } ) %> + :sort_header_params => sort_header_params, + :hide_cols => [ :project ] } ) %> <%= end_form_tag %> <% else %>

No stories have been assigned to this iteration.

Modified: trunk/app/views/stories/_story_list_item.rhtml =================================================================== --- trunk/app/views/stories/_story_list_item.rhtml 2006-03-27 18:51:12 UTC (rev 218) +++ trunk/app/views/stories/_story_list_item.rhtml 2006-03-27 19:11:10 UTC (rev 219) @@ -5,6 +5,10 @@ value="<%= story.id -%>" id="story_card_check_<%= story.id -%>" /> + <% unless hide_cols.include?( :project ) %> + <%= link_to( story.project.name, :controller => 'dashboard', + :action => 'index', :project_id => story.project.id ) -%> + <% end %> <%= story.sub_project.name if story.sub_project -%> <%= link_to( story.title, :controller => 'stories', @@ -42,10 +46,12 @@ :action => 'edit', :id => story.id, :project_id => story.project.id ) %> - - <%= link_to( 'Delete', - { :controller => 'stories', :action => 'delete', - :id => story.id, :project_id => story.project.id }, - :confirm => "Are you sure you want to delete SC#{story.scid}?" ) %> - + <% unless hide_cols.include?( :delete ) %> + + <%= link_to( 'Delete', + { :controller => 'stories', :action => 'delete', + :id => story.id, :project_id => story.project.id }, + :confirm => "Are you sure you want to delete SC#{story.scid}?" ) %> + + <% end %> Modified: trunk/app/views/stories/_story_list_table.rhtml =================================================================== --- trunk/app/views/stories/_story_list_table.rhtml 2006-03-27 18:51:12 UTC (rev 218) +++ trunk/app/views/stories/_story_list_table.rhtml 2006-03-27 19:11:10 UTC (rev 219) @@ -3,6 +3,9 @@ <%= link_to_sort_by( 'ID', 'scid', sort_header_params ) -%> + <% unless hide_cols.include?( :project ) %> + <%= link_to_sort_by( 'Project', 'project.name', sort_header_params ) -%> + <% end %> <%= link_to_sort_by( 'Sub-Project', 'sub_project.name', sort_header_params ) -%> <%= link_to_sort_by( 'Title', 'title', sort_header_params ) -%> @@ -13,7 +16,10 @@ <% unless hide_cols.include?( :owner ) %> <%= link_to_sort_by( 'Owner', 'owner', sort_header_params ) -%> <% end %> -   +   + <% unless hide_cols.include?( :delete ) %> +   + <% end %> Modified: trunk/app/views/stories/index.rhtml =================================================================== --- trunk/app/views/stories/index.rhtml 2006-03-27 18:51:12 UTC (rev 218) +++ trunk/app/views/stories/index.rhtml 2006-03-27 19:11:10 UTC (rev 219) @@ -40,7 +40,7 @@ <%= render( :partial => 'story_list_table', :locals => { :sort_header_params => sort_header_params, :stories => @stories, - :hide_cols => [ :owner ] } ) %> + :hide_cols => [ :owner, :project ] } ) %> <%= end_form_tag %> <% else %>

No stories are in the project backlog.

From svn at explainpmt.com Mon Mar 27 14:22:41 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Mon, 27 Mar 2006 19:22:41 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [220] Refactored IterationsController#select_stories view to use the new shared partial for listing the stories to select from. Message-ID: <20060327192241.9CE7DC9887@thurlow.textdrive.com> Revision: 220 Author: jwilger Date: 2006-03-27 19:22:40 +0000 (Mon, 27 Mar 2006) Log Message: ----------- Refactored IterationsController#select_stories view to use the new shared partial for listing the stories to select from. Modified Paths: -------------- trunk/app/controllers/iterations_controller.rb trunk/app/views/iterations/select_stories.rhtml trunk/app/views/stories/_story_list_item.rhtml trunk/app/views/stories/_story_list_table.rhtml Modified: trunk/app/controllers/iterations_controller.rb =================================================================== --- trunk/app/controllers/iterations_controller.rb 2006-03-27 19:11:10 UTC (rev 219) +++ trunk/app/controllers/iterations_controller.rb 2006-03-27 19:22:40 UTC (rev 220) @@ -145,7 +145,7 @@ # the selected stories to the iteration being displayed. def select_stories @page_title = "Assign Story Cards" - SortHelper.columns = %w(id title points priority risk status) + SortHelper.columns = %w(scid title points priority risk status) SortHelper.default_order = %w(status priority risk) @stories = @project.stories.backlog.select { |s| s.status != Story::Status::New and Modified: trunk/app/views/iterations/select_stories.rhtml =================================================================== --- trunk/app/views/iterations/select_stories.rhtml 2006-03-27 19:11:10 UTC (rev 219) +++ trunk/app/views/iterations/select_stories.rhtml 2006-03-27 19:22:40 UTC (rev 220) @@ -5,46 +5,12 @@

-<% -sort_header_params = { - :controller => 'iterations', - :action => 'select_stories', - :id => @iteration.id, - :project_id => @project.id -} -stories_table = CollectionTableHelper::CollectionTable.new(@stories, - [:scid, - link_to_sort_by('ID', 'scid', - sort_header_params)], - [:title, - link_to_sort_by('Title', 'title', - sort_header_params)], - [:points, - link_to_sort_by('Points', 'points', - sort_header_params)], - [:priority, - link_to_sort_by('Priority', 'priority', - sort_header_params)], - [:risk, - link_to_sort_by('Risk', 'risk', - sort_header_params)], - [:status, - link_to_sort_by('Status', 'status', - sort_header_params)]) -stories_table.column_align(:points, 'center') -stories_table.data_row_class = ['odd_row','even_row'] -stories_table.column_modifier(:scid) do |s| - " " + - "" -end -stories_table.column_modifier(:title) do |s| - link_to(s.title, - :controller => 'stories', - :action => 'show', - :id => s.id, - :project_id => s.project.id) -end -%> -<%= stories_table.build_table %> +<% sort_header_params = { :controller => 'iterations', + :action => 'select_stories', + :id => @iteration.id, :project_id => @project.id } %> +<%= render( :partial => 'stories/story_list_table', + :locals => { :stories => @stories, + :sort_header_params => sort_header_params, + :hide_cols => [ :owner, :delete, :edit, + :project ] } ) %> <%= end_form_tag %> Modified: trunk/app/views/stories/_story_list_item.rhtml =================================================================== --- trunk/app/views/stories/_story_list_item.rhtml 2006-03-27 19:11:10 UTC (rev 219) +++ trunk/app/views/stories/_story_list_item.rhtml 2006-03-27 19:22:40 UTC (rev 220) @@ -41,11 +41,13 @@ %> <% end %> - - <%= link_to( 'Edit', :controller => 'stories', - :action => 'edit', :id => story.id, - :project_id => story.project.id ) %> - + <% unless hide_cols.include?( :edit ) %> + + <%= link_to( 'Edit', :controller => 'stories', + :action => 'edit', :id => story.id, + :project_id => story.project.id ) %> + + <% end %> <% unless hide_cols.include?( :delete ) %> <%= link_to( 'Delete', Modified: trunk/app/views/stories/_story_list_table.rhtml =================================================================== --- trunk/app/views/stories/_story_list_table.rhtml 2006-03-27 19:11:10 UTC (rev 219) +++ trunk/app/views/stories/_story_list_table.rhtml 2006-03-27 19:22:40 UTC (rev 220) @@ -16,7 +16,9 @@ <% unless hide_cols.include?( :owner ) %> <%= link_to_sort_by( 'Owner', 'owner', sort_header_params ) -%> <% end %> -   + <% unless hide_cols.include?( :edit ) %> +   + <% end %> <% unless hide_cols.include?( :delete ) %>   <% end %> From svn at explainpmt.com Tue Mar 28 09:53:21 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Tue, 28 Mar 2006 14:53:21 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [221] Removed CollectionTableHelper code. Message-ID: <20060328145321.41A5BC9865@thurlow.textdrive.com> Revision: 221 Author: jwilger Date: 2006-03-28 14:53:19 +0000 (Tue, 28 Mar 2006) Log Message: ----------- Removed CollectionTableHelper code. Modified Paths: -------------- trunk/app/controllers/application.rb Removed Paths: ------------- trunk/app/helpers/collection_table_helper.rb trunk/test/unit/collection_table_helper_test.rb Property Changed: ---------------- trunk/log/ Modified: trunk/app/controllers/application.rb =================================================================== --- trunk/app/controllers/application.rb 2006-03-27 19:22:40 UTC (rev 220) +++ trunk/app/controllers/application.rb 2006-03-28 14:53:19 UTC (rev 221) @@ -25,12 +25,12 @@ # here to prevent an error when the session variables are read in model :user model :project + model :sub_project model :iteration model :story model :milestone helper :sort - helper :collection_table layout :choose_layout before_filter :check_authentication Deleted: trunk/app/helpers/collection_table_helper.rb =================================================================== --- trunk/app/helpers/collection_table_helper.rb 2006-03-27 19:22:40 UTC (rev 220) +++ trunk/app/helpers/collection_table_helper.rb 2006-03-28 14:53:19 UTC (rev 221) @@ -1,210 +0,0 @@ -############################################################################## -# eXPlain Project Management Tool -# Copyright (C) 2005 John Wilger -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -############################################################################## - - -module CollectionTableHelper - # Creates an HTML table based on any collection (Enumerable) of objects. - # - # Example: - # - # # Assuming we have a Person model class with the attributes - # # id, last_name, first_name, email and we have assigned the - # # @people variable in the controller as @people = Person.find_all - # people_table = CollectionTable.new(@people, [:id,'ID'], :last_name, - # :first_name, :email, [:edit,nil], [:delete,nil]) - # people_table.data_row_class = ['odd_row','even_row'] - # people_table.column_align(:id, 'right') - # people_table.column_modifier(:email) do |p| - # "p.email" - # end - # people_table.column_modifier(:edit) do |p| - # link_to('Edit', :controller => 'people', :action => 'edit', - # :id => p.id) - # end - # people_table.column_modifier(:delete) do |p| - # link_to('Delete', :controller => 'people', :action => 'delete', - # :id => p.id) - # end - # people_table.build_table - # - # Depending on the actual Person records in the collection, the call to - # build_table might return the following HTML: - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - # - #
IDLast NameFirst NameEmail
2DoeJanejanedoe at example.comEditDelete
3BodySomesomebody at example.comEditDelete
- class CollectionTable - # The attribute of each item in the collection that can be used as a unique - # identifier. Defaults to the +id+ attribute. - attr_accessor :item_id_attr - - # Takes an array of CSS class names. The class names will be applied in - # sequence to the +tr+ tag of each data row. - attr_writer :data_row_class - - # Returns a new CollectionTable object that will operate on the +items+ (any - # enumerable) and will display a column for each of the specified +columns+. - # Each column can be specified either as a symbol (:column_name) which will - # be displayed using the humanized form (Column Name), or it can be - # specified as a two-element array ([:column_name,'Text for Header'] where - # the first element identifies the column, and the second element specifies - # the text to display for that column in the table header. If the second - # element is +nil+ that column will have a blank header. In either case, the - # column identifier (the symbol part) should usually match the name of an - # attribute on the items in the collection. This attribute will be used as - # the data to display in that column. If you specify an identifier which - # _does not_ match an attribute of the items in the collection, you _must_ - # specify a #column_modifier for that column, or you will get run-time - # exceptions. - def initialize(items, *columns) - @items = items - @item_id_attr = :id - @columns = {} - @column_order = [] - @column_modifiers = {} - @column_alignments = {} - columns.each do |col| - if col.kind_of? Enumerable - @column_order << col.first - @columns[col.first] = (col.last || '') - else - @column_order << col - @columns[col] = Inflector.humanize(col.to_s) - end - end - end - - # Returns the complete HTML for the table based on the options set on the - # object with a data row for each item in the collection. - def build_table - output = "\n" - output += header_row - output += @items.inject('') { |txt,item| txt + data_row(item) } - output += "
\n" - end - - # Allows you to modify the contents of a column for each of the data rows by - # specifying the identifier for the column (as specified in the - # CollectionTable.new method) and providing a block that returns the - # contents of the cell. The block should take one argument, which is the - # item in the collection that is being operated on (not just the property - # that would correspond to the column). - # - # call-seq: - # column_modifier(column) { |item| item.some_property.to_s } - # - def column_modifier(column, &block) - @column_modifiers[column] = block - end - - # Allows you to set the HTML +align+ attribute on the data cells in the - # specified column. - def column_align(column, alignment) - @column_alignments[column] = alignment - end - - # See the documentation for the attribute definition - def data_row_class=(classes) #:nodoc: - @data_row_class = classes.to_a - end - - # See the documentation for the attribute definition - def item_id_attr=(attr_name) #:nodoc: - @items.each do |i| - unless i.respond_to?(attr_name) - raise "Not all items in collection have an #{attr_name.to_s} " + - "attribute." - end - end - @item_id_attr = attr_name - end - - private - - # Returns the content for the table header for the specified column. - def heading_for(column) - @columns[column] or raise "Heading requested for unknown column" - end - - # Returns the table data row for +item+ - def data_row(item) - data_cells = @column_order.inject('') do |txt,col| - if @column_modifiers[col] - cell_content = @column_modifiers[col].call(item) - else - cell_content = item.send(col).to_s - end - if @column_alignments[col] - td = "" - else - td = "" - end - txt + " #{td + cell_content}\n" - end - " \n" + - data_cells + " \n" - end - - # Returns the complete table header row - def header_row - header_cells = @column_order.inject('') do |txt,col| - txt + " #{heading_for(col)}\n" - end - " \n" + header_cells + " \n" - end - - - # Returns the class HTML attribute for the data table row (if any) - def current_row_class - unless @data_row_class.nil? - @used_data_row_class ||= [] - if current_class = @data_row_class.shift - @used_data_row_class << current_class - else - @data_row_class = @used_data_row_class - @used_data_row_class = [ current_class = @data_row_class.shift ] - end - return " class=\"#{current_class}\"" - end - end - end -end Property changes on: trunk/log ___________________________________________________________________ Name: svn:ignore - *.log + *.log mongrel.pid Deleted: trunk/test/unit/collection_table_helper_test.rb =================================================================== --- trunk/test/unit/collection_table_helper_test.rb 2006-03-27 19:22:40 UTC (rev 220) +++ trunk/test/unit/collection_table_helper_test.rb 2006-03-28 14:53:19 UTC (rev 221) @@ -1,143 +0,0 @@ -############################################################################## -# eXPlain Project Management Tool -# Copyright (C) 2005 John Wilger -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -############################################################################## - - -require File.dirname(__FILE__) + '/../test_helper' -require 'collection_table_helper' - -class CollectionTableHelperTest < Test::Unit::TestCase - TestItem = Struct.new(:id, :name, :phone) - TestItemB = Struct.new(:id, :phone) - def setup - @items = [ TestItem.new(1, 'John Doe', '555-5555'), - TestItem.new(2, 'Jane Doe', '666-6666') ] - end - - def test_collection_table_initialize - assert_nothing_raised { - ct = CollectionTableHelper::CollectionTable.new(@items, :id, :name, - :phone) - } - end - - def test_heading_for - ct = CollectionTableHelper::CollectionTable.new(@items, [:id,'ID'], :name, - :phone, [:edit,nil], - [:delete,'']) - assert_equal 'ID', ct.send(:heading_for, :id) - assert_equal 'Name', ct.send(:heading_for, :name) - assert_equal 'Phone', ct.send(:heading_for, :phone) - assert_equal '', ct.send(:heading_for, :edit) - assert_equal '', ct.send(:heading_for, :delete) - assert_raises(RuntimeError) { ct.send(:heading_for, :not_a_column) } - end - - def test_item_id_attribute - @items << TestItemB.new(3, '777-7777') - ct = CollectionTableHelper::CollectionTable.new(@items, :name, :phone) - assert_equal :id, ct.item_id_attr - - assert_nothing_raised { ct.item_id_attr = :phone } - assert_equal :phone, ct.item_id_attr - - # TestItemB class has no 'name' attribute - assert_raises(RuntimeError) { ct.item_id_attr = :name } - end - - def test_header_row - ct = CollectionTableHelper::CollectionTable.new(@items, [:id,'ID'], :name, - :phone) - expected = " \n ID\n Name\n" + - " Phone\n \n" - assert_equal expected, ct.send(:header_row) - end - - def test_data_row - ct = CollectionTableHelper::CollectionTable.new(@items, :id, :name, :phone) - item = @items.first - expected = " \n #{item.id}\n" + - " #{item.name}\n #{item.phone}\n" + - " \n" - assert_equal expected, ct.send(:data_row, item) - end - - def test_column_modifier - ct = CollectionTableHelper::CollectionTable.new(@items, :id, :name, :phone, - :edit) - ct.column_modifier(:phone) { |item| - item.phone.gsub('-', '#') - } - ct.column_modifier(:edit) { |item| - "Edit" - } - item = @items.first - expected = " \n #{item.id}\n" + - " #{item.name}\n" + - " #{item.phone.gsub('-','#')}\n" + - " Edit\n" + - " \n" - assert_equal expected, ct.send(:data_row, item) - end - - def test_alternating_data_row_classes - ct = CollectionTableHelper::CollectionTable.new(@items, :id, :name, :phone) - @items << @items.first - ct.data_row_class = [ 'odd_row', 'even_row' ] - item = @items.shift - assert(/#{item.id}\n \n" - assert_equal expected, ct.send(:data_row, item) - end - - def test_build_table - ct = CollectionTableHelper::CollectionTable.new(@items, [:id,'ID'], :name, - :phone) - ct.data_row_class = [ 'odd_row', 'even_row' ] - expected = < - - ID - Name - Phone - - - 1 - John Doe - 555-5555 - - - 2 - Jane Doe - 666-6666 - - -EOF - assert_equal expected, ct.build_table - end -end From svn at explainpmt.com Tue Mar 28 10:14:37 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Tue, 28 Mar 2006 15:14:37 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [222] Renamed Story#priority to Story#value, Story::Priority to Story::Value and Story::Priorities to Story::Values; and renamed the Message-ID: <20060328151437.68C5CC98E6@thurlow.textdrive.com> Revision: 222 Author: jwilger Date: 2006-03-28 15:14:34 +0000 (Tue, 28 Mar 2006) Log Message: ----------- Renamed Story#priority to Story#value, Story::Priority to Story::Value and Story::Priorities to Story::Values; and renamed the labels as appropriate where they appeared in the interface. Modified Paths: -------------- trunk/app/controllers/dashboard_controller.rb trunk/app/controllers/iterations_controller.rb trunk/app/controllers/stories_controller.rb trunk/app/models/story.rb trunk/app/views/stories/_story_form.rhtml trunk/app/views/stories/_story_list_item.rhtml trunk/app/views/stories/_story_list_table.rhtml trunk/app/views/stories/show.rhtml trunk/test/fixtures/stories.yml trunk/test/functional/dashboard_controller_test.rb trunk/test/unit/story_test.rb Added Paths: ----------- trunk/db/migrate/004_change_prioritycolumn_to_value_column_on_stories_table.rb Modified: trunk/app/controllers/dashboard_controller.rb =================================================================== --- trunk/app/controllers/dashboard_controller.rb 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/app/controllers/dashboard_controller.rb 2006-03-28 15:14:34 UTC (rev 222) @@ -54,12 +54,12 @@ def sort_stories(stories) if @project.nil? SortHelper.columns = %w( project.name sub_project.name scid title points - priority risk status ) + value risk status ) else - SortHelper.columns = %w( sub_project.name scid title points priority + SortHelper.columns = %w( sub_project.name scid title points value risk status ) end - SortHelper.default_order = %w( status priority risk ) + SortHelper.default_order = %w( status value risk ) stories = stories.sort do |a,b| SortHelper.sort(a,b,params) end Modified: trunk/app/controllers/iterations_controller.rb =================================================================== --- trunk/app/controllers/iterations_controller.rb 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/app/controllers/iterations_controller.rb 2006-03-28 15:14:34 UTC (rev 222) @@ -116,9 +116,9 @@ def show @iteration = Iteration.find(params['id']) @page_title = "Iteration: #{@iteration.start_date} - #{@iteration.stop_date}" - SortHelper.columns = %w(scid sub_project.name title points priority risk + SortHelper.columns = %w(scid sub_project.name title points value risk status) - SortHelper.default_order = %w(status priority risk) + SortHelper.default_order = %w(status value risk) @stories = @iteration.stories.sort do |a,b| SortHelper.sort(a,b,params) end @@ -145,8 +145,8 @@ # the selected stories to the iteration being displayed. def select_stories @page_title = "Assign Story Cards" - SortHelper.columns = %w(scid title points priority risk status) - SortHelper.default_order = %w(status priority risk) + SortHelper.columns = %w(scid title points value risk status) + SortHelper.default_order = %w(status value risk) @stories = @project.stories.backlog.select { |s| s.status != Story::Status::New and s.status != Story::Status::Cancelled Modified: trunk/app/controllers/stories_controller.rb =================================================================== --- trunk/app/controllers/stories_controller.rb 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/app/controllers/stories_controller.rb 2006-03-28 15:14:34 UTC (rev 222) @@ -27,9 +27,9 @@ # non-blank value). def index @page_title = "Backlog" - SortHelper.columns = %w( scid sub_project.name title points priority risk + SortHelper.columns = %w( scid sub_project.name title points value risk status ) - SortHelper.default_order = %w( status priority risk ) + SortHelper.default_order = %w( status value risk ) if params['show_cancelled'] @stories = @project.stories.backlog else @@ -85,7 +85,7 @@ def update @page_title = "Edit story card" @selected_main_menu_link = :none - modify_risk_status_and_priority_params + modify_risk_status_and_value_params story = Story.find(params['id']) story.attributes = params['story'] if story.valid? @@ -144,18 +144,18 @@ protected - # Sets the parameter values for a story's status, priority and risk to the + # Sets the parameter values for a story's status, value and risk to the # actual objects that need to be assigned based on the integer value # originally passed in that parameter. - def modify_risk_status_and_priority_params + def modify_risk_status_and_value_params if params['story'] if params['story']['status'] params['story']['status'] = Story::Status.new(params['story']['status'].to_i) end - if params['story']['priority'] - params['story']['priority'] = - Story::Priority.new(params['story']['priority'].to_i) + if params['story']['value'] + params['story']['value'] = + Story::Value.new(params['story']['value'].to_i) end if params['story']['risk'] params['story']['risk'] = Modified: trunk/app/models/story.rb =================================================================== --- trunk/app/models/story.rb 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/app/models/story.rb 2006-03-28 15:14:34 UTC (rev 222) @@ -34,7 +34,7 @@ # # As well as the following aggregations: # composed_of :status, :mapping => %w(status order) -# composed_of :priority, :mapping => %w(priority order) +# composed_of :value, :mapping => %w(value order) # composed_of :risk, :mapping => %w(risk order) # class Story < ActiveRecord::Base @@ -47,8 +47,8 @@ # The collection of defined Status objects Statuses = [] - # The collection of defined Priority objects - Priorities = [] + # The collection of defined Value objects + Values = [] # The collection of defined Risk objects Risks = [] @@ -60,7 +60,7 @@ validates_length_of :title, :maximum => 255 composed_of :status, :mapping => %w(status order) - composed_of :priority, :mapping => %w(priority order) + composed_of :value, :mapping => %w(value order) composed_of :risk, :mapping => %w(risk order) class RankedValue @@ -132,19 +132,19 @@ Statuses << Cancelled = create(8, 'Cancelled', false, true) end - class Priority < RankedValue + class Value < RankedValue class << self def new(order) - super(order,Priorities) + super(order,Values) end end - Priorities << High = create(1, 'High') - Priorities << MedHigh = create(2, 'Med-High') - Priorities << Medium = create(3, 'Medium') - Priorities << MedLow = create(4, 'Med-Low') - Priorities << Low = create(5, 'Low') - Priorities << NA = create(6,'') + Values << High = create(1, 'High') + Values << MedHigh = create(2, 'Med-High') + Values << Medium = create(3, 'Medium') + Values << MedLow = create(4, 'Med-Low') + Values << Low = create(5, 'Low') + Values << NA = create(6,'') end class Risk < RankedValue @@ -167,15 +167,15 @@ # story = Story.new # story.return_ids_for_aggregations # story.status => 1 - # story.priority => 6 + # story.value => 6 # story.risk => 4 def return_ids_for_aggregations self.instance_eval <<-EOF def status read_attribute('status') end - def priority - read_attribute('priority') + def value + read_attribute('value') end def risk read_attribute('risk') @@ -193,10 +193,10 @@ end # The after_initialize callback is used to set the default values for #status, - # #priority and #risk. + # #value and #risk. def after_initialize self.status = Status::New unless self.status - self.priority = Priority::NA unless self.priority + self.value = Value::NA unless self.value self.risk = Risk::Normal unless self.risk end @@ -236,7 +236,7 @@ end def is_defined? - self.priority != Priority::NA and self.points? + self.value != Value::NA and self.points? end def before_save_reset_status Modified: trunk/app/views/stories/_story_form.rhtml =================================================================== --- trunk/app/views/stories/_story_form.rhtml 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/app/views/stories/_story_form.rhtml 2006-03-28 15:14:34 UTC (rev 222) @@ -18,8 +18,8 @@ 'name') %> - - <%= collection_select('story', 'priority', Story::Priorities, 'order', + + <%= collection_select('story', 'value', Story::Values, 'order', 'name') %> Modified: trunk/app/views/stories/_story_list_item.rhtml =================================================================== --- trunk/app/views/stories/_story_list_item.rhtml 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/app/views/stories/_story_list_item.rhtml 2006-03-28 15:14:34 UTC (rev 222) @@ -22,7 +22,7 @@ <% end %> <%= story.points -%> - <%= story.priority -%> + <%= story.value -%> <%= story.risk -%> <%= story.status -%> <% unless hide_cols.include?( :owner ) %> Modified: trunk/app/views/stories/_story_list_table.rhtml =================================================================== --- trunk/app/views/stories/_story_list_table.rhtml 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/app/views/stories/_story_list_table.rhtml 2006-03-28 15:14:34 UTC (rev 222) @@ -10,7 +10,7 @@ sort_header_params ) -%> <%= link_to_sort_by( 'Title', 'title', sort_header_params ) -%> <%= link_to_sort_by( 'Points', 'points', sort_header_params ) -%> - <%= link_to_sort_by( 'Priority', 'priority', sort_header_params ) -%> + <%= link_to_sort_by( 'Value', 'value', sort_header_params ) -%> <%= link_to_sort_by( 'Risk', 'risk', sort_header_params ) -%> <%= link_to_sort_by( 'Status', 'status', sort_header_params ) -%> <% unless hide_cols.include?( :owner ) %> Modified: trunk/app/views/stories/show.rhtml =================================================================== --- trunk/app/views/stories/show.rhtml 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/app/views/stories/show.rhtml 2006-03-28 15:14:34 UTC (rev 222) @@ -21,8 +21,8 @@ <% end %> - Priority: - <%= @story.priority.name %> + Value: + <%= @story.value.name %> Risk: Added: trunk/db/migrate/004_change_prioritycolumn_to_value_column_on_stories_table.rb =================================================================== --- trunk/db/migrate/004_change_prioritycolumn_to_value_column_on_stories_table.rb (rev 0) +++ trunk/db/migrate/004_change_prioritycolumn_to_value_column_on_stories_table.rb 2006-03-28 15:14:34 UTC (rev 222) @@ -0,0 +1,9 @@ +class ChangePrioritycolumnToValueColumnOnStoriesTable < ActiveRecord::Migration + def self.up + rename_column :stories, :priority, :value + end + + def self.down + rename_column :stories, :value, :priority + end +end Modified: trunk/test/fixtures/stories.yml =================================================================== --- trunk/test/fixtures/stories.yml 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/test/fixtures/stories.yml 2006-03-28 15:14:34 UTC (rev 222) @@ -8,7 +8,7 @@ title: 'First Story' status: 2 points: 3 - priority: 2 + value: 2 risk: 1 second: id: 2 @@ -19,7 +19,7 @@ title: 'Second Story' status: 6 points: 3 - priority: 2 + value: 2 risk: 1 third: id: 3 @@ -28,7 +28,7 @@ title: 'Third Story' status: 8 points: 2 - priority: 2 + value: 2 risk: 1 fourth: id: 4 @@ -39,7 +39,7 @@ title: 'Fourth Story' status: 2 points: 3 - priority: 1 + value: 1 risk: 1 fifth: id: 5 @@ -50,7 +50,7 @@ title: 'Fifth Story' status: 2 points: 3 - priority: 1 + value: 1 risk: 1 sixth: id: 6 @@ -59,5 +59,5 @@ title: 'Sixth Story' status: 2 points: 2 - priority: 1 + value: 1 risk: 1 \ No newline at end of file Modified: trunk/test/functional/dashboard_controller_test.rb =================================================================== --- trunk/test/functional/dashboard_controller_test.rb 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/test/functional/dashboard_controller_test.rb 2006-03-28 15:14:34 UTC (rev 222) @@ -102,7 +102,7 @@ 'budget' => 5) story = project1.stories.create('title' => 'Story Card', 'description' => 'test', 'points' => 1, - 'priority' => Story::Priority::High, 'risk' => Story::Risk::High) + 'value' => Story::Value::High, 'risk' => Story::Risk::High) iteration.stories << story story.owner = user story.save Modified: trunk/test/unit/story_test.rb =================================================================== --- trunk/test/unit/story_test.rb 2006-03-28 14:53:19 UTC (rev 221) +++ trunk/test/unit/story_test.rb 2006-03-28 15:14:34 UTC (rev 222) @@ -59,12 +59,12 @@ ) end - def test_priority_collection + def test_value_collection assert_equal( - [ Story::Priority::High, Story::Priority::MedHigh, - Story::Priority::Medium, Story::Priority::MedLow, Story::Priority::Low, - Story::Priority::NA ], - Story::Priorities + [ Story::Value::High, Story::Value::MedHigh, + Story::Value::Medium, Story::Value::MedLow, Story::Value::Low, + Story::Value::NA ], + Story::Values ) end @@ -80,9 +80,9 @@ assert_equal Story::Status::New, story.status end - def test_priority_default_na + def test_value_default_na story = Story.new - assert_equal Story::Priority::NA, story.priority + assert_equal Story::Value::NA, story.value end def test_risk_default_na @@ -94,7 +94,7 @@ story = Story.new story.return_ids_for_aggregations assert_equal Story::Status::New.order, story.status - assert_equal Story::Priority::NA.order, story.priority + assert_equal Story::Value::NA.order, story.value assert_equal Story::Risk::Normal.order, story.risk end @@ -121,7 +121,7 @@ story = @project_one.stories.create('title' => 'Test Story') assert_equal Story::Status::New, story.status story.risk = Story::Risk::Low - story.priority = Story::Priority::Low + story.value = Story::Value::Low story.points = 2 story.save assert_equal Story::Status::Defined, story.status @@ -156,7 +156,7 @@ story.iteration = @iteration_one assert !story.save assert story.errors.on(:iteration) - story.priority = Story::Priority::High + story.value = Story::Value::High story.risk = Story::Risk::High story.points = 1 story.description = 'description' @@ -173,7 +173,7 @@ def test_status_set_to_in_progress_when_taken_by_user_if_status_is_defined story = @project_one.stories.create('title' => 'Test Story') - story.priority = Story::Priority::Low + story.value = Story::Value::Low story.risk = Story::Risk::Low story.points = 1 story.description = 'description' @@ -216,23 +216,23 @@ end end -class StoryPriorityTest < Test::Unit::TestCase +class StoryValueTest < Test::Unit::TestCase def setup - @priorities = [] + @values = [] 5.times do |i| - @priorities << Story::Priority.new(i + 1) + @values << Story::Value.new(i + 1) end end def test_order - @priorities.each_with_index do |p,i| + @values.each_with_index do |p,i| i += 1 assert_equal i, p.order end end def test_name - @priorities.each_with_index do |p,i| + @values.each_with_index do |p,i| i += 1 case i when 1 @@ -253,22 +253,22 @@ end def test_to_s - @priorities.each do |p| + @values.each do |p| assert_equal p.name, p.to_s end end def test_invalid_order - assert_raise(Story::Priority::InvalidOrder) { Story::Priority.new(7) } + assert_raise(Story::Value::InvalidOrder) { Story::Value.new(7) } end def test_constants - assert_equal Story::Priority.new(1), Story::Priority::High - assert_equal Story::Priority.new(2), Story::Priority::MedHigh - assert_equal Story::Priority.new(3), Story::Priority::Medium - assert_equal Story::Priority.new(4), Story::Priority::MedLow - assert_equal Story::Priority.new(5), Story::Priority::Low - assert_equal Story::Priority.new(6), Story::Priority::NA + assert_equal Story::Value.new(1), Story::Value::High + assert_equal Story::Value.new(2), Story::Value::MedHigh + assert_equal Story::Value.new(3), Story::Value::Medium + assert_equal Story::Value.new(4), Story::Value::MedLow + assert_equal Story::Value.new(5), Story::Value::Low + assert_equal Story::Value.new(6), Story::Value::NA end end From svn at explainpmt.com Tue Mar 28 18:58:42 2006 From: svn at explainpmt.com (svn at explainpmt.com) Date: Tue, 28 Mar 2006 23:58:42 +0000 (GMT) Subject: [eXPlainPMT Developers] [SVN commit] [223] Update to Rails 1.1 Message-ID: <20060328235842.B58AEC97CE@thurlow.textdrive.com> Revision: 223 Author: jwilger Date: 2006-03-28 23:58:41 +0000 (Tue, 28 Mar 2006) Log Message: ----------- Update to Rails 1.1 Modified Paths: -------------- trunk/public/javascripts/controls.js trunk/public/javascripts/dragdrop.js trunk/public/javascripts/effects.js trunk/public/javascripts/prototype.js Added Paths: ----------- trunk/public/javascripts/application.js Added: trunk/public/javascripts/application.js =================================================================== --- trunk/public/javascripts/application.js (rev 0) +++ trunk/public/javascripts/application.js 2006-03-28 23:58:41 UTC (rev 223) @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults Modified: trunk/public/javascripts/controls.js =================================================================== --- trunk/public/javascripts/controls.js 2006-03-28 15:14:34 UTC (rev 222) +++ trunk/public/javascripts/controls.js 2006-03-28 23:58:41 UTC (rev 223) @@ -141,8 +141,8 @@ return; } else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) - return; + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; @@ -152,6 +152,12 @@ setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + onHover: function(event) { var element = Event.findElement(event, 'LI'); if(this.index != element.autocompleteIndex) @@ -221,8 +227,13 @@ this.options.updateElement(selectedElement); return; } - - var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + var lastTokenPos = this.findLastToken(); if (lastTokenPos != -1) { var newValue = this.element.value.substr(0, lastTokenPos + 1); @@ -305,7 +316,7 @@ Ajax.Autocompleter = Class.create(); Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { initialize: function(element, update, url, options) { - this.baseInitialize(element, update, options); + this.baseInitialize(element, update, options); this.options.asynchronous = true; this.options.onComplete = this.onComplete.bind(this); this.options.defaultParams = this.options.parameters || null; @@ -448,7 +459,9 @@ this.element = $(element); this.options = Object.extend({ + okButton: true, okText: "ok", + cancelLink: true, cancelText: "cancel", savingText: "Saving...", clickToEditText: "Click to edit", @@ -470,8 +483,10 @@ formClassName: 'inplaceeditor-form', highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, highlightendcolor: "#FFFFFF", - externalControl: null, - ajaxOptions: {} + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false }, options || {}); if(!this.options.formId && this.element.id) { @@ -536,16 +551,22 @@ this.form.appendChild(br); } - okButton = document.createElement("input"); - okButton.type = "submit"; - okButton.value = this.options.okText; - this.form.appendChild(okButton); + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } - cancelLink = document.createElement("a"); - cancelLink.href = "#"; - cancelLink.appendChild(document.createTextNode(this.options.cancelText)); - cancelLink.onclick = this.onclickCancel.bind(this); - this.form.appendChild(cancelLink); + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } }, hasHTMLLineBreaks: function(string) { if (!this.options.handleLineBreaks) return false; @@ -561,24 +582,34 @@ } else { text = this.getText(); } + + var obj = this; if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { this.options.textarea = false; var textField = document.createElement("input"); + textField.obj = this; textField.type = "text"; textField.name = "value"; textField.value = text; textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; var size = this.options.size || this.options.cols || 0; if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); this.editField = textField; } else { this.options.textarea = true; var textArea = document.createElement("textarea"); + textArea.obj = this; textArea.name = "value"; textArea.value = this.convertHTMLLineBreaks(text); textArea.rows = this.options.rows; textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); this.editField = textArea; } @@ -629,19 +660,26 @@ // to be displayed indefinitely this.onLoading(); - new Ajax.Updater( - { - success: this.element, - // don't update on failure (this could be an option) - failure: null - }, - this.url, - Object.extend({ - parameters: this.options.callback(form, value), - onComplete: this.onComplete.bind(this), - onFailure: this.onFailure.bind(this) - }, this.options.ajaxOptions) - ); + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } // stop the event to avoid a page refresh in Safari if (arguments.length > 1) { Event.stop(arguments[0]); @@ -723,6 +761,33 @@ } }; +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + // Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields @@ -747,4 +812,4 @@ this.timer = null; this.callback(this.element, $F(this.element)); } -}; \ No newline at end of file +}; Modified: trunk/public/javascripts/dragdrop.js =================================================================== --- trunk/public/javascripts/dragdrop.js 2006-03-28 15:14:34 UTC (rev 222) +++ trunk/public/javascripts/dragdrop.js 2006-03-28 23:58:41 UTC (rev 223) @@ -128,7 +128,7 @@ this.activeDraggable = draggable; }, - deactivate: function(draggbale) { + deactivate: function() { this.activeDraggable = null; }, @@ -146,6 +146,7 @@ if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); + this.activeDraggable = null; }, keyPress: function(event) { @@ -191,22 +192,28 @@ }, reverteffect: function(element, top_offset, left_offset) { var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; - element._revert = new Effect.MoveBy(element, -top_offset, -left_offset, {duration:dur}); + element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur}); }, endeffect: function(element) { new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0}); }, zindex: 1000, revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] } }, arguments[1] || {}); this.element = $(element); if(options.handle && (typeof options.handle == 'string')) - this.handle = Element.childrenWithClassName(this.element, options.handle)[0]; + this.handle = Element.childrenWithClassName(this.element, options.handle, true)[0]; if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) + options.scroll = $(options.scroll); Element.makePositioned(this.element); // fix IE @@ -227,8 +234,8 @@ currentDelta: function() { return([ - parseInt(this.element.style.left || '0'), - parseInt(this.element.style.top || '0')]); + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); }, initDrag: function(event) { @@ -238,6 +245,7 @@ if(src.tagName && ( src.tagName=='INPUT' || src.tagName=='SELECT' || + src.tagName=='OPTION' || src.tagName=='BUTTON' || src.tagName=='TEXTAREA')) return; @@ -269,6 +277,17 @@ this.element.parentNode.insertBefore(this._clone, this.element); } + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + Draggables.notify('onStart', this, event); if(this.options.starteffect) this.options.starteffect(this.element); }, @@ -281,8 +300,30 @@ this.draw(pointer); if(this.options.change) this.options.change(this); + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft; + p[1] += this.options.scroll.scrollTop; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + // fix AppleWebKit rendering if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + Event.stop(event); }, @@ -320,13 +361,14 @@ }, keyPress: function(event) { - if(!event.keyCode==Event.KEY_ESC) return; + if(event.keyCode!=Event.KEY_ESC) return; this.finishDrag(event, false); Event.stop(event); }, endDrag: function(event) { if(!this.dragging) return; + this.stopScrolling(); this.finishDrag(event, true); Event.stop(event); }, @@ -336,8 +378,15 @@ var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; - var p = [0,1].map(function(i){ return (point[i]-pos[i]-this.offset[i]) }.bind(this)); + if(this.options.scroll && (this.options.scroll != window)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + if(this.options.snap) { if(typeof this.options.snap == 'function') { p = this.options.snap(p[0],p[1]); @@ -357,6 +406,67 @@ if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + } + }, + + startScrolling: function(speed) { + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + this.draw(Draggables._lastPointer); + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; } } @@ -413,7 +523,10 @@ only: false, hoverclass: null, ghosting: false, - format: null, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: /^[^_]*_(.*)$/, onChange: Prototype.emptyFunction, onUpdate: Prototype.emptyFunction }, arguments[1] || {}); @@ -424,6 +537,9 @@ // build options for the draggables var options_for_draggable = { revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, ghosting: options.ghosting, constraint: options.constraint, handle: options.handle }; @@ -491,9 +607,10 @@ findElements: function(element, options) { if(!element.hasChildNodes()) return null; var elements = []; + var only = options.only ? [options.only].flatten() : null; $A(element.childNodes).each( function(e) { if(e.tagName && e.tagName.toUpperCase()==options.tag.toUpperCase() && - (!options.only || (Element.hasClassName(e, options.only)))) + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) elements.push(e); if(options.tree) { var grandchildren = this.findElements(e, options); @@ -567,18 +684,41 @@ Element.show(Sortable._marker); }, + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + serialize: function(element) { element = $(element); - var sortableOptions = this.options(element); - var options = Object.extend({ - tag: sortableOptions.tag, - only: sortableOptions.only, - name: element.id, - format: sortableOptions.format || /^[^_]*_(.*)$/ - }, arguments[1] || {}); - return $(this.findElements(element, options) || []).map( function(item) { - return (encodeURIComponent(options.name) + "[]=" + - encodeURIComponent(item.id.match(options.format) ? item.id.match(options.format)[1] : '')); - }).join("&"); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); } -} \ No newline at end of file +} Modified: trunk/public/javascripts/effects.js =================================================================== --- trunk/public/javascripts/effects.js 2006-03-28 15:14:34 UTC (rev 222) +++ trunk/public/javascripts/effects.js 2006-03-28 23:58:41 UTC (rev 223) @@ -6,8 +6,6 @@ // // See scriptaculous.js for full license. -/* ------------- element ext -------------- */ - // converts rgb() and #xxx to #xxxxxx format, // returns self (or first argument) if not convertable String.prototype.parseColor = function() { @@ -22,33 +20,29 @@ } } return(color.length==7 ? color : (arguments[0] || this)); -} +} -Element.collectTextNodesIgnoreClass = function(element, ignoreclass) { - var children = $(element).childNodes; - var text = ''; - var classtest = new RegExp('^([^ ]+ )*' + ignoreclass+ '( [^ ]+)*$','i'); - - for (var i = 0; i < children.length; i++) { - if(children[i].nodeType==3) { - text+=children[i].nodeValue; - } else { - if((!children[i].className.match(classtest)) && children[i].hasChildNodes()) - text += Element.collectTextNodesIgnoreClass(children[i], ignoreclass); - } - } - - return text; +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); } -Element.setStyle = function(element, style) { - element = $(element); - for(k in style) element.style[k.camelize()] = style[k]; +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); } -Element.setContentZoom = function(element, percent) { +Element.setContentZoom = function(element, percent) { + element = $(element); Element.setStyle(element, {fontSize: (percent/100) + 'em'}); - if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); } Element.getOpacity = function(element){ @@ -75,18 +69,35 @@ Element.setStyle(element, { filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') + 'alpha(opacity='+value*100+')' }); - } + } } Element.getInlineOpacity = function(element){ return $(element).style.opacity || ''; } -Element.childrenWithClassName = function(element, className) { - return $A($(element).getElementsByTagName('*')).select( - function(c) { return Element.hasClassName(c, className) }); +Element.childrenWithClassName = function(element, className, findFirst) { + return [$A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) { + return c.className ? Element.hasClassName(c, className) : false; + })].flatten(); } +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +/*--------------------------------------------------------------------------*/ + Array.prototype.call = function() { var args = arguments; this.each(function(f){ f.apply(this, args) }); @@ -129,6 +140,20 @@ $A(elements).each( function(element, index) { new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); } }; @@ -166,16 +191,22 @@ /* ------------- core effects ------------- */ -Effect.Queue = { - effects: [], +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, _each: function(iterator) { this.effects._each(iterator); }, - interval: null, add: function(effect) { var timestamp = new Date().getTime(); - switch(effect.options.queue) { + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { case 'front': // move unstarted effects after this effect this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { @@ -191,7 +222,10 @@ effect.startOn += timestamp; effect.finishOn += timestamp; - this.effects.push(effect); + + if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + if(!this.interval) this.interval = setInterval(this.loop.bind(this), 40); }, @@ -206,32 +240,45 @@ var timePos = new Date().getTime(); this.effects.invoke('loop', timePos); } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } } -Object.extend(Effect.Queue, Enumerable); +Effect.Queue = Effect.Queues.get('global'); +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + Effect.Base = function() {}; Effect.Base.prototype = { position: null, - setOptions: function(options) { - this.options = Object.extend({ - transition: Effect.Transitions.sinoidal, - duration: 1.0, // seconds - fps: 25.0, // max. 25fps due to Effect.Queue implementation - sync: false, // true for combining - from: 0.0, - to: 1.0, - delay: 0.0, - queue: 'parallel' - }, options || {}); - }, start: function(options) { - this.setOptions(options || {}); + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); this.currentFrame = 0; this.state = 'idle'; this.startOn = this.options.delay*1000; this.finishOn = this.startOn + (this.options.duration*1000); this.event('beforeStart'); - if(!this.options.sync) Effect.Queue.add(this); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); }, loop: function(timePos) { if(timePos >= this.startOn) { @@ -269,7 +316,9 @@ } }, cancel: function() { - if(!this.options.sync) Effect.Queue.remove(this); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); this.state = 'finished'; }, event: function(eventName) { @@ -307,43 +356,57 @@ this.element = $(element); // make this work on IE on elements without 'layout' if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) - Element.setStyle(this.element, {zoom: 1}); + this.element.setStyle({zoom: 1}); var options = Object.extend({ - from: Element.getOpacity(this.element) || 0.0, + from: this.element.getOpacity() || 0.0, to: 1.0 }, arguments[1] || {}); this.start(options); }, update: function(position) { - Element.setOpacity(this.element, position); + this.element.setOpacity(position); } }); -Effect.MoveBy = Class.create(); -Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { - initialize: function(element, toTop, toLeft) { - this.element = $(element); - this.toTop = toTop; - this.toLeft = toLeft; - this.start(arguments[3]); +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); }, setup: function() { // Bug in Opera: Opera returns the "real" position of a static element or // relative element that does not have top/left explicitly set. // ==> Always set top and left for position relative elements in your stylesheets // (to 0 if you do not need them) - Element.makePositioned(this.element); - this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); - this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } }, update: function(position) { - Element.setStyle(this.element, { - top: this.toTop * position + this.originalTop + 'px', - left: this.toLeft * position + this.originalLeft + 'px' + this.element.setStyle({ + left: this.options.x * position + this.originalLeft + 'px', + top: this.options.y * position + this.originalTop + 'px' }); } }); +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + Effect.Scale = Class.create(); Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { initialize: function(element, percent) { @@ -361,7 +424,7 @@ }, setup: function() { this.restoreAfterFinish = this.options.restoreAfterFinish || false; - this.elementPositioning = Element.getStyle(this.element,'position'); + this.elementPositioning = this.element.getStyle('position'); this.originalStyle = {}; ['top','left','width','height','fontSize'].each( function(k) { @@ -371,7 +434,7 @@ this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; - var fontSize = Element.getStyle(this.element,'font-size') || '100%'; + var fontSize = this.element.getStyle('font-size') || '100%'; ['em','px','%'].each( function(fontSizeType) { if(fontSize.indexOf(fontSizeType)>0) { this.fontSize = parseFloat(fontSize); @@ -393,11 +456,11 @@ update: function(position) { var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); if(this.options.scaleContent && this.fontSize) - Element.setStyle(this.element, {fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); }, finish: function(position) { - if (this.restoreAfterFinish) Element.setStyle(this.element, this.originalStyle); + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); }, setDimensions: function(height, width) { var d = {}; @@ -414,7 +477,7 @@ if(this.options.scaleX) d.left = -leftd + 'px'; } } - Element.setStyle(this.element, d); + this.element.setStyle(d); } }); @@ -427,25 +490,25 @@ }, setup: function() { // Prevent executing on elements not in the layout flow - if(Element.getStyle(this.element, 'display')=='none') { this.cancel(); return; } + if(this.element.getStyle('display')=='none') { this.cancel(); return; } // Disable background image during the effect this.oldStyle = { - backgroundImage: Element.getStyle(this.element, 'background-image') }; - Element.setStyle(this.element, {backgroundImage: 'none'}); + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); if(!this.options.endcolor) - this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); if(!this.options.restorecolor) - this.options.restorecolor = Element.getStyle(this.element, 'background-color'); + this.options.restorecolor = this.element.getStyle('background-color'); // init color calculations this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); }, update: function(position) { - Element.setStyle(this.element,{backgroundColor: $R(0,2).inject('#',function(m,v,i){ + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); }, finish: function() { - Element.setStyle(this.element, Object.extend(this.oldStyle, { + this.element.setStyle(Object.extend(this.oldStyle, { backgroundColor: this.options.restorecolor })); } @@ -479,85 +542,91 @@ /* ------------- combination effects ------------- */ Effect.Fade = function(element) { - var oldOpacity = Element.getInlineOpacity(element); + element = $(element); + var oldOpacity = element.getInlineOpacity(); var options = Object.extend({ - from: Element.getOpacity(element) || 1.0, + from: element.getOpacity() || 1.0, to: 0.0, - afterFinishInternal: function(effect) { with(Element) { + afterFinishInternal: function(effect) { if(effect.options.to!=0) return; - hide(effect.element); - setStyle(effect.element, {opacity: oldOpacity}); }} - }, arguments[1] || {}); + effect.element.hide(); + effect.element.setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); return new Effect.Opacity(element,options); } Effect.Appear = function(element) { + element = $(element); var options = Object.extend({ - from: (Element.getStyle(element, 'display') == 'none' ? 0.0 : Element.getOpacity(element) || 0.0), + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), to: 1.0, - beforeSetup: function(effect) { with(Element) { - setOpacity(effect.element, effect.options.from); - show(effect.element); }} - }, arguments[1] || {}); + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from); + effect.element.show(); + }}, arguments[1] || {}); return new Effect.Opacity(element,options); } Effect.Puff = function(element) { element = $(element); - var oldStyle = { opacity: Element.getInlineOpacity(element), position: Element.getStyle(element, 'position') }; + var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') }; return new Effect.Parallel( [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], Object.extend({ duration: 1.0, - beforeSetupInternal: function(effect) { with(Element) { - setStyle(effect.effects[0].element, {position: 'absolute'}); }}, - afterFinishInternal: function(effect) { with(Element) { - hide(effect.effects[0].element); - setStyle(effect.effects[0].element, oldStyle); }} + beforeSetupInternal: function(effect) { + effect.effects[0].element.setStyle({position: 'absolute'}); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.setStyle(oldStyle); } }, arguments[1] || {}) ); } Effect.BlindUp = function(element) { element = $(element); - Element.makeClipping(element); + element.makeClipping(); return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, restoreAfterFinish: true, - afterFinishInternal: function(effect) { with(Element) { - [hide, undoClipping].call(effect.element); }} + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + } }, arguments[1] || {}) ); } Effect.BlindDown = function(element) { element = $(element); - var oldHeight = Element.getStyle(element, 'height'); - var elementDimensions = Element.getDimensions(element); + var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, - afterSetup: function(effect) { with(Element) { - makeClipping(effect.element); - setStyle(effect.element, {height: '0px'}); - show(effect.element); - }}, - afterFinishInternal: function(effect) { with(Element) { - undoClipping(effect.element); - setStyle(effect.element, {height: oldHeight}); - }} + afterSetup: function(effect) { + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } }, arguments[1] || {}) ); } Effect.SwitchOff = function(element) { element = $(element); - var oldOpacity = Element.getInlineOpacity(element); + var oldOpacity = element.getInlineOpacity(); return new Effect.Appear(element, { duration: 0.4, from: 0, @@ -566,13 +635,16 @@ new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, - beforeSetup: function(effect) { with(Element) { - [makePositioned,makeClipping].call(effect.element); - }}, - afterFinishInternal: function(effect) { with(Element) { - [hide,undoClipping,undoPositioned].call(effect.element); - setStyle(effect.element, {opacity: oldOpacity}); - }} + beforeSetup: function(effect) { + effect.element.makePositioned(); + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.undoPositioned(); + effect.element.setStyle({opacity: oldOpacity}); + } }) } }); @@ -581,99 +653,110 @@ Effect.DropOut = function(element) { element = $(element); var oldStyle = { - top: Element.getStyle(element, 'top'), - left: Element.getStyle(element, 'left'), - opacity: Element.getInlineOpacity(element) }; + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; return new Effect.Parallel( - [ new Effect.MoveBy(element, 100, 0, { sync: true }), + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), new Effect.Opacity(element, { sync: true, to: 0.0 }) ], Object.extend( { duration: 0.5, - beforeSetup: function(effect) { with(Element) { - makePositioned(effect.effects[0].element); }}, - afterFinishInternal: function(effect) { with(Element) { - [hide, undoPositioned].call(effect.effects[0].element); - setStyle(effect.effects[0].element, oldStyle); }} + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } }, arguments[1] || {})); } Effect.Shake = function(element) { element = $(element); var oldStyle = { - top: Element.getStyle(element, 'top'), - left: Element.getStyle(element, 'left') }; - return new Effect.MoveBy(element, 0, 20, - { duration: 0.05, afterFinishInternal: function(effect) { - new Effect.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinishInternal: function(effect) { - new Effect.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinishInternal: function(effect) { - new Effect.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinishInternal: function(effect) { - new Effect.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinishInternal: function(effect) { - new Effect.MoveBy(effect.element, 0, -20, - { duration: 0.05, afterFinishInternal: function(effect) { with(Element) { - undoPositioned(effect.element); - setStyle(effect.element, oldStyle); - }}}) }}) }}) }}) }}) }}); + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned(); + effect.element.setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); } Effect.SlideDown = function(element) { element = $(element); - Element.cleanWhitespace(element); + element.cleanWhitespace(); // SlideDown need to have the content of the element wrapped in a container element with fixed height! - var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); - var elementDimensions = Element.getDimensions(element); + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); + var elementDimensions = element.getDimensions(); return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, - afterSetup: function(effect) { with(Element) { - makePositioned(effect.element); - makePositioned(effect.element.firstChild); - if(window.opera) setStyle(effect.element, {top: ''}); - makeClipping(effect.element); - setStyle(effect.element, {height: '0px'}); - show(element); }}, - afterUpdateInternal: function(effect) { with(Element) { - setStyle(effect.element.firstChild, {bottom: - (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, - afterFinishInternal: function(effect) { with(Element) { - undoClipping(effect.element); - undoPositioned(effect.element.firstChild); - undoPositioned(effect.element); - setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.setStyle({height: '0px'}); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + // IE will crash if child is undoPositioned first + if(/MSIE/.test(navigator.userAgent)){ + effect.element.undoPositioned(); + effect.element.firstChild.undoPositioned(); + }else{ + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + } + effect.element.firstChild.setStyle({bottom: oldInnerBottom}); } }, arguments[1] || {}) ); } Effect.SlideUp = function(element) { element = $(element); - Element.cleanWhitespace(element); - var oldInnerBottom = Element.getStyle(element.firstChild, 'bottom'); + element.cleanWhitespace(); + var oldInnerBottom = $(element.firstChild).getStyle('bottom'); return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, scaleMode: 'box', scaleFrom: 100, restoreAfterFinish: true, - beforeStartInternal: function(effect) { with(Element) { - makePositioned(effect.element); - makePositioned(effect.element.firstChild); - if(window.opera) setStyle(effect.element, {top: ''}); - makeClipping(effect.element); - show(element); }}, - afterUpdateInternal: function(effect) { with(Element) { - setStyle(effect.element.firstChild, {bottom: - (effect.dims[0] - effect.element.clientHeight) + 'px' }); }}, - afterFinishInternal: function(effect) { with(Element) { - [hide, undoClipping].call(effect.element); - undoPositioned(effect.element.firstChild); - undoPositioned(effect.element); - setStyle(effect.element.firstChild, {bottom: oldInnerBottom}); }} + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + effect.element.firstChild.makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping(); + effect.element.show(); }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.firstChild.undoPositioned(); + effect.element.undoPositioned(); + effect.element.setStyle({bottom: oldInnerBottom}); } }, arguments[1] || {}) ); } @@ -682,11 +765,11 @@ Effect.Squish = function(element) { return new Effect.Scale(element, window.opera ? 1 : 0, { restoreAfterFinish: true, - beforeSetup: function(effect) { with(Element) { - makeClipping(effect.element); }}, - afterFinishInternal: function(effect) { with(Element) { - hide(effect.element); - undoClipping(effect.element); }} + beforeSetup: function(effect) { + effect.element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + effect.element.hide(effect.element); + effect.element.undoClipping(effect.element); } }); } @@ -694,7 +777,7 @@ element = $(element); var options = Object.extend({ direction: 'center', - moveTransistion: Effect.Transitions.sinoidal, + moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.full }, arguments[1] || {}); @@ -703,9 +786,9 @@ left: element.style.left, height: element.style.height, width: element.style.width, - opacity: Element.getInlineOpacity(element) }; + opacity: element.getInlineOpacity() }; - var dims = Element.getDimensions(element); + var dims = element.getDimensions(); var initialMoveX, initialMoveY; var moveX, moveY; @@ -737,27 +820,32 @@ break; } - return new Effect.MoveBy(element, initialMoveY, initialMoveX, { + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, duration: 0.01, - beforeSetup: function(effect) { with(Element) { - hide(effect.element); - makeClipping(effect.element); - makePositioned(effect.element); - }}, + beforeSetup: function(effect) { + effect.element.hide(); + effect.element.makeClipping(); + effect.element.makePositioned(); + }, afterFinishInternal: function(effect) { new Effect.Parallel( [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), - new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: options.moveTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), new Effect.Scale(effect.element, 100, { scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) ], Object.extend({ - beforeSetup: function(effect) { with(Element) { - setStyle(effect.effects[0].element, {height: '0px'}); - show(effect.effects[0].element); }}, - afterFinishInternal: function(effect) { with(Element) { - [undoClipping, undoPositioned].call(effect.effects[0].element); - setStyle(effect.effects[0].element, oldStyle); }} + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}); + effect.effects[0].element.show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); + } }, options) ) } @@ -768,7 +856,7 @@ element = $(element); var options = Object.extend({ direction: 'center', - moveTransistion: Effect.Transitions.sinoidal, + moveTransition: Effect.Transitions.sinoidal, scaleTransition: Effect.Transitions.sinoidal, opacityTransition: Effect.Transitions.none }, arguments[1] || {}); @@ -777,9 +865,9 @@ left: element.style.left, height: element.style.height, width: element.style.width, - opacity: Element.getInlineOpacity(element) }; + opacity: element.getInlineOpacity() }; - var dims = Element.getDimensions(element); + var dims = element.getDimensions(); var moveX, moveY; switch (options.direction) { @@ -807,13 +895,16 @@ return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), - new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: options.moveTransition }) + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) ], Object.extend({ - beforeStartInternal: function(effect) { with(Element) { - [makePositioned, makeClipping].call(effect.effects[0].element) }}, - afterFinishInternal: function(effect) { with(Element) { - [hide, undoClipping, undoPositioned].call(effect.effects[0].element); - setStyle(effect.effects[0].element, oldStyle); }} + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned(); + effect.effects[0].element.makeClipping(); }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide(); + effect.effects[0].element.undoClipping(); + effect.effects[0].element.undoPositioned(); + effect.effects[0].element.setStyle(oldStyle); } }, options) ); } @@ -821,13 +912,13 @@ Effect.Pulsate = function(element) { element = $(element); var options = arguments[1] || {}; - var oldOpacity = Element.getInlineOpacity(element); + var oldOpacity = element.getInlineOpacity(); var transition = options.transition || Effect.Transitions.sinoidal; var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; reverser.bind(transition); return new Effect.Opacity(element, Object.extend(Object.extend({ duration: 3.0, from: 0, - afterFinishInternal: function(effect) { Element.setStyle(effect.element, {opacity: oldOpacity}); } + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } }, options), {transition: reverser})); } @@ -846,9 +937,17 @@ new Effect.Scale(element, 1, { scaleContent: false, scaleY: false, - afterFinishInternal: function(effect) { with(Element) { - [hide, undoClipping].call(effect.element); - setStyle(effect.element, oldStyle); - }} }); + afterFinishInternal: function(effect) { + effect.element.hide(); + effect.element.undoClipping(); + effect.element.setStyle(oldStyle); + } }); }}, arguments[1] || {})); } + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; \ No newline at end of file Modified: trunk/public/javascripts/prototype.js =================================================================== --- trunk/public/javascripts/prototype.js 2006-03-28 15:14:34 UTC (rev 222) +++ trunk/public/javascripts/prototype.js 2006-03-28 23:58:41 UTC (rev 223) @@ -1,17 +1,13 @@ -/* Prototype JavaScript framework, version 1.4.0 +/* Prototype JavaScript framework, version 1.5.0_pre1 * (c) 2005 Sam Stephenson * - * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff - * against the source tree, available from the Prototype darcs repository. - * * Prototype is freely distributable under the terms of an MIT-style license. - * * For details, see the Prototype web site: http://prototype.conio.net/ * /*--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.4.0', + Version: '1.5.0_pre1', ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', emptyFunction: function() {}, @@ -120,26 +116,49 @@ } } } +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); -/*--------------------------------------------------------------------------*/ + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += (replacement(match) || '').toString(); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, -function $() { - var elements = new Array(); + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; - for (var i = 0; i < arguments.length; i++) { - var element = arguments[i]; - if (typeof element == 'string') - element = document.getElementById(element); + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, - if (arguments.length == 1) - return element; + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, - elements.push(element); - } + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, - return elements; -} -Object.extend(String.prototype, { + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + stripTags: function() { return this.replace(/<\/?[^>]+>/gi, ''); }, @@ -203,12 +222,35 @@ }, inspect: function() { - return "'" + this.replace('\\', '\\\\').replace("'", '\\\'') + "'"; + return "'" + this.replace(/\\/g, '\\\\').replace(/'/g, '\\\'') + "'"; } }); +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + String.prototype.parseQuery = String.prototype.toQueryParams; +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + (object[match[3]] || '').toString(); + }); + } +} + var $break = new Object(); var $continue = new Object(); @@ -375,8 +417,7 @@ var collections = [this].concat(args).map($A); return this.map(function(value, index) { - iterator(value = collections.pluck(index)); - return value; + return iterator(collections.pluck(index)); }); }, @@ -662,7 +703,8 @@ setRequestHeaders: function() { var requestHeaders = ['X-Requested-With', 'XMLHttpRequest', - 'X-Prototype-Version', Prototype.Version]; + 'X-Prototype-Version', Prototype.Version, + 'Accept', 'text/javascript, text/html, application/xml, text/xml, */*']; if (this.options.method == 'post') { requestHeaders.push('Content-type', @@ -831,22 +873,48 @@ this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); +function $() { + var results = [], element; + for (var i = 0; i < arguments.length; i++) { + element = arguments[i]; + if (typeof element == 'string') + element = document.getElementById(element); + results.push(Element.extend(element)); + } + return results.length < 2 ? results[0] : results; +} + document.getElementsByClassName = function(className, parentElement) { var children = ($(parentElement) || document.body).getElementsByTagName('*'); return $A(children).inject([], function(elements, child) { if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) - elements.push(child); + elements.push(Element.extend(child)); return elements; }); } /*--------------------------------------------------------------------------*/ -if (!window.Element) { +if (!window.Element) var Element = new Object(); + +Element.extend = function(element) { + if (!element) return; + + if (!element._extended && element.tagName && element != window) { + var methods = Element.Methods; + for (property in methods) { + var value = methods[property]; + if (typeof value == 'function') + element[property] = value.bind(null, element); + } + } + + element._extended = true; + return element; } -Object.extend(Element, { +Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; }, @@ -882,6 +950,19 @@ setTimeout(function() {html.evalScripts()}, 10); }, + replace: function(element, html) { + element = $(element); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + }, + getHeight: function(element) { element = $(element); return element.offsetHeight; @@ -920,6 +1001,13 @@ return $(element).innerHTML.match(/^\s*$/); }, + childOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + scrollTo: function(element) { element = $(element); var x = element.x ? element.x : element.offsetLeft, @@ -1013,8 +1101,10 @@ element.style.overflow = element._overflow; element._overflow = undefined; } -}); +} +Object.extend(Element, Element.Methods); + var Toggle = new Object(); Toggle.display = Element.toggle; @@ -1148,6 +1238,116 @@ } Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.id == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0; i < clause.length; i++) + conditions.push('Element.hasClassName(element, ' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.getAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push(value + ' != null'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0; i < scope.length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +function $$() { + return $A(arguments).map(function(expression) { + return expression.strip().split(/\s+/).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.map(selector.findElements.bind(selector)).flatten(); + }); + }).flatten(); +} var Field = { clear: function() { for (var i = 0; i < arguments.length; i++) From johnwilger at gmail.com Wed Mar 29 19:40:02 2006 From: johnwilger at gmail.com (John Wilger) Date: Wed, 29 Mar 2006 16:40:02 -0800 Subject: [eXPlainPMT Developers] Subversion repository is down Message-ID: Just a quick note to let you all know that, yes, the Subversion repository is down at the moment. I've set up a dedicated server again, and I'm moving everything over from TextDrive. It should all be back up and running again very soon -- I'll keep you posted. -- Regards, John Wilger http://johnwilger.com ----------- Alice came to a fork in the road. "Which road do I take?" she asked. "Where do you want to go?" responded the Cheshire cat. "I don't know," Alice answered. "Then," said the cat, "it doesn't matter." - Lewis Carrol, Alice in Wonderland From edavis10 at gmail.com Wed Mar 29 20:27:37 2006 From: edavis10 at gmail.com (Eric Davis) Date: Wed, 29 Mar 2006 17:27:37 -0800 Subject: [eXPlainPMT Developers] Subversion repository is down In-Reply-To: References: Message-ID: <442B3409.3060702@gmail.com> John Wilger wrote: > Just a quick note to let you all know that, yes, the Subversion > repository is down at the moment. I've set up a dedicated server > again, and I'm moving everything over from TextDrive. It should all be > back up and running again very soon -- I'll keep you posted. Ok, if you need any help with just ask (and not just eXPlainPMT stuff, I am always willing to lend a hand). Eric Davis edavis10 at gmail.com