From johnwilger at gmail.com Thu Oct 6 14:53:57 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Thu, 06 Oct 2005 18:53:57 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r13 - in /trunk: ./ config/ config/environments/ lib/tasks/ public/ public/javascripts/ script/ vendor/ vendor/plugins/ Message-ID: <200510061854.j96Is0rX008338@porkchop.wilger.local> Author: jwilger Date: Thu Oct 6 14:53:39 2005 New Revision: 13 Log: Switched to using the trunk line of the Rails libs in vendor/rails. Also, installed edge Rails gems (build 2471) and updated the porject files by running rails on the project directory. Added: trunk/config/boot.rb trunk/lib/tasks/ trunk/public/robots.txt trunk/script/perform (with props) trunk/script/process (with props) trunk/vendor/plugins/ Modified: trunk/Rakefile trunk/config/environment.rb trunk/config/environments/development.rb trunk/config/environments/production.rb trunk/config/environments/test.rb trunk/public/.htaccess trunk/public/dispatch.cgi trunk/public/dispatch.fcgi trunk/public/dispatch.rb trunk/public/javascripts/controls.js trunk/public/javascripts/dragdrop.js trunk/public/javascripts/effects.js trunk/public/javascripts/prototype.js trunk/script/breakpointer trunk/script/console trunk/script/destroy trunk/script/generate trunk/script/runner trunk/script/server trunk/vendor/ (props changed) Modified: trunk/Rakefile ============================================================================== --- trunk/Rakefile (original) +++ trunk/Rakefile Thu Oct 6 14:53:39 2005 @@ -1,202 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/switchtower.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + require 'rake' require 'rake/testtask' require 'rake/rdoctask' -$VERBOSE = nil -TEST_CHANGES_SINCE = Time.now - 600 - -desc "Run all the tests on a fresh test database" -task :default => [ :test_units, :test_functional ] - - -desc 'Require application environment.' -task :environment do - unless defined? RAILS_ROOT - require File.dirname(__FILE__) + '/config/environment' - end -end - -desc "Generate API documentation, show coding stats" -task :doc => [ :appdoc, :stats ] - - -# Look up tests for recently modified sources. -def recent_tests(source_pattern, test_path, touched_since = 10.minutes.ago) - FileList[source_pattern].map do |path| - if File.mtime(path) > touched_since - test = "#{test_path}/#{File.basename(path, '.rb')}_test.rb" - test if File.exists?(test) - end - end.compact -end - -desc 'Test recent changes.' -Rake::TestTask.new(:recent => [ :clone_structure_to_test ]) do |t| - since = TEST_CHANGES_SINCE - touched = FileList['test/**/*_test.rb'].select { |path| File.mtime(path) > since } + - recent_tests('app/models/*.rb', 'test/unit', since) + - recent_tests('app/controllers/*.rb', 'test/functional', since) - - t.libs << 'test' - t.verbose = true - t.test_files = touched.uniq -end -task :test_recent => [ :clone_structure_to_test ] - -desc "Run the unit tests in test/unit" -Rake::TestTask.new("test_units") { |t| - t.libs << "test" - t.pattern = 'test/unit/**/*_test.rb' - t.verbose = true -} -task :test_units => [ :clone_structure_to_test ] - -desc "Run the functional tests in test/functional" -Rake::TestTask.new("test_functional") { |t| - t.libs << "test" - t.pattern = 'test/functional/**/*_test.rb' - t.verbose = true -} -task :test_functional => [ :clone_structure_to_test ] - -desc "Generate documentation for the application" -Rake::RDocTask.new("appdoc") { |rdoc| - rdoc.rdoc_dir = 'doc/app' - rdoc.title = "Rails Application Documentation" - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('doc/README_FOR_APP') - rdoc.rdoc_files.include('app/**/*.rb') -} - -desc "Generate documentation for the Rails framework" -Rake::RDocTask.new("apidoc") { |rdoc| - rdoc.rdoc_dir = 'doc/api' - rdoc.template = "#{ENV['template']}.rb" if ENV['template'] - rdoc.title = "Rails Framework Documentation" - rdoc.options << '--line-numbers --inline-source' - rdoc.rdoc_files.include('README') - rdoc.rdoc_files.include('CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/railties/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/railties/MIT-LICENSE') - rdoc.rdoc_files.include('vendor/rails/activerecord/README') - rdoc.rdoc_files.include('vendor/rails/activerecord/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/activerecord/lib/active_record/**/*.rb') - rdoc.rdoc_files.exclude('vendor/rails/activerecord/lib/active_record/vendor/*') - rdoc.rdoc_files.include('vendor/rails/actionpack/README') - rdoc.rdoc_files.include('vendor/rails/actionpack/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_controller/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionpack/lib/action_view/**/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionmailer/README') - rdoc.rdoc_files.include('vendor/rails/actionmailer/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionmailer/lib/action_mailer/base.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/README') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/api/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/client/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/container/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/dispatcher/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/protocol/*.rb') - rdoc.rdoc_files.include('vendor/rails/actionwebservice/lib/action_web_service/support/*.rb') - rdoc.rdoc_files.include('vendor/rails/activesupport/README') - rdoc.rdoc_files.include('vendor/rails/activesupport/CHANGELOG') - rdoc.rdoc_files.include('vendor/rails/activesupport/lib/active_support/**/*.rb') -} - -desc "Report code statistics (KLOCs, etc) from the application" -task :stats => [ :environment ] do - require 'code_statistics' - CodeStatistics.new( - ["Helpers", "app/helpers"], - ["Controllers", "app/controllers"], - ["APIs", "app/apis"], - ["Components", "components"], - ["Functionals", "test/functional"], - ["Models", "app/models"], - ["Units", "test/unit"] - ).to_s -end - -desc "Recreate the test databases from the development structure" -task :clone_structure_to_test => [ :db_structure_dump, :purge_test_database ] do - abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.execute('SET foreign_key_checks = 0') - IO.readlines("db/#{RAILS_ENV}_structure.sql").join.split("\n\n").each do |table| - ActiveRecord::Base.connection.execute(table) - end - when "postgresql" - ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] - ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] - ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `psql -U "#{abcs["test"]["username"]}" -f db/#{RAILS_ENV}_structure.sql #{abcs["test"]["database"]}` - when "sqlite", "sqlite3" - `#{abcs[RAILS_ENV]["adapter"]} #{abcs["test"]["dbfile"]} < db/#{RAILS_ENV}_structure.sql` - when "sqlserver" - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` - else - raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" - end -end - -desc "Dump the database structure to a SQL file" -task :db_structure_dump => :environment do - abcs = ActiveRecord::Base.configurations - case abcs[RAILS_ENV]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(abcs[RAILS_ENV]) - File.open("db/#{RAILS_ENV}_structure.sql", "w+") { |f| f << ActiveRecord::Base.connection.structure_dump } - when "postgresql" - ENV['PGHOST'] = abcs[RAILS_ENV]["host"] if abcs[RAILS_ENV]["host"] - ENV['PGPORT'] = abcs[RAILS_ENV]["port"].to_s if abcs[RAILS_ENV]["port"] - ENV['PGPASSWORD'] = abcs[RAILS_ENV]["password"].to_s if abcs[RAILS_ENV]["password"] - `pg_dump -U "#{abcs[RAILS_ENV]["username"]}" -s -x -O -f db/#{RAILS_ENV}_structure.sql #{abcs[RAILS_ENV]["database"]}` - when "sqlite", "sqlite3" - `#{abcs[RAILS_ENV]["adapter"]} #{abcs[RAILS_ENV]["dbfile"]} .schema > db/#{RAILS_ENV}_structure.sql` - when "sqlserver" - `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /f db\\#{RAILS_ENV}_structure.sql /q /A /r` - `scptxfr /s #{abcs[RAILS_ENV]["host"]} /d #{abcs[RAILS_ENV]["database"]} /I /F db\ /q /A /r` - else - raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" - end -end - -desc "Empty the test database" -task :purge_test_database => :environment do - abcs = ActiveRecord::Base.configurations - case abcs["test"]["adapter"] - when "mysql" - ActiveRecord::Base.establish_connection(:test) - ActiveRecord::Base.connection.recreate_database(abcs["test"]["database"]) - when "postgresql" - ENV['PGHOST'] = abcs["test"]["host"] if abcs["test"]["host"] - ENV['PGPORT'] = abcs["test"]["port"].to_s if abcs["test"]["port"] - ENV['PGPASSWORD'] = abcs["test"]["password"].to_s if abcs["test"]["password"] - `dropdb -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` - `createdb -T template0 -U "#{abcs["test"]["username"]}" #{abcs["test"]["database"]}` - when "sqlite","sqlite3" - File.delete(abcs["test"]["dbfile"]) if File.exist?(abcs["test"]["dbfile"]) - when "sqlserver" - dropfkscript = "#{abcs["test"]["host"]}.#{abcs["test"]["database"]}.DP1".gsub(/\\/,'-') - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{dropfkscript}` - `osql -E -S #{abcs["test"]["host"]} -d #{abcs["test"]["database"]} -i db\\#{RAILS_ENV}_structure.sql` - else - raise "Unknown database adapter '#{abcs["test"]["adapter"]}'" - end -end - -desc "Clears all *.log files in log/" -task :clear_logs => :environment do - FileList["log/*.log"].each do |log_file| - f = File.open(log_file, "w") - f.close - end -end - -desc "Migrate the database according to the migrate scripts in db/migrate (only supported on PG/MySQL). A specific version can be targetted with VERSION=x" -task :migrate => :environment do - ActiveRecord::Migrator.migrate(File.dirname(__FILE__) + '/db/migrate/', ENV["VERSION"] ? ENV["VERSION"].to_i : nil) -end +require 'tasks/rails' Added: trunk/config/boot.rb ============================================================================== --- trunk/config/boot.rb (added) +++ trunk/config/boot.rb Thu Oct 6 14:53:39 2005 @@ -1,0 +1,10 @@ +RAILS_ROOT = File.join(File.dirname(__FILE__), '..') unless defined?(RAILS_ROOT) + +if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" +else + require 'rubygems' + require 'initializer' +end + +Rails::Initializer.run(:set_load_path) Modified: trunk/config/environment.rb ============================================================================== --- trunk/config/environment.rb (original) +++ trunk/config/environment.rb Thu Oct 6 14:53:39 2005 @@ -1,87 +1,47 @@ -# Load the Rails framework and configure your application. -# You can include your own configuration at the end of this file. -# # Be sure to restart your webserver when you modify this file. -# The path to the root directory of your application. -RAILS_ROOT = File.join(File.dirname(__FILE__), '..') +# Uncomment below to force Rails into production mode +# (Use only when you can't set environment variables through your web/app server) +# ENV['RAILS_ENV'] = 'production' -# The environment your application is currently running. Don't set -# this here; put it in your webserver's configuration as the RAILS_ENV -# environment variable instead. -# -# See config/environments/*.rb for environment-specific configuration. -RAILS_ENV = ENV['RAILS_ENV'] || 'development' +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') +Rails::Initializer.run do |config| + # Skip frameworks you're not going to use + # config.frameworks -= [ :action_web_service, :action_mailer ] -# Load the Rails framework. Mock classes for testing come first. -ADDITIONAL_LOAD_PATHS = ["#{RAILS_ROOT}/test/mocks/#{RAILS_ENV}"] + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/app/services #{RAILS_ROOT}/app/services ) -# Then model subdirectories. -ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/app/models/[_a-z]*"]) -ADDITIONAL_LOAD_PATHS.concat(Dir["#{RAILS_ROOT}/components/[_a-z]*"]) + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug -# Followed by the standard includes. -ADDITIONAL_LOAD_PATHS.concat %w( - app - app/models - app/controllers - app/helpers - app/apis - components - config - lib - vendor - vendor/rails/railties - vendor/rails/railties/lib - vendor/rails/actionpack/lib - vendor/rails/activesupport/lib - vendor/rails/activerecord/lib - vendor/rails/actionmailer/lib - vendor/rails/actionwebservice/lib -).map { |dir| "#{RAILS_ROOT}/#{dir}" }.select { |dir| File.directory?(dir) } + # Use the database for sessions instead of the file system + # (create the session table with 'rake create_sessions_table') + # config.action_controller.session_store = :active_record_store -# Prepend to $LOAD_PATH -ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } + # Enable page/fragment caching by setting a file-based store + # (remember to create the caching directory and make it readable to the application) + # config.action_controller.fragment_cache_store = :file_store, "#{RAILS_ROOT}/cache" -# Require Rails libraries. -require 'rubygems' unless File.directory?("#{RAILS_ROOT}/vendor/rails") + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector -require 'active_support' -require 'active_record' -require 'action_controller' -require 'action_mailer' -require 'action_web_service' + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc -# Environment-specific configuration. -require_dependency "environments/#{RAILS_ENV}" -ActiveRecord::Base.configurations = File.open("#{RAILS_ROOT}/config/database.yml") { |f| YAML::load(f) } -ActiveRecord::Base.establish_connection - - -# Configure defaults if the included environment did not. -begin - RAILS_DEFAULT_LOGGER = Logger.new("#{RAILS_ROOT}/log/#{RAILS_ENV}.log") - RAILS_DEFAULT_LOGGER.level = (RAILS_ENV == 'production' ? Logger::INFO : Logger::DEBUG) -rescue StandardError - RAILS_DEFAULT_LOGGER = Logger.new(STDERR) - RAILS_DEFAULT_LOGGER.level = Logger::WARN - RAILS_DEFAULT_LOGGER.warn( - "Rails Error: Unable to access log file. Please ensure that log/#{RAILS_ENV}.log exists and is chmod 0666. " + - "The log level has been raised to WARN and the output directed to STDERR until the problem is fixed." - ) + # See Rails::Configuration for more options end -[ActiveRecord, ActionController, ActionMailer].each { |mod| mod::Base.logger ||= RAILS_DEFAULT_LOGGER } -[ActionController, ActionMailer].each { |mod| mod::Base.template_root ||= "#{RAILS_ROOT}/app/views/" } +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end -# Set up routes. -ActionController::Routing::Routes.reload - -Controllers = Dependencies::LoadingModule.root( - File.join(RAILS_ROOT, 'app', 'controllers'), - File.join(RAILS_ROOT, 'components') -) - -# Include your app's configuration here: - +# Include your application configuration below Modified: trunk/config/environments/development.rb ============================================================================== --- trunk/config/environments/development.rb (original) +++ trunk/config/environments/development.rb Thu Oct 6 14:53:39 2005 @@ -1,14 +1,17 @@ # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the webserver when you make code changes. +config.cache_classes = false # Log error messages when you accidentally call methods on nil. -require 'active_support/whiny_nil' +config.whiny_nils = true -# Reload code; show full error reports; disable caching. -Dependencies.mechanism = :load -ActionController::Base.consider_all_requests_local = true -ActionController::Base.perform_caching = false +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true -# The breakpoint server port that script/breakpointer connects to. -BREAKPOINT_SERVER_PORT = 42531 +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false Modified: trunk/config/environments/production.rb ============================================================================== --- trunk/config/environments/production.rb (original) +++ trunk/config/environments/production.rb Thu Oct 6 14:53:39 2005 @@ -1,8 +1,17 @@ # The production environment is meant for finished, "live" apps. -# Code is not reloaded between requests, full error reports are disabled, -# and caching is turned on. +# Code is not reloaded between requests +config.cache_classes = true -# Don't reload code; don't show full error reports; enable caching. -Dependencies.mechanism = :require -ActionController::Base.consider_all_requests_local = false -ActionController::Base.perform_caching = true +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors if you bad email addresses should just be ignored +# config.action_mailer.raise_delivery_errors = false Modified: trunk/config/environments/test.rb ============================================================================== --- trunk/config/environments/test.rb (original) +++ trunk/config/environments/test.rb Thu Oct 6 14:53:39 2005 @@ -2,16 +2,16 @@ # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! +config.cache_classes = true # Log error messages when you accidentally call methods on nil. -require 'active_support/whiny_nil' +config.whiny_nils = true -# Don't reload code; show full error reports; disable caching. -Dependencies.mechanism = :require -ActionController::Base.consider_all_requests_local = true -ActionController::Base.perform_caching = false +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false # Tell ActionMailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the # ActionMailer::Base.deliveries array. -ActionMailer::Base.delivery_method = :test +config.action_mailer.delivery_method = :test Modified: trunk/public/.htaccess ============================================================================== --- trunk/public/.htaccess (original) +++ trunk/public/.htaccess Thu Oct 6 14:53:39 2005 @@ -18,6 +18,14 @@ # Example: # RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + RewriteRule ^$ index.html [QSA] RewriteRule ^([^.]+)$ $1.html [QSA] RewriteCond %{REQUEST_FILENAME} !-f Modified: trunk/public/dispatch.cgi ============================================================================== --- trunk/public/dispatch.cgi (original) +++ trunk/public/dispatch.cgi Thu Oct 6 14:53:39 2005 @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/usr/local/bin/ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) Modified: trunk/public/dispatch.fcgi ============================================================================== --- trunk/public/dispatch.fcgi (original) +++ trunk/public/dispatch.fcgi Thu Oct 6 14:53:39 2005 @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/usr/local/bin/ruby # # You may specify the path to the FastCGI crash log (a log of unhandled # exceptions which forced the FastCGI instance to exit, great for debugging) Modified: trunk/public/dispatch.rb ============================================================================== --- trunk/public/dispatch.rb (original) +++ trunk/public/dispatch.rb Thu Oct 6 14:53:39 2005 @@ -1,4 +1,4 @@ -#!/usr/bin/ruby +#!/usr/local/bin/ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) Modified: trunk/public/javascripts/controls.js ============================================================================== --- trunk/public/javascripts/controls.js (original) +++ trunk/public/javascripts/controls.js Thu Oct 6 14:53:39 2005 @@ -1,41 +1,12 @@ // Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills // -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -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; -} +// See scriptaculous.js for full license. // Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This @@ -46,7 +17,7 @@ // a getUpdatedChoices function that will be invoked every time // the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by -// invoking this.getEntry(), NOT by directly accessing +// invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized // autocompletion. Specific auto-completion logic (AJAX, etc) // belongs in getUpdatedChoices. @@ -57,7 +28,7 @@ // new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with -// a token array, e.g. { tokens: new Array (',', '\n') } which +// a token array, e.g. { tokens: [',', '\n'] } which // enables autocompletion on multiple tokens. This is most // useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. @@ -65,57 +36,54 @@ var Autocompleter = {} Autocompleter.Base = function() {}; Autocompleter.Base.prototype = { - base_initialize: function(element, update, options) { + baseInitialize: function(element, update, options) { this.element = $(element); this.update = $(update); - this.has_focus = false; + this.hasFocus = false; this.changed = false; this.active = false; this.index = 0; - this.entry_count = 0; + this.entryCount = 0; if (this.setOptions) this.setOptions(options); else - this.options = {} - - this.options.tokens = this.options.tokens || new Array(); + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; - this.options.min_chars = this.options.min_chars || 1; + this.options.minChars = this.options.minChars || 1; this.options.onShow = this.options.onShow || function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; - var offsets = Position.cumulativeOffset(element); - update.style.left = offsets[0] + 'px'; - update.style.top = (offsets[1] + element.offsetHeight) + 'px'; - update.style.width = element.offsetWidth + 'px'; + Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight}); } new Effect.Appear(update,{duration:0.15}); }; this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - - if(this.options.indicator) - this.indicator = $(this.options.indicator); if (typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); - + this.observer = null; + this.element.setAttribute('autocomplete','off'); + Element.hide(this.update); - + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); }, show: function() { - if(this.update.style.display=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && this.update.style.position=='absolute') { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && (navigator.appVersion.indexOf('MSIE')>0) && (Element.getStyle(this.update, 'position')=='absolute')) { new Insertion.After(this.update, ''); this.iefix = $(this.update.id+'_iefix'); } @@ -126,18 +94,18 @@ Element.show(this.iefix); } }, - + hide: function() { - if(this.update.style.display=='') this.options.onHide(this.element, this.update); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); if(this.iefix) Element.hide(this.iefix); }, - + startIndicator: function() { - if(this.indicator) Element.show(this.indicator); - }, - + if(this.options.indicator) Element.show(this.options.indicator); + }, + stopIndicator: function() { - if(this.indicator) Element.hide(this.indicator); + if(this.options.indicator) Element.hide(this.options.indicator); }, onKeyPress: function(event) { @@ -145,22 +113,23 @@ switch(event.keyCode) { case Event.KEY_TAB: case Event.KEY_RETURN: - this.select_entry(); + this.selectEntry(); Event.stop(event); case Event.KEY_ESC: this.hide(); this.active = false; + Event.stop(event); return; case Event.KEY_LEFT: case Event.KEY_RIGHT: return; case Event.KEY_UP: - this.mark_previous(); + this.markPrevious(); this.render(); if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); return; case Event.KEY_DOWN: - this.mark_next(); + this.markNext(); this.render(); if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); return; @@ -168,15 +137,15 @@ else if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN) return; - + this.changed = true; - this.has_focus = true; - + this.hasFocus = true; + if(this.observer) clearTimeout(this.observer); this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, - + onHover: function(event) { var element = Event.findElement(event, 'LI'); if(this.index != element.autocompleteIndex) @@ -190,92 +159,97 @@ onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; - this.select_entry(); - Event.stop(event); + this.selectEntry(); + this.hide(); }, onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); - this.has_focus = false; + this.hasFocus = false; this.active = false; }, render: function() { - if(this.entry_count > 0) { - for (var i = 0; i < this.entry_count; i++) + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) this.index==i ? - Element.addClassName(this.get_entry(i),"selected") : - Element.removeClassName(this.get_entry(i),"selected"); + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); - if(this.has_focus) { - if(this.get_current_entry().scrollIntoView) - this.get_current_entry().scrollIntoView(false); - + if(this.hasFocus) { this.show(); this.active = true; } } else this.hide(); }, - mark_previous: function() { + markPrevious: function() { if(this.index > 0) this.index-- - else this.index = this.entry_count-1; + else this.index = this.entryCcount-1; }, - mark_next: function() { - if(this.index < this.entry_count-1) this.index++ + markNext: function() { + if(this.index < this.entryCount-1) this.index++ else this.index = 0; }, - get_entry: function(index) { + getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, - get_current_entry: function() { - return this.get_entry(this.index); + getCurrentEntry: function() { + return this.getEntry(this.index); }, - select_entry: function() { + selectEntry: function() { this.active = false; - value = Element.collectTextNodesIgnoreClass(this.get_current_entry(), 'informal').unescapeHTML(); - this.updateElement(value); - this.element.focus(); - }, - - updateElement: function(value) { - var last_token_pos = this.findLastToken(); - if (last_token_pos != -1) { - var new_value = this.element.value.substr(0, last_token_pos + 1); - var whitespace = this.element.value.substr(last_token_pos + 1).match(/^\s+/); + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + + var value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); if (whitespace) - new_value += whitespace[0]; - this.element.value = new_value + value; + newValue += whitespace[0]; + this.element.value = newValue + value; } else { this.element.value = value; - } - }, - + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + updateChoices: function(choices) { - if(!this.changed && this.has_focus) { + if(!this.changed && this.hasFocus) { this.update.innerHTML = choices; Element.cleanWhitespace(this.update); Element.cleanWhitespace(this.update.firstChild); if(this.update.firstChild && this.update.firstChild.childNodes) { - this.entry_count = + this.entryCount = this.update.firstChild.childNodes.length; - for (var i = 0; i < this.entry_count; i++) { - entry = this.get_entry(i); + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } } else { - this.entry_count = 0; + this.entryCount = 0; } - + this.stopIndicator(); - + this.index = 0; this.render(); } @@ -288,7 +262,7 @@ onObserverEvent: function() { this.changed = false; - if(this.getEntry().length>=this.options.min_chars) { + if(this.getToken().length>=this.options.minChars) { this.startIndicator(); this.getUpdatedChoices(); } else { @@ -297,58 +271,56 @@ } }, - getEntry: function() { - var token_pos = this.findLastToken(); - if (token_pos != -1) - var ret = this.element.value.substr(token_pos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); else var ret = this.element.value; - + return /\n/.test(ret) ? '' : ret; }, findLastToken: function() { - var last_token_pos = -1; + var lastTokenPos = -1; for (var i=0; i last_token_pos) - last_token_pos = this_token_pos; - } - return last_token_pos; + var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]); + if (thisTokenPos > lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; } } Ajax.Autocompleter = Class.create(); -Ajax.Autocompleter.prototype = Object.extend(new Autocompleter.Base(), -Object.extend(new Ajax.Base(), { +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { initialize: function(element, update, url, options) { - this.base_initialize(element, update, options); + this.baseInitialize(element, update, options); this.options.asynchronous = true; - this.options.onComplete = this.onComplete.bind(this) - this.options.method = 'post'; + this.options.onComplete = this.onComplete.bind(this); this.options.defaultParams = this.options.parameters || null; this.url = url; }, - + getUpdatedChoices: function() { - entry = encodeURIComponent(this.element.name) + '=' + - encodeURIComponent(this.getEntry()); - + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; - + if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; - + new Ajax.Request(this.url, this.options); }, - + onComplete: function(request) { this.updateChoices(request.responseText); } -})); +}); // The local array autocompleter. Used when you'd prefer to // inject an array of autocompletion options into the page, rather @@ -362,22 +334,22 @@ // Extra local autocompletion options: // - choices - How many autocompletion choices to offer // -// - partial_search - If false, the autocompleter will match entered +// - partialSearch - If false, the autocompleter will match entered // text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to // search anywhere in the string, additionally set -// the option full_search to true (default: off). -// -// - full_search - Search anywhere in autocomplete array strings. -// -// - partial_chars - How many characters to enter before triggering -// a partial match (unlike min_chars, which defines +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines // how many characters are required to do any match // at all). Defaults to 2. // -// - ignore_case - Whether to ignore case when autocompleting. +// - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // // It's possible to pass in a custom function as the 'selector' @@ -388,7 +360,7 @@ Autocompleter.Local = Class.create(); Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { initialize: function(element, update, array, options) { - this.base_initialize(element, update, options); + this.baseInitialize(element, update, options); this.options.array = array; }, @@ -399,41 +371,42 @@ setOptions: function(options) { this.options = Object.extend({ choices: 10, - partial_search: true, - partial_chars: 2, - ignore_case: true, - full_search: false, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, selector: function(instance) { - var ret = new Array(); // Beginning matches - var partial = new Array(); // Inside matches - var entry = instance.getEntry(); + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); var count = 0; - + for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { + ret.length < instance.options.choices ; i++) { + var elem = instance.options.array[i]; - var found_pos = instance.options.ignore_case ? + var foundPos = instance.options.ignoreCase ? elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); - while (found_pos != -1) { - if (found_pos == 0 && elem.length != entry.length) { + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; - } else if (entry.length >= instance.options.partial_chars && - instance.options.partial_search && found_pos != -1) { - if (instance.options.full_search || /\s/.test(elem.substr(found_pos-1,1))) { - partial.push("
  • " + elem.substr(0, found_pos) + "" + - elem.substr(found_pos, entry.length) + "" + elem.substr( - found_pos + entry.length) + "
  • "); + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); break; } } - found_pos = instance.options.ignore_case ? - elem.toLowerCase().indexOf(entry.toLowerCase(), found_pos + 1) : - elem.indexOf(entry, found_pos + 1); + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); } } @@ -444,3 +417,287 @@ }, options || {}); } }); + +// AJAX in-place editor +// +// The constructor takes three parameters. The first is the element +// that should support in-place editing. The second is the url to submit +// the changed value to. The server should respond with the updated +// value (the server might have post-processed it or validation might +// have prevented it from changing). The third is a hash of options. +// +// Supported options are (all are optional and have sensible defaults): +// - okText - The text of the submit button that submits the changed value +// to the server (default: "ok") +// - cancelText - The text of the link that cancels editing (default: "cancel") +// - savingText - The text being displayed as the AJAX engine communicates +// with the server (default: "Saving...") +// - formId - The id given to the
    element +// (default: the id of the element to edit plus '-inplaceeditor') + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + okText: "ok", + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + ajaxOptions: {} + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function() { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.form = this.getForm(); + this.element.parentNode.insertBefore(this.form, this.element); + Field.focus(this.editField); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + }, + getForm: function() { + form = document.createElement("form"); + form.id = this.options.formId; + Element.addClassName(form, this.options.formClassName) + form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(form); + + if (this.options.textarea) { + var br = document.createElement("br"); + form.appendChild(br); + } + + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + form.appendChild(okButton); + + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + form.appendChild(cancelLink); + return form; + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function(form) { + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(this.getText())) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.type = "text"; + textField.name = "value"; + textField.value = this.getText(); + textField.style.backgroundColor = this.options.highlightcolor; + var size = this.options.size || this.options.cols || 0; + if (size != 0) + textField.size = size; + form.appendChild(textField); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.name = "value"; + textArea.value = this.convertHTMLLineBreaks(this.getText()); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + form.appendChild(textArea); + this.editField = textArea; + } + }, + getText: function() { + if (this.options.loadTextURL) { + this.loadExternalText(); + return this.options.loadingText; + } else { + return this.element.innerHTML; + } + }, + loadExternalText: function() { + new Ajax.Request( + this.options.loadTextURL, + { + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + } + ); + }, + onLoadedExternalText: function(transport) { + this.form.value.value = transport.responseText.stripTags(); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + this.saving = true; + 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(this.form, this.editField.value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions) + ); + this.onLoading(); + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; Modified: trunk/public/javascripts/dragdrop.js ============================================================================== --- trunk/public/javascripts/dragdrop.js (original) +++ trunk/public/javascripts/dragdrop.js Thu Oct 6 14:53:39 2005 @@ -2,193 +2,79 @@ // // Element.Class part Copyright (c) 2005 by Rick Olson // -// Permission is hereby granted, free of charge, to any person obtaining -// a copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to -// permit persons to whom the Software is furnished to do so, subject to -// the following conditions: -// -// The above copyright notice and this permission notice shall be -// included in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -Element.Class = { - // Element.toggleClass(element, className) toggles the class being on/off - // Element.toggleClass(element, className1, className2) toggles between both classes, - // defaulting to className1 if neither exist - toggle: function(element, className) { - if(Element.Class.has(element, className)) { - Element.Class.remove(element, className); - if(arguments.length == 3) Element.Class.add(element, arguments[2]); - } else { - Element.Class.add(element, className); - if(arguments.length == 3) Element.Class.remove(element, arguments[2]); - } - }, - - // gets space-delimited classnames of an element as an array - get: function(element) { - element = $(element); - return element.className.split(' '); - }, - - // functions adapted from original functions by Gavin Kistner - remove: function(element) { - element = $(element); - var regEx; - for(var i = 1; i < arguments.length; i++) { - regEx = new RegExp("^" + arguments[i] + "\\b\\s*|\\s*\\b" + arguments[i] + "\\b", 'g'); - element.className = element.className.replace(regEx, '') - } - }, - - add: function(element) { - element = $(element); - for(var i = 1; i < arguments.length; i++) { - Element.Class.remove(element, arguments[i]); - element.className += (element.className.length > 0 ? ' ' : '') + arguments[i]; - } - }, - - // returns true if all given classes exist in said element - has: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - regEx = new RegExp("\\b" + arguments[i] + "\\b"); - if(!regEx.test(element.className)) return false; - } - return true; - }, - - // expects arrays of strings and/or strings as optional paramters - // Element.Class.has_any(element, ['classA','classB','classC'], 'classD') - has_any: function(element) { - element = $(element); - if(!element || !element.className) return false; - var regEx; - for(var i = 1; i < arguments.length; i++) { - if((typeof arguments[i] == 'object') && - (arguments[i].constructor == Array)) { - for(var j = 0; j < arguments[i].length; j++) { - regEx = new RegExp("\\b" + arguments[i][j] + "\\b"); - if(regEx.test(element.className)) return true; - } - } else { - regEx = new RegExp("\\b" + arguments[i] + "\\b"); - if(regEx.test(element.className)) return true; - } - } - return false; - }, - - childrenWith: function(element, className) { - var children = $(element).getElementsByTagName('*'); - var elements = new Array(); - - for (var i = 0; i < children.length; i++) { - if (Element.Class.has(children[i], className)) { - elements.push(children[i]); - break; - } - } - - return elements; - } -} +// See scriptaculous.js for full license. /*--------------------------------------------------------------------------*/ var Droppables = { - drops: false, - + drops: [], + remove: function(element) { - for(var i = 0; i < this.drops.length; i++) - if(this.drops[i].element == element) - this.drops.splice(i,1); - }, - + this.drops = this.drops.reject(function(e) { return e==element }); + }, + add: function(element) { - var element = $(element); + element = $(element); var options = Object.extend({ greedy: true, hoverclass: null }, arguments[1] || {}); - + // cache containers if(options.containment) { - options._containers = new Array(); + options._containers = []; var containment = options.containment; if((typeof containment == 'object') && (containment.constructor == Array)) { - for(var i=0; i0) window.scrollBy(0,0); - + Event.stop(event); } } @@ -381,7 +298,7 @@ /*--------------------------------------------------------------------------*/ -SortableObserver = Class.create(); +var SortableObserver = Class.create(); SortableObserver.prototype = { initialize: function(element, observer) { this.element = $(element); @@ -391,147 +308,197 @@ onStart: function() { this.lastValue = Sortable.serialize(this.element); }, - onEnd: function() { + onEnd: function() { + Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) this.observer(this.element) } } -Sortable = { +var Sortable = { sortables: new Array(), options: function(element){ - var element = $(element); - for(var i=0;i0.5) { - if(dropon.previousSibling != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) - oldParentNode.sortable.onChange(element); - if(dropon.parentNode.sortable) - dropon.parentNode.sortable.onChange(element); - } - } else { - var nextElement = dropon.nextSibling || null; - if(nextElement != element) { - var oldParentNode = element.parentNode; - element.style.visibility = "hidden"; // fix gecko rendering - dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode && oldParentNode.sortable) - oldParentNode.sortable.onChange(element); - if(dropon.parentNode.sortable) - dropon.parentNode.sortable.onChange(element); - } - } - } + onHover: Sortable.onHover, + greedy: !options.dropOnEmpty } // fix for gecko engine Element.cleanWhitespace(element); - + options.draggables = []; options.droppables = []; - - // make it so - var elements = element.childNodes; - for (var i = 0; i < elements.length; i++) - if(elements[i].tagName && elements[i].tagName==options.tag.toUpperCase() && - (!options.only || (Element.Class.has(elements[i], options.only)))) { - - // handles are per-draggable - var handle = options.handle ? - Element.Class.childrenWith(elements[i], options.handle)[0] : elements[i]; - - options.draggables.push(new Draggable(elements[i], Object.extend(options_for_draggable, { handle: handle }))); - - Droppables.add(elements[i], options_for_droppable); - options.droppables.push(elements[i]); - - } - + + // make it so + + // drop on empty handling + if(options.dropOnEmpty) { + Droppables.add(element, + {containment: options.containment, onHover: Sortable.onEmptyHover, greedy: false}); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + Element.Class.childrenWith(e, options.handle)[0] : e; + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + options.droppables.push(e); + }); + // keep reference this.sortables.push(options); - + // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + if(!element.hasChildNodes()) return null; + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName==options.tag.toUpperCase() && + (!options.only || (Element.Class.has(e, options.only)))) + elements.push(e); + if(options.tree) { + var grandchildren = this.findElements(e, options); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : null); + }, + + onHover: function(element, dropon, overlap) { + if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon) { + if(element.parentNode!=dropon) { + dropon.appendChild(element); + } + }, + + unmark: function() { + if(Sortable._marker) Element.hide(Sortable._marker); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = $('dropmarker') || document.createElement('DIV'); + Element.hide(Sortable._marker); + Element.Class.add(Sortable._marker, 'dropmarker'); + Sortable._marker.style.position = 'absolute'; + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.style.top = offsets[1] + 'px'; + if(position=='after') Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px'; + Sortable._marker.style.left = offsets[0] + 'px'; + Element.show(Sortable._marker); + }, + serialize: function(element) { - var element = $(element); + element = $(element); var sortableOptions = this.options(element); var options = Object.extend({ tag: sortableOptions.tag, only: sortableOptions.only, name: element.id }, arguments[1] || {}); - - var items = $(element).childNodes; - var queryComponents = new Array(); - - for(var i=0; i= this.finishOn) { - this.render(this.options.to); - if(this.finish) this.finish(); - if(this.options.afterFinish) this.options.afterFinish(this); - return; - } - var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); - var frame = Math.round(pos * this.options.fps * this.options.duration); - if(frame > this.currentFrame) { - this.render(pos); - this.currentFrame = frame; - } - this.timeout = setTimeout(this.loop.bind(this), 10); + this.event('beforeStart'); + if(!this.options.sync) Effect.Queue.add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } }, render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } if(this.options.transition) pos = this.options.transition(pos); pos *= (this.options.to-this.options.from); - pos += this.options.from; - if(this.options.beforeUpdate) this.options.beforeUpdate(this); + pos += this.options.from; + this.event('beforeUpdate'); if(this.update) this.update(pos); - if(this.options.afterUpdate) this.options.afterUpdate(this); + this.event('afterUpdate'); }, cancel: function() { - if(this.timeout) clearTimeout(this.timeout); + if(!this.options.sync) Effect.Queue.remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); } } @@ -133,35 +190,36 @@ this.start(arguments[1]); }, update: function(position) { - for (var i = 0; i < this.effects.length; i++) - this.effects[i].render(position); + this.effects.invoke('render', position); }, finish: function(position) { - for (var i = 0; i < this.effects.length; i++) - if(this.effects[i].finish) this.effects[i].finish(position); + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); } }); -// Internet Explorer caveat: works only on elements the have -// a 'layout', meaning having a given width or height. -// There is no way to safely set this automatically. Effect.Opacity = Class.create(); Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); - options = Object.extend({ - from: 0.0, + + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout)) + this.element.style.zoom = 1; + + var options = Object.extend({ + from: Element.getOpacity(this.element) || 0.0, to: 1.0 }, arguments[1] || {}); this.start(options); }, update: function(position) { - this.setOpacity(position); - }, - setOpacity: function(opacity) { - opacity = (opacity == 1) ? 0.99999 : opacity; - this.element.style.opacity = opacity; - this.element.style.filter = "alpha(opacity:"+opacity*100+")"; + Element.setOpacity(this.element, position); } }); @@ -169,16 +227,23 @@ Object.extend(Object.extend(Effect.MoveBy.prototype, Effect.Base.prototype), { initialize: function(element, toTop, toLeft) { this.element = $(element); - this.originalTop = parseFloat(this.element.style.top || '0'); - this.originalLeft = parseFloat(this.element.style.left || '0'); this.toTop = toTop; this.toLeft = toLeft; + this.start(arguments[3]); + }, + 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.start(arguments[3]); + this.originalTop = parseFloat(Element.getStyle(this.element,'top') || '0'); + this.originalLeft = parseFloat(Element.getStyle(this.element,'left') || '0'); }, update: function(position) { - topd = this.toTop * position + this.originalTop; - leftd = this.toLeft * position + this.originalLeft; + var topd = this.toTop * position + this.originalTop; + var leftd = this.toLeft * position + this.originalLeft; this.setPosition(topd, leftd); }, setPosition: function(topd, leftd) { @@ -191,50 +256,76 @@ Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { initialize: function(element, percent) { this.element = $(element) - options = Object.extend({ + var options = Object.extend({ scaleX: true, scaleY: true, scaleContent: true, scaleFromCenter: false, scaleMode: 'box', // 'box' or 'contents' or {} with provided values - scaleFrom: 100.0 + scaleFrom: 100.0, + scaleTo: percent }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = Element.getStyle(this.element,'position'); + this.elementStyleTop = this.element.style.top; + this.elementStyleLeft = this.element.style.left; + this.elementStyleWidth = this.element.style.width; + this.elementStyleHeight = this.element.style.height; + this.elementStyleFontSize = this.element.style.fontSize; this.originalTop = this.element.offsetTop; this.originalLeft = this.element.offsetLeft; - if(this.element.style.fontSize=="") this.sizeEm = 1.0; - if(this.element.style.fontSize && this.element.style.fontSize.indexOf("em")>0) - this.sizeEm = parseFloat(this.element.style.fontSize); - this.factor = (percent/100.0) - (options.scaleFrom/100.0); - if(options.scaleMode=='box') { + var fontSize = Element.getStyle(this.element,'font-size') || "100%"; + if(fontSize.indexOf("em")>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = "em"; + } else if(fontSize.indexOf("px")>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = "px"; + } else if(fontSize.indexOf("%")>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = "%"; + } + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + if(this.options.scaleMode=='box') { this.originalHeight = this.element.clientHeight; this.originalWidth = this.element.clientWidth; - } else - if(options.scaleMode=='contents') { + } else if(this.options.scaleMode=='contents') { this.originalHeight = this.element.scrollHeight; this.originalWidth = this.element.scrollWidth; } else { - this.originalHeight = options.scaleMode.originalHeight; - this.originalWidth = options.scaleMode.originalWidth; - } - this.start(options); - }, - + this.originalHeight = this.options.scaleMode.originalHeight; + this.originalWidth = this.options.scaleMode.originalWidth; + } + }, update: function(position) { - currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); - if(this.options.scaleContent && this.sizeEm) - this.element.style.fontSize = this.sizeEm*currentScale + "em"; + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.style.fontSize = this.fontSize*currentScale + this.fontSizeType; this.setDimensions( this.originalWidth * currentScale, this.originalHeight * currentScale); }, - + finish: function(position) { + if (this.restoreAfterFinish) { + var els = this.element.style; + els.top = this.elementStyleTop; + els.left = this.elementStyleLeft; + els.width = this.elementStyleWidth; + els.height = this.elementStyleHeight; + els.height = this.elementStyleHeight; + els.fontSize = this.elementStyleFontSize; + } + }, setDimensions: function(width, height) { if(this.options.scaleX) this.element.style.width = width + 'px'; if(this.options.scaleY) this.element.style.height = height + 'px'; if(this.options.scaleFromCenter) { - topd = (height - this.originalHeight)/2; - leftd = (width - this.originalWidth)/2; - if(this.element.style.position=='absolute') { + var topd = (height - this.originalHeight)/2; + var leftd = (width - this.originalWidth)/2; + if(this.elementPositioning == 'absolute') { if(this.options.scaleY) this.element.style.top = this.originalTop-topd + "px"; if(this.options.scaleX) this.element.style.left = this.originalLeft-leftd + "px"; } else { @@ -249,33 +340,28 @@ Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); - - // try to parse current background color as default for endcolor - // browser stores this as: "rgb(255, 255, 255)", convert to "#ffffff" format - var endcolor = "#ffffff"; - var current = this.element.style.backgroundColor; - if(current && current.slice(0,4) == "rgb(") { - endcolor = "#"; - var cols = current.slice(4,current.length-1).split(','); - var i=0; do { endcolor += parseInt(cols[i]).toColorPart() } while (++i<3); } - var options = Object.extend({ - startcolor: "#ffff99", - endcolor: endcolor, - restorecolor: current + startcolor: "#ffff99" }, arguments[1] || {}); - + this.start(options); + }, + setup: function() { + // Disable background image during the effect + this.oldBgImage = this.element.style.backgroundImage; + this.element.style.backgroundImage = "none"; + if(!this.options.endcolor) + this.options.endcolor = Element.getStyle(this.element, 'background-color').parseColor('#ffffff'); + if (typeof this.options.restorecolor == "undefined") + this.options.restorecolor = this.element.style.backgroundColor; // init color calculations this.colors_base = [ - parseInt(options.startcolor.slice(1,3),16), - parseInt(options.startcolor.slice(3,5),16), - parseInt(options.startcolor.slice(5),16) ]; + parseInt(this.options.startcolor.slice(1,3),16), + parseInt(this.options.startcolor.slice(3,5),16), + parseInt(this.options.startcolor.slice(5),16) ]; this.colors_delta = [ - parseInt(options.endcolor.slice(1,3),16)-this.colors_base[0], - parseInt(options.endcolor.slice(3,5),16)-this.colors_base[1], - parseInt(options.endcolor.slice(5),16)-this.colors_base[2] ]; - - this.start(options); + parseInt(this.options.endcolor.slice(1,3),16)-this.colors_base[0], + parseInt(this.options.endcolor.slice(3,5),16)-this.colors_base[1], + parseInt(this.options.endcolor.slice(5),16)-this.colors_base[2]]; }, update: function(position) { var colors = [ @@ -287,6 +373,7 @@ }, finish: function() { this.element.style.backgroundColor = this.options.restorecolor; + this.element.style.backgroundImage = this.oldBgImage; } }); @@ -294,6 +381,9 @@ Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { initialize: function(element) { this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { Position.prepare(); var offsets = Position.cumulativeOffset(this.element); var max = window.innerHeight ? @@ -302,8 +392,7 @@ (document.documentElement.clientHeight ? document.documentElement.clientHeight : document.body.clientHeight); this.scrollStart = Position.deltaY; - this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; - this.start(arguments[1] || {}); + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; }, update: function(position) { Position.prepare(); @@ -312,51 +401,61 @@ } }); -/* ------------- prepackaged effects ------------- */ +/* ------------- combination effects ------------- */ Effect.Fade = function(element) { - options = Object.extend({ - from: 1.0, + var oldOpacity = Element.getInlineOpacity(element); + var options = Object.extend({ + from: Element.getOpacity(element) || 1.0, to: 0.0, - afterFinish: function(effect) - { Element.hide(effect.element); - effect.setOpacity(1); } + afterFinishInternal: function(effect) + { if (effect.options.to == 0) { + Element.hide(effect.element); + Element.setInlineOpacity(effect.element, oldOpacity); + } + } }, arguments[1] || {}); - new Effect.Opacity(element,options); + return new Effect.Opacity(element,options); } Effect.Appear = function(element) { - options = Object.extend({ - from: 0.0, + var options = Object.extend({ + from: (Element.getStyle(element, "display") == "none" ? 0.0 : Element.getOpacity(element) || 0.0), to: 1.0, - beforeStart: function(effect) - { effect.setOpacity(0); - Element.show(effect.element); }, - afterUpdate: function(effect) - { Element.show(effect.element); } + beforeSetup: function(effect) + { Element.setOpacity(effect.element, effect.options.from); + Element.show(effect.element); } }, arguments[1] || {}); - new Effect.Opacity(element,options); + return new Effect.Opacity(element,options); } Effect.Puff = function(element) { - new Effect.Parallel( - [ new Effect.Scale(element, 200, { sync: true, scaleFromCenter: true }), - new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], - { duration: 1.0, - afterUpdate: function(effect) + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + var oldPosition = element.style.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) { effect.effects[0].element.style.position = 'absolute'; }, - afterFinish: function(effect) - { Element.hide(effect.effects[0].element); } - } + afterFinishInternal: function(effect) + { Element.hide(effect.effects[0].element); + effect.effects[0].element.style.position = oldPosition; + Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } + }, arguments[1] || {}) ); } Effect.BlindUp = function(element) { + element = $(element); Element.makeClipping(element); - new Effect.Scale(element, 0, + return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, - afterFinish: function(effect) + restoreAfterFinish: true, + afterFinishInternal: function(effect) { Element.hide(effect.element); Element.undoClipping(effect.element); @@ -366,120 +465,179 @@ } Effect.BlindDown = function(element) { - $(element).style.height = '0px'; - Element.makeClipping(element); - Element.show(element); - new Effect.Scale(element, 100, + element = $(element); + var oldHeight = element.style.height; + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, - scaleX: false, - scaleMode: 'contents', + scaleX: false, scaleFrom: 0, - afterFinish: function(effect) { + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + Element.makeClipping(effect.element); + effect.element.style.height = "0px"; + Element.show(effect.element); + }, + afterFinishInternal: function(effect) { Element.undoClipping(effect.element); + effect.element.style.height = oldHeight; } }, arguments[1] || {}) ); } Effect.SwitchOff = function(element) { - new Effect.Appear(element, - { duration: 0.4, - transition: Effect.Transitions.flicker, - afterFinish: function(effect) - { effect.element.style.overflow = 'hidden'; - new Effect.Scale(effect.element, 1, - { duration: 0.3, scaleFromCenter: true, - scaleX: false, scaleContent: false, - afterUpdate: function(effect) { - if(effect.element.style.position=="") - effect.element.style.position = 'relative'; }, - afterFinish: function(effect) { Element.hide(effect.element); } - } ) - } - } ); + element = $(element); + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Appear(element, { + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + Element.makePositioned(effect.element); + Element.makeClipping(effect.element); + }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element); + Element.setInlineOpacity(effect.element, oldOpacity); + } + }) + } + }); } Effect.DropOut = function(element) { - new Effect.Parallel( + element = $(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldOpacity = Element.getInlineOpacity(element); + return new Effect.Parallel( [ new Effect.MoveBy(element, 100, 0, { sync: true }), - new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0 } ) ], - { duration: 0.5, - afterFinish: function(effect) - { Element.hide(effect.effects[0].element); } - }); + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + Element.makePositioned(effect.effects[0].element); }, + afterFinishInternal: function(effect) { + Element.hide(effect.effects[0].element); + Element.undoPositioned(effect.effects[0].element); + effect.effects[0].element.style.left = oldLeft; + effect.effects[0].element.style.top = oldTop; + Element.setInlineOpacity(effect.effects[0].element, oldOpacity); } + }, arguments[1] || {})); } Effect.Shake = function(element) { - new Effect.MoveBy(element, 0, 20, - { duration: 0.05, afterFinish: function(effect) { + element = $(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + return new Effect.MoveBy(element, 0, 20, + { duration: 0.05, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinish: function(effect) { + { duration: 0.1, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinish: function(effect) { + { duration: 0.1, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, -40, - { duration: 0.1, afterFinish: function(effect) { + { duration: 0.1, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, 40, - { duration: 0.1, afterFinish: function(effect) { + { duration: 0.1, afterFinishInternal: function(effect) { new Effect.MoveBy(effect.element, 0, -20, - { duration: 0.05, afterFinish: function(effect) { + { duration: 0.05, afterFinishInternal: function(effect) { + Element.undoPositioned(effect.element); + effect.element.style.left = oldLeft; + effect.element.style.top = oldTop; }}) }}) }}) }}) }}) }}); } Effect.SlideDown = function(element) { element = $(element); - element.style.height = '0px'; - Element.makeClipping(element); Element.cleanWhitespace(element); - Element.makePositioned(element.firstChild); - Element.show(element); - new Effect.Scale(element, 100, + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.firstChild.style.bottom; + var elementDimensions = Element.getDimensions(element); + return new Effect.Scale(element, 100, Object.extend({ scaleContent: false, scaleX: false, - scaleMode: 'contents', scaleFrom: 0, - afterUpdate: function(effect) - { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, - afterFinish: function(effect) - { Element.undoClipping(effect.element); } + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + Element.makePositioned(effect.element.firstChild); + if (window.opera) effect.element.firstChild.style.top = ""; + Element.makeClipping(effect.element); + element.style.height = '0'; + Element.show(element); + }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinishInternal: function(effect) { + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element.firstChild); + effect.element.firstChild.style.bottom = oldInnerBottom; } }, arguments[1] || {}) ); } Effect.SlideUp = function(element) { element = $(element); - Element.makeClipping(element); Element.cleanWhitespace(element); - Element.makePositioned(element.firstChild); - Element.show(element); - new Effect.Scale(element, 0, + var oldInnerBottom = element.firstChild.style.bottom; + return new Effect.Scale(element, 0, Object.extend({ scaleContent: false, scaleX: false, - afterUpdate: function(effect) - { effect.element.firstChild.style.bottom = - (effect.originalHeight - effect.element.clientHeight) + 'px'; }, - afterFinish: function(effect) - { + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + Element.makePositioned(effect.element.firstChild); + if (window.opera) effect.element.firstChild.style.top = ""; + Element.makeClipping(effect.element); + Element.show(element); + }, + afterUpdateInternal: function(effect) { + effect.element.firstChild.style.bottom = + (effect.originalHeight - effect.element.clientHeight) + 'px'; }, + afterFinishInternal: function(effect) { Element.hide(effect.element); - Element.undoClipping(effect.element); - } + Element.undoClipping(effect.element); + Element.undoPositioned(effect.element.firstChild); + effect.element.firstChild.style.bottom = oldInnerBottom; } }, arguments[1] || {}) ); } Effect.Squish = function(element) { - new Effect.Scale(element, 0, - { afterFinish: function(effect) { Element.hide(effect.element); } }); + // Bug in opera makes the TD containing this element expand for a instance after finish + return new Effect.Scale(element, window.opera ? 1 : 0, + { restoreAfterFinish: true, + beforeSetup: function(effect) { + Element.makeClipping(effect.element); }, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); } + }); } Effect.Grow = function(element) { element = $(element); var options = arguments[1] || {}; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - element.style.overflow = 'hidden'; - Element.show(element); + var elementDimensions = Element.getDimensions(element); + var originalWidth = elementDimensions.width; + var originalHeight = elementDimensions.height; + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldHeight = element.style.height; + var oldWidth = element.style.width; + var oldOpacity = Element.getInlineOpacity(element); var direction = options.direction || 'center'; var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; @@ -517,18 +675,40 @@ break; } - new Effect.MoveBy(element, initialMoveY, initialMoveX, { + return new Effect.MoveBy(element, initialMoveY, initialMoveX, { duration: 0.01, - beforeUpdate: function(effect) { $(element).style.height = '0px'; }, - afterFinish: function(effect) { + beforeSetup: function(effect) { + Element.hide(effect.element); + Element.makeClipping(effect.element); + Element.makePositioned(effect.element); + }, + afterFinishInternal: function(effect) { new Effect.Parallel( - [ new Effect.Opacity(element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), - new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }), - new Effect.Scale(element, 100, { + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: opacityTransition }), + new Effect.MoveBy(effect.element, moveY, moveX, { sync: true, transition: moveTransition }), + new Effect.Scale(effect.element, 100, { scaleMode: { originalHeight: originalHeight, originalWidth: originalWidth }, - sync: true, scaleFrom: 0, scaleTo: 100, transition: scaleTransition })], - options); } - }); + sync: true, scaleFrom: window.opera ? 1 : 0, transition: scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.style.height = 0; + Element.show(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + var el = effect.effects[0].element; + var els = el.style; + Element.undoClipping(el); + Element.undoPositioned(el); + els.top = oldTop; + els.left = oldLeft; + els.height = oldHeight; + els.width = originalWidth; + Element.setInlineOpacity(el, oldOpacity); + } + }, options) + ) + } + }); } Effect.Shrink = function(element) { @@ -537,8 +717,11 @@ var originalWidth = element.clientWidth; var originalHeight = element.clientHeight; - element.style.overflow = 'hidden'; - Element.show(element); + var oldTop = element.style.top; + var oldLeft = element.style.left; + var oldHeight = element.style.height; + var oldWidth = element.style.width; + var oldOpacity = Element.getInlineOpacity(element); var direction = options.direction || 'center'; var moveTransition = options.moveTransition || Effect.Transitions.sinoidal; @@ -569,44 +752,65 @@ break; } - new Effect.Parallel( + return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: opacityTransition }), - new Effect.Scale(element, 0, { sync: true, transition: moveTransition }), - new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: scaleTransition }) ], - options); + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: scaleTransition, restoreAfterFinish: true}), + new Effect.MoveBy(element, moveY, moveX, { sync: true, transition: moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + Element.makePositioned(effect.effects[0].element); + Element.makeClipping(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + var el = effect.effects[0].element; + var els = el.style; + Element.hide(el); + Element.undoClipping(el); + Element.undoPositioned(el); + els.top = oldTop; + els.left = oldLeft; + els.height = oldHeight; + els.width = oldWidth; + Element.setInlineOpacity(el, oldOpacity); + } + }, options) + ); } Effect.Pulsate = function(element) { + element = $(element); var options = arguments[1] || {}; + var oldOpacity = Element.getInlineOpacity(element); var transition = options.transition || Effect.Transitions.sinoidal; var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) }; reverser.bind(transition); - new Effect.Opacity(element, - Object.extend(Object.extend({ duration: 3.0, - afterFinish: function(effect) { Element.show(effect.element); } + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 3.0, from: 0, + afterFinishInternal: function(effect) { Element.setInlineOpacity(effect.element, oldOpacity); } }, options), {transition: reverser})); } Effect.Fold = function(element) { - $(element).style.overflow = 'hidden'; - new Effect.Scale(element, 5, Object.extend({ - scaleContent: false, - scaleTo: 100, - scaleX: false, - afterFinish: function(effect) { - new Effect.Scale(element, 1, { - scaleContent: false, - scaleTo: 0, - scaleY: false, - afterFinish: function(effect) { Element.hide(effect.element) } }); - }}, arguments[1] || {})); -} - -// old: new Effect.ContentZoom(element, percent) -// new: Element.setContentZoom(element, percent) - -Element.setContentZoom = function(element, percent) { - var element = $(element); - element.style.fontSize = (percent/100) + "em"; - if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); -} + element = $(element); + var originalTop = element.style.top; + var originalLeft = element.style.left; + var originalWidth = element.style.width; + var originalHeight = element.style.height; + Element.makeClipping(element); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + Element.hide(effect.element); + Element.undoClipping(effect.element); + effect.element.style.top = originalTop; + effect.element.style.left = originalLeft; + effect.element.style.width = originalWidth; + effect.element.style.height = originalHeight; + } }); + }}, arguments[1] || {})); +} Modified: trunk/public/javascripts/prototype.js ============================================================================== --- trunk/public/javascripts/prototype.js (original) +++ trunk/public/javascripts/prototype.js Thu Oct 6 14:53:39 2005 @@ -1,4 +1,4 @@ -/* Prototype JavaScript framework, version 1.3.1 +/* Prototype JavaScript framework, version 1.4.0_pre7 * (c) 2005 Sam Stephenson * * THIS FILE IS AUTOMATICALLY GENERATED. When sending patches, please diff @@ -11,8 +11,10 @@ /*--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.3.1', - emptyFunction: function() {} + Version: '1.4.0_pre7', + + emptyFunction: function() {}, + K: function(x) {return x} } var Class = { @@ -32,29 +34,36 @@ return destination; } -Object.prototype.extend = function(object) { - return Object.extend.apply(this, [this, object]); -} - Function.prototype.bind = function(object) { var __method = this; return function() { - __method.apply(object, arguments); + return __method.apply(object, arguments); } } Function.prototype.bindAsEventListener = function(object) { var __method = this; return function(event) { - __method.call(object, event || window.event); - } -} - -Number.prototype.toColorPart = function() { - var digits = this.toString(16); - if (this < 16) return '0' + digits; - return digits; -} + return __method.call(object, event || window.event); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); var Try = { these: function() { @@ -140,14 +149,14 @@ object.__apply__ = this; var result = eval('object.__apply__(' + - parameterStrings[i].join(', ') + ')'); + parameterStrings.join(', ') + ')'); object.__apply__ = null; return result; } } -String.prototype.extend({ +Object.extend(String.prototype, { stripTags: function() { return this.replace(/<\/?[^>]+>/gi, ''); }, @@ -163,8 +172,261 @@ var div = document.createElement('div'); div.innerHTML = this.stripTags(); return div.childNodes[0].nodeValue; - } -}); + }, + + parseQuery: function() { + var str = this; + if (str.substring(0,1) == '?') { + str = this.substring(1); + } + var result = {}; + var pairs = str.split('&'); + for (var i = 0; i < pairs.length; i++) { + var pair = pairs[i].split('='); + result[pair[0]] = pair[1]; + } + return result; + } +}); + + +var _break = new Object(); +var _continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != _continue) throw e; + } + }); + } catch (e) { + if (e != _break) throw e; + } + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + if (!(result &= (iterator || Prototype.K)(value, index))) + throw _break; + }); + return result; + }, + + any: function(iterator) { + var result = true; + this.each(function(value, index) { + if (result &= (iterator || Prototype.K)(value, index)) + throw _break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function (iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw _break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw _break; + } + }); + return found; + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.collect(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value >= (result || value)) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (value <= (result || value)) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.collect(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.collect(Prototype.K); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + iterator(value = collections.pluck(index)); + return value; + }); + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); + +var $A = Array.from = function(iterable) { + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0; i < iterable.length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0; i < this.length; i++) + iterator(this[i]); + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + } +}); + +Object.extend(Array.prototype, Enumerable); + +var Range = Class.create(); +Object.extend(Range.prototype, Enumerable); +Object.extend(Range.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + do { + iterator(value); + value = value.succ(); + } while (this.include(value)); + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new Range(start, end, exclusive); +} var Ajax = { getTransport: function() { @@ -183,7 +445,8 @@ method: 'post', asynchronous: true, parameters: '' - }.extend(options || {}); + } + Object.extend(this.options, options || {}); }, responseIsSuccess: function() { @@ -201,7 +464,7 @@ Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; -Ajax.Request.prototype = (new Ajax.Base()).extend({ +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { initialize: function(url, options) { this.transport = Ajax.getTransport(); this.setOptions(options); @@ -262,16 +525,26 @@ if (readyState != 1) this.respondToReadyState(this.transport.readyState); }, + + evalJSON: function() { + try { + var json = this.transport.getResponseHeader('X-JSON'), object; + object = eval(json); + return object; + } catch (e) { + } + }, respondToReadyState: function(readyState) { var event = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); if (event == 'Complete') (this.options['on' + this.transport.status] - || this.options['on' + this.responseIsSuccess() ? 'Success' : 'Failure'] - || Prototype.emptyFunction)(this.transport); - - (this.options['on' + event] || Prototype.emptyFunction)(this.transport); + || this.options['on' + (this.responseIsSuccess() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + + (this.options['on' + event] || Prototype.emptyFunction)(transport, json); /* Avoid memory leak in MSIE: clean up the oncomplete event handler */ if (event == 'Complete') @@ -282,7 +555,7 @@ Ajax.Updater = Class.create(); Ajax.Updater.ScriptFragment = '(?:)((\n|.)*?)(?:<\/script>)'; -Ajax.Updater.prototype.extend(Ajax.Request.prototype).extend({ +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { initialize: function(container, url, options) { this.containers = { success: container.success ? $(container.success) : $(container), @@ -294,9 +567,9 @@ this.setOptions(options); var onComplete = this.options.onComplete || Prototype.emptyFunction; - this.options.onComplete = (function() { + this.options.onComplete = (function(transport, object) { this.updateContent(); - onComplete(this.transport); + onComplete(transport, object); }).bind(this); this.request(url); @@ -320,8 +593,7 @@ if (this.responseIsSuccess()) { if (this.onComplete) - setTimeout((function() {this.onComplete( - this.transport)}).bind(this), 10); + setTimeout(this.onComplete.bind(this), 10); } if (this.options.evalScripts && scripts) { @@ -335,7 +607,7 @@ }); Ajax.PeriodicalUpdater = Class.create(); -Ajax.PeriodicalUpdater.prototype = (new Ajax.Base()).extend({ +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { initialize: function(container, url, options) { this.setOptions(options); this.onComplete = this.options.onComplete; @@ -494,20 +766,35 @@ this.content = content; if (this.adjacency && this.element.insertAdjacentHTML) { - this.element.insertAdjacentHTML(this.adjacency, this.content); + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + if (this.element.tagName.toLowerCase() == 'tbody') { + this.fragment = this.contentFromAnonymousTable(); + this.insertContent(); + } else { + throw e; + } + } } else { this.range = this.element.ownerDocument.createRange(); if (this.initializeRange) this.initializeRange(); this.fragment = this.range.createContextualFragment(this.content); this.insertContent(); } + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return div.childNodes[0].childNodes[0].childNodes[0]; } } var Insertion = new Object(); Insertion.Before = Class.create(); -Insertion.Before.prototype = (new Abstract.Insertion('beforeBegin')).extend({ +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { initializeRange: function() { this.range.setStartBefore(this.element); }, @@ -518,7 +805,7 @@ }); Insertion.Top = Class.create(); -Insertion.Top.prototype = (new Abstract.Insertion('afterBegin')).extend({ +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(true); @@ -530,7 +817,7 @@ }); Insertion.Bottom = Class.create(); -Insertion.Bottom.prototype = (new Abstract.Insertion('beforeEnd')).extend({ +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { initializeRange: function() { this.range.selectNodeContents(this.element); this.range.collapse(this.element); @@ -542,7 +829,7 @@ }); Insertion.After = Class.create(); -Insertion.After.prototype = (new Abstract.Insertion('afterEnd')).extend({ +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { initializeRange: function() { this.range.setStartAfter(this.element); }, @@ -704,19 +991,32 @@ textarea: function(element) { return [element.name, element.value]; }, - + select: function(element) { - var value = ''; - if (element.type == 'select-one') { - var index = element.selectedIndex; - if (index >= 0) - value = element.options[index].value || element.options[index].text; - } else { - value = new Array(); - for (var i = 0; i < element.length; i++) { - var opt = element.options[i]; - if (opt.selected) - value.push(opt.value || opt.text); + return Form.Element.Serializers[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var value = '', opt, index = element.selectedIndex; + if (index >= 0) { + opt = element.options[index]; + value = opt.value; + if (!value && !('value' in opt)) + value = opt.text; + } + return [element.name, value]; + }, + + selectMany: function(element) { + var value = new Array(); + for (var i = 0; i < element.length; i++) { + var opt = element.options[i]; + if (opt.selected) { + var optValue = opt.value; + if (!optValue && !('value' in opt)) + optValue = opt.text; + value.push(optValue); } } return [element.name, value]; @@ -754,14 +1054,14 @@ } Form.Element.Observer = Class.create(); -Form.Element.Observer.prototype = (new Abstract.TimedObserver()).extend({ +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.Observer = Class.create(); -Form.Observer.prototype = (new Abstract.TimedObserver()).extend({ +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { getValue: function() { return Form.serialize(this.element); } @@ -826,14 +1126,14 @@ } Form.Element.EventObserver = Class.create(); -Form.Element.EventObserver.prototype = (new Abstract.EventObserver()).extend({ +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.Element.getValue(this.element); } }); Form.EventObserver = Class.create(); -Form.EventObserver.prototype = (new Abstract.EventObserver()).extend({ +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { getValue: function() { return Form.serialize(this.element); } @@ -880,6 +1180,7 @@ event.stopPropagation(); } else { event.returnValue = false; + event.cancelBubble = true; } }, @@ -920,7 +1221,7 @@ useCapture = useCapture || false; if (name == 'keypress' && - ((navigator.appVersion.indexOf('AppleWebKit') > 0) + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.attachEvent)) name = 'keydown'; @@ -932,7 +1233,7 @@ useCapture = useCapture || false; if (name == 'keypress' && - ((navigator.appVersion.indexOf('AppleWebKit') > 0) + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) || element.detachEvent)) name = 'keydown'; Added: trunk/public/robots.txt ============================================================================== --- trunk/public/robots.txt (added) +++ trunk/public/robots.txt Thu Oct 6 14:53:39 2005 @@ -1,0 +1,1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file Modified: trunk/script/breakpointer ============================================================================== --- trunk/script/breakpointer (original) +++ trunk/script/breakpointer Thu Oct 6 14:53:39 2005 @@ -1,4 +1,3 @@ -#!/usr/bin/ruby -require 'rubygems' -require_gem 'rails' -require 'breakpoint_client' +#!/usr/local/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' Modified: trunk/script/console ============================================================================== --- trunk/script/console (original) +++ trunk/script/console Thu Oct 6 14:53:39 2005 @@ -1,23 +1,3 @@ -#!/usr/bin/ruby -irb = RUBY_PLATFORM =~ /mswin32/ ? 'irb.bat' : 'irb' - -require 'optparse' -options = { :sandbox => false, :irb => irb } -OptionParser.new do |opt| - opt.on('-s', '--sandbox', 'Rollback database modifications on exit.') { |options[:sandbox]| } - opt.on("--irb=[#{irb}]", 'Invoke a different irb.') { |options[:irb]| } - opt.parse!(ARGV) -end - -libs = " -r irb/completion" -libs << " -r #{File.dirname(__FILE__)}/../config/environment" -libs << " -r console_sandbox" if options[:sandbox] - -ENV['RAILS_ENV'] = ARGV.first || 'development' -if options[:sandbox] - puts "Loading #{ENV['RAILS_ENV']} environment in sandbox." - puts "Any modifications you make will be rolled back on exit." -else - puts "Loading #{ENV['RAILS_ENV']} environment." -end -exec "#{options[:irb]} #{libs} --prompt-mode simple" +#!/usr/local/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' Modified: trunk/script/destroy ============================================================================== --- trunk/script/destroy (original) +++ trunk/script/destroy Thu Oct 6 14:53:39 2005 @@ -1,7 +1,3 @@ -#!/usr/bin/ruby -require File.dirname(__FILE__) + '/../config/environment' -require 'rails_generator' -require 'rails_generator/scripts/destroy' - -ARGV.shift if ['--help', '-h'].include?(ARGV[0]) -Rails::Generator::Scripts::Destroy.new.run(ARGV) +#!/usr/local/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' Modified: trunk/script/generate ============================================================================== --- trunk/script/generate (original) +++ trunk/script/generate Thu Oct 6 14:53:39 2005 @@ -1,7 +1,3 @@ -#!/usr/bin/ruby -require File.dirname(__FILE__) + '/../config/environment' -require 'rails_generator' -require 'rails_generator/scripts/generate' - -ARGV.shift if ['--help', '-h'].include?(ARGV[0]) -Rails::Generator::Scripts::Generate.new.run(ARGV) +#!/usr/local/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' Added: trunk/script/perform ============================================================================== --- trunk/script/perform (added) +++ trunk/script/perform Thu Oct 6 14:53:39 2005 @@ -1,0 +1,3 @@ +#!/usr/local/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/perform' Propchange: trunk/script/perform ------------------------------------------------------------------------------ svn:executable = * Added: trunk/script/process ============================================================================== --- trunk/script/process (added) +++ trunk/script/process Thu Oct 6 14:53:39 2005 @@ -1,0 +1,3 @@ +#!/usr/local/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/process' Propchange: trunk/script/process ------------------------------------------------------------------------------ svn:executable = * Modified: trunk/script/runner ============================================================================== --- trunk/script/runner (original) +++ trunk/script/runner Thu Oct 6 14:53:39 2005 @@ -1,29 +1,3 @@ -#!/usr/bin/ruby -require 'optparse' - -options = { :environment => "development" } - -ARGV.options do |opts| - script_name = File.basename($0) - opts.banner = "Usage: runner 'puts Person.find(1).name' [options]" - - opts.separator "" - - opts.on("-e", "--environment=name", String, - "Specifies the environment for the runner to operate under (test/development/production).", - "Default: development") { |options[:environment]| } - - opts.separator "" - - opts.on("-h", "--help", - "Show this help message.") { puts opts; exit } - - opts.parse! -end - -ENV["RAILS_ENV"] = options[:environment] - #!/usr/local/bin/ruby - -require File.dirname(__FILE__) + '/../config/environment' -eval(ARGV.first) +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' Modified: trunk/script/server ============================================================================== --- trunk/script/server (original) +++ trunk/script/server Thu Oct 6 14:53:39 2005 @@ -1,49 +1,3 @@ -#!/usr/bin/ruby - -require 'webrick' -require 'optparse' - -OPTIONS = { - :port => 3000, - :ip => "0.0.0.0", - :environment => "development", - :server_root => File.expand_path(File.dirname(__FILE__) + "/../public/"), - :server_type => WEBrick::SimpleServer -} - -ARGV.options do |opts| - script_name = File.basename($0) - opts.banner = "Usage: ruby #{script_name} [options]" - - opts.separator "" - - opts.on("-p", "--port=port", Integer, - "Runs Rails on the specified port.", - "Default: 3000") { |OPTIONS[:port]| } - opts.on("-b", "--binding=ip", String, - "Binds Rails to the specified ip.", - "Default: 0.0.0.0") { |OPTIONS[:ip]| } - opts.on("-e", "--environment=name", String, - "Specifies the environment to run this server under (test/development/production).", - "Default: development") { |OPTIONS[:environment]| } - opts.on("-d", "--daemon", - "Make Rails run as a Daemon (only works if fork is available -- meaning on *nix)." - ) { OPTIONS[:server_type] = WEBrick::Daemon } - - opts.separator "" - - opts.on("-h", "--help", - "Show this help message.") { puts opts; exit } - - opts.parse! -end - -ENV["RAILS_ENV"] = OPTIONS[:environment] -require File.dirname(__FILE__) + "/../config/environment" -require 'webrick_server' - -OPTIONS['working_directory'] = File.expand_path(RAILS_ROOT) - -puts "=> Rails application started on http://#{OPTIONS[:ip]}:#{OPTIONS[:port]}" -puts "=> Ctrl-C to shutdown server; call with --help for options" if OPTIONS[:server_type] == WEBrick::SimpleServer -DispatchServlet.dispatch(OPTIONS) +#!/usr/local/bin/ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' Propchange: trunk/vendor/ ------------------------------------------------------------------------------ --- svn:externals (original) +++ svn:externals Thu Oct 6 14:53:39 2005 @@ -1,1 +1,1 @@ -rails http://dev.rubyonrails.org/svn/rails/tags/rel_0-13-1 +rails http://dev.rubyonrails.org/svn/rails/trunk From johnwilger at gmail.com Thu Oct 6 15:18:19 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Thu, 06 Oct 2005 19:18:19 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r14 - in /trunk: app/views/main/dashboard.rhtml public/stylesheets/colors.css public/stylesheets/fonts.css public/stylesheets/layout.css public/stylesheets/main.css Message-ID: <200510061918.j96JIKmH008375@porkchop.wilger.local> Author: jwilger Date: Thu Oct 6 15:18:10 2005 New Revision: 14 Log: Added (mostly blank) CSS files and linked them into the template for MainController#dashboard Added: trunk/public/stylesheets/colors.css trunk/public/stylesheets/fonts.css trunk/public/stylesheets/layout.css trunk/public/stylesheets/main.css Modified: trunk/app/views/main/dashboard.rhtml Modified: trunk/app/views/main/dashboard.rhtml ============================================================================== --- trunk/app/views/main/dashboard.rhtml (original) +++ trunk/app/views/main/dashboard.rhtml Thu Oct 6 15:18:10 2005 @@ -5,6 +5,7 @@ eXPlainPMT » Dashboard + <%= stylesheet_link_tag 'main' %>

    -

    eXPlainPMT :: Dashboard

    +

    eXPlainPMT :: Dashboard

    Modified: trunk/public/stylesheets/layout.css ============================================================================== --- trunk/public/stylesheets/layout.css (original) +++ trunk/public/stylesheets/layout.css Thu Oct 6 15:33:27 2005 @@ -1,0 +1,27 @@ +body { + display: block; + margin: 0; + padding: 0; +} + +#Header { + margin: 0; + padding: 0; + height: 40px; + width: 100%; + border-bottom: 1px solid #000000; +} + +#CurrentUserInfo { + margin: 5px; + padding: 0; + float: right; + height: 30px; + text-align: right; +} + +#Header h1 { + margin: 5px; + padding: 0; + font-size: 30px; +} Modified: trunk/public/stylesheets/main.css ============================================================================== --- trunk/public/stylesheets/main.css (original) +++ trunk/public/stylesheets/main.css Thu Oct 6 15:33:27 2005 @@ -1,3 +1,3 @@ @import "fonts.css"; - at import "colors.css"; - at import "layout.css"; + at import "layout.css"; + at import "colors.css"; From johnwilger at gmail.com Thu Oct 6 15:40:30 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Thu, 06 Oct 2005 19:40:30 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r16 - /trunk/app/views/main/dashboard.rhtml Message-ID: <200510061940.j96JeUln008499@porkchop.wilger.local> Author: jwilger Date: Thu Oct 6 15:40:25 2005 New Revision: 16 Log: Minor reformatting of file. Modified: trunk/app/views/main/dashboard.rhtml Modified: trunk/app/views/main/dashboard.rhtml ============================================================================== --- trunk/app/views/main/dashboard.rhtml (original) +++ trunk/app/views/main/dashboard.rhtml Thu Oct 6 15:40:25 2005 @@ -13,7 +13,8 @@ Logged in as: <%= link_to 'John Wilger', :action => 'user_profile' %> (<%= link_to 'log out', :action => 'log_out' %>)
    -

    eXPlainPMT :: Dashboard

    +

    eXPlainPMT :: + Dashboard

    @@ -30,7 +31,8 @@

    My Story Cards

    - + @@ -42,8 +44,10 @@ - - + + @@ -51,19 +55,24 @@ - - + + - + - + - + @@ -82,12 +91,14 @@ - + - +
      
    <%= link_to 'Project Three', :action => 'project_overview' %> + <%= link_to 'Project Three', :action => 'project_overview' %> + <%= link_to 'SC4', :action => 'view_story_card' %> <%= link_to 'check out', :action => 'view_story_card' %> In Progress<%= link_to 'edit', :action => 'edit_story_card' %>
    <%= link_to 'Project Three', :action => 'project_overview' %> + <%= link_to 'Project Three', :action => 'project_overview' %> + <%= link_to 'SC19', :action => 'view_story_card' %><%= link_to 'redeem a coupon', :action => 'view_story_card' %><%= link_to 'redeem a coupon', :action => 'view_story_card' %> + Blocked <%= link_to 'release', :action => 'release_story_card' %> <%= link_to 'edit', :action => 'edit_story_card' %>
    + <%= link_to 'Project Two', :action => 'project_overview' %> <%= link_to 'SC1', :action => 'view_story_card' %><%= link_to 'create new work-item', :action => 'view_story_card' %><%= link_to 'create new work-item', + :action => 'view_story_card' %> Complete <%= link_to 'release', :action => 'release_story_card' %> <%= link_to 'edit', :action => 'edit_story_card' %>
    <%= link_to 'Project Three', :action => 'project_overview' %><%= link_to 'Version 1.6 Release', :action => 'view_milestone' %><%= link_to 'Version 1.6 Release', :action => 'view_milestone' %> + 10/31/2005
    <%= link_to 'Project Two', :action => 'project_overview' %><%= link_to 'Project Post-Mortem', :action => 'view_milestone' %><%= link_to 'Project Post-Mortem', :action => 'view_milestone' %> + 10/15/2005
    @@ -104,24 +115,30 @@ Who? - <%= image_tag 'story_card_icon_small', :alt => 'SC', :size => '10x10' %> + <%= image_tag 'story_card_icon_small', :alt => 'SC', + :size => '10x10' %> <%= link_to 'Project Four', :action => 'project_overview' %> 9/27/2005 12:33pm - New story card added: "<%= link_to 'calculate probability', :action => 'view_story_card' %>" + New story card added: "<%= link_to 'calculate probability', + :action => 'view_story_card' %>" <%= link_to 'J. Brown', :action => 'user_profile' %> - <%= image_tag 'feature_icon_small', :alt => 'FE', :size => '10x10' %> + <%= image_tag 'feature_icon_small', :alt => 'FE', + :size => '10x10' %> <%= link_to 'Project Three', :action => 'project_overview' %> 9/27/2005 09:24am - Changed feature: "<%= link_to 'Secure Checkout', :action => 'view_feature' %>" + Changed feature: "<%= link_to 'Secure Checkout', + :action => 'view_feature' %>" <%= link_to 'M. Rogers', :action => 'user_profile' %> - <%= image_tag 'defect_icon_small', :alt => 'DF', :size => '10x10' %> + <%= image_tag 'defect_icon_small', :alt => 'DF', + :size => '10x10' %> <%= link_to 'Project Two', :action => 'project_overview' %> 9/26/2005 10:55pm - New defect reported: "<%= link_to 'DF82', :action => 'view_defect' %>" + New defect reported: "<%= link_to 'DF82', + :action => 'view_defect' %>" <%= link_to 'D. Vader', :action => 'user_profile' %> From johnwilger at gmail.com Fri Oct 7 09:30:39 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Fri, 07 Oct 2005 13:30:39 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r17 - /trunk/lib/tasks/test_aliases.rake Message-ID: <200510071330.j97DUfK0010725@porkchop.wilger.local> Author: jwilger Date: Fri Oct 7 09:30:22 2005 New Revision: 17 Log: Added Rake task aliases: tu => test_units tf => test_functional Added: trunk/lib/tasks/test_aliases.rake Added: trunk/lib/tasks/test_aliases.rake ============================================================================== --- trunk/lib/tasks/test_aliases.rake (added) +++ trunk/lib/tasks/test_aliases.rake Fri Oct 7 09:30:22 2005 @@ -1,0 +1,3 @@ +task :tu => :test_units + +task :tf => :test_functional From johnwilger at gmail.com Fri Oct 7 10:06:30 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Fri, 07 Oct 2005 14:06:30 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r18 - in /trunk/public/stylesheets: layout.css main.css undohtml.css Message-ID: <200510071406.j97E6VCG011023@porkchop.wilger.local> Author: jwilger Date: Fri Oct 7 10:06:17 2005 New Revision: 18 Log: Worked a bit more on the layout of Main#dashboard Added: trunk/public/stylesheets/undohtml.css Modified: trunk/public/stylesheets/layout.css trunk/public/stylesheets/main.css Modified: trunk/public/stylesheets/layout.css ============================================================================== --- trunk/public/stylesheets/layout.css (original) +++ trunk/public/stylesheets/layout.css Fri Oct 7 10:06:17 2005 @@ -1,27 +1,35 @@ body { - display: block; - margin: 0; - padding: 0; + display: block; + margin: 0; + padding: 0; } #Header { - margin: 0; - padding: 0; - height: 40px; - width: 100%; - border-bottom: 1px solid #000000; + margin: 0; + padding: 0; + height: 40px; + width: 100%; + background-color: #AAAAAA; } #CurrentUserInfo { - margin: 5px; - padding: 0; - float: right; - height: 30px; - text-align: right; + margin: 5px; + padding: 0; + float: right; + height: 30px; + text-align: right; } #Header h1 { - margin: 5px; - padding: 0; - font-size: 30px; -} + margin: 0; + padding: 5px; + font-size: 30px; +} + +#MyProjects { + float: right; + width: 15em; + margin: 0 0 10px 10px; + padding: 5px; + background-color: #CCCCCC; +} Modified: trunk/public/stylesheets/main.css ============================================================================== --- trunk/public/stylesheets/main.css (original) +++ trunk/public/stylesheets/main.css Fri Oct 7 10:06:17 2005 @@ -1,3 +1,4 @@ + at import "undohtml.css"; @import "fonts.css"; @import "layout.css"; - at import "colors.css"; + at import "colors.css"; Added: trunk/public/stylesheets/undohtml.css ============================================================================== --- trunk/public/stylesheets/undohtml.css (added) +++ trunk/public/stylesheets/undohtml.css Fri Oct 7 10:06:17 2005 @@ -1,0 +1,35 @@ +/* undohtml.css */ +/* (CC) 2004 Tantek Celik. Some Rights Reserved. */ +/* http://creativecommons.org/licenses/by/2.0 */ +/* This style sheet is licensed under a Creative Commons License. */ + +/* Purpose: undo some of the default styling of common (X)HTML browsers */ + + +/* link underlines tend to make hypertext less readable, + because underlines obscure the shapes of the lower halves of words */ +:link,:visited { text-decoration:none } + +/* no list-markers by default, since lists are used more often for semantics */ +ul,ol { list-style:none } + +/* avoid browser default inconsistent heading font-sizes */ +/* and pre/code too */ +h1,h2,h3,h4,h5,h6,pre,code { font-size:1em; } + +/* remove the inconsistent (among browsers) default ul,ol padding or margin */ +/* the default spacing on headings does not match nor align with + normal interline spacing at all, so let's get rid of it. */ +/* zero out the spacing around pre, form, body, html, p, blockquote as well */ +/* form elements are oddly inconsistent, and not quite CSS emulatable. */ +/* nonetheless strip their margin and padding as well */ +ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,body,html,p,blockquote,fieldset,input +{ margin:0; padding:0 } + +/* whoever thought blue linked image borders were a good idea? */ +a img,:link img,:visited img { border:none } + +/* de-italicize address */ +address { font-style:normal } + +/* more varnish stripping as necessary... */ From johnwilger at gmail.com Fri Oct 7 13:51:09 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Fri, 07 Oct 2005 17:51:09 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r19 - in /trunk: app/views/main/dashboard.rhtml public/stylesheets/layout.css Message-ID: <200510071751.j97HpAOR011603@porkchop.wilger.local> Author: jwilger Date: Fri Oct 7 13:50:50 2005 New Revision: 19 Log: more layout styling for MainController#dashboard Modified: trunk/app/views/main/dashboard.rhtml trunk/public/stylesheets/layout.css Modified: trunk/app/views/main/dashboard.rhtml ============================================================================== --- trunk/app/views/main/dashboard.rhtml (original) +++ trunk/app/views/main/dashboard.rhtml Fri Oct 7 13:50:50 2005 @@ -31,9 +31,13 @@

    My Story Cards

    - + + + Modified: trunk/public/stylesheets/layout.css ============================================================================== --- trunk/public/stylesheets/layout.css (original) +++ trunk/public/stylesheets/layout.css Fri Oct 7 13:50:50 2005 @@ -2,6 +2,10 @@ display: block; margin: 0; padding: 0; +} + +h2 { + font-size: 1.1em; } #Header { @@ -29,7 +33,22 @@ #MyProjects { float: right; width: 15em; - margin: 0 0 10px 10px; + margin: 0; padding: 5px; background-color: #CCCCCC; } + +#MyProjects h2 { + text-align: center; +} + +#MyProjects li a { + display: block; + text-decoration: none; + margin: 0; + padding: 0.25em 1em; +} + +#MyProjects li a:hover { + background-color: #EEEEEE; +} From edavis10 at gmail.com Sat Oct 8 03:15:28 2005 From: edavis10 at gmail.com (edavis10@gmail.com) Date: Sat, 08 Oct 2005 07:15:28 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r20 - in /trunk: app/controllers/ app/helpers/ app/models/ app/views/layouts/ app/views/users/ db/ public/stylesheets/ test/fixtures/ test/functional/ test/unit/ Message-ID: <200510080715.j987FTQg013437@porkchop.wilger.local> Author: edavis Date: Sat Oct 8 03:14:53 2005 New Revision: 20 Log: For Stoycard #2, Manage user accounts Added scaffolding for the User table and users controller Added unit tests for User model Added: trunk/app/controllers/users_controller.rb trunk/app/helpers/users_helper.rb trunk/app/models/user.rb trunk/app/views/layouts/users.rhtml trunk/app/views/users/ trunk/app/views/users/_form.rhtml trunk/app/views/users/edit.rhtml trunk/app/views/users/list.rhtml trunk/app/views/users/new.rhtml trunk/app/views/users/show.rhtml trunk/db/development.db (with props) trunk/db/test.db (with props) trunk/public/stylesheets/scaffold.css trunk/test/fixtures/users.yml trunk/test/functional/users_controller_test.rb trunk/test/unit/user_test.rb Added: trunk/app/controllers/users_controller.rb ============================================================================== --- trunk/app/controllers/users_controller.rb (added) +++ trunk/app/controllers/users_controller.rb Sat Oct 8 03:14:53 2005 @@ -1,0 +1,47 @@ +class UsersController < ApplicationController + def index + list + render :action => 'list' + end + + def list + @user_pages, @users = paginate :user, :per_page => 10 + end + + def show + @user = User.find(params[:id]) + end + + def new + @user = User.new + end + + def create + @user = User.new(params[:user]) + if @user.save + flash[:notice] = 'User was successfully created.' + redirect_to :action => 'list' + else + render :action => 'new' + end + end + + def edit + @user = User.find(params[:id]) + end + + def update + @user = User.find(params[:id]) + if @user.update_attributes(params[:user]) + flash[:notice] = 'User was successfully updated.' + redirect_to :action => 'show', :id => @user + else + render :action => 'edit' + end + end + + def destroy + User.find(params[:id]).destroy + redirect_to :action => 'list' + end +end Added: trunk/app/helpers/users_helper.rb ============================================================================== --- trunk/app/helpers/users_helper.rb (added) +++ trunk/app/helpers/users_helper.rb Sat Oct 8 03:14:53 2005 @@ -1,0 +1,2 @@ +module UsersHelper +end Added: trunk/app/models/user.rb ============================================================================== --- trunk/app/models/user.rb (added) +++ trunk/app/models/user.rb Sat Oct 8 03:14:53 2005 @@ -1,0 +1,2 @@ +class User < ActiveRecord::Base +end Added: trunk/app/views/layouts/users.rhtml ============================================================================== --- trunk/app/views/layouts/users.rhtml (added) +++ trunk/app/views/layouts/users.rhtml Sat Oct 8 03:14:53 2005 @@ -1,0 +1,13 @@ + + + Users: <%= controller.action_name %> + <%= stylesheet_link_tag 'scaffold' %> + + + +

    <%= flash[:notice] %>

    + +<%= @content_for_layout %> + + + Added: trunk/app/views/users/_form.rhtml ============================================================================== --- trunk/app/views/users/_form.rhtml (added) +++ trunk/app/views/users/_form.rhtml Sat Oct 8 03:14:53 2005 @@ -1,0 +1,19 @@ +<%= error_messages_for 'user' %> + + +


    +<%= text_field 'user', 'admin' %>

    + +


    +<%= text_field 'user', 'login' %>

    + +


    +<%= password_field 'user', 'password' %>

    + +


    +<%= text_area 'user', 'email' %>

    + +


    +<%= text_area 'user', 'name' %>

    + + Added: trunk/app/views/users/edit.rhtml ============================================================================== --- trunk/app/views/users/edit.rhtml (added) +++ trunk/app/views/users/edit.rhtml Sat Oct 8 03:14:53 2005 @@ -1,0 +1,9 @@ +

    Editing user

    + +<%= start_form_tag :action => 'update', :id => @user %> + <%= render :partial => 'form' %> + <%= submit_tag 'Edit' %> +<%= end_form_tag %> + +<%= link_to 'Show', :action => 'show', :id => @user %> | +<%= link_to 'Back', :action => 'list' %> Added: trunk/app/views/users/list.rhtml ============================================================================== --- trunk/app/views/users/list.rhtml (added) +++ trunk/app/views/users/list.rhtml Sat Oct 8 03:14:53 2005 @@ -1,0 +1,27 @@ +

    Listing users

    + +
    + +
      Project
    + + <% for column in User.content_columns %> + + <% end %> + + +<% for user in @users %> + + <% for column in User.content_columns %> + + <% end %> + + + + +<% end %> +
    <%= column.human_name %>
    <%=h user.send(column.name) %><%= link_to 'Show', :action => 'show', :id => user %><%= link_to 'Edit', :action => 'edit', :id => user %><%= link_to 'Destroy', {:action => 'destroy', :id => user}, :confirm => 'Are you sure?' %>
    + +<%= link_to 'Previous page', { :page => @user_pages.current.previous } if @user_pages.current.previous %> +<%= link_to 'Next page', { :page => @user_pages.current.next } if @user_pages.current.next %> + +
    + +<%= link_to 'New user', :action => 'new' %> Added: trunk/app/views/users/new.rhtml ============================================================================== --- trunk/app/views/users/new.rhtml (added) +++ trunk/app/views/users/new.rhtml Sat Oct 8 03:14:53 2005 @@ -1,0 +1,8 @@ +

    New user

    + +<%= start_form_tag :action => 'create' %> + <%= render :partial => 'form' %> + <%= submit_tag "Create" %> +<%= end_form_tag %> + +<%= link_to 'Back', :action => 'list' %> Added: trunk/app/views/users/show.rhtml ============================================================================== --- trunk/app/views/users/show.rhtml (added) +++ trunk/app/views/users/show.rhtml Sat Oct 8 03:14:53 2005 @@ -1,0 +1,8 @@ +<% for column in User.content_columns %> +

    + <%= column.human_name %>: <%=h @user.send(column.name) %> +

    +<% end %> + +<%= link_to 'Edit', :action => 'edit', :id => @user %> | +<%= link_to 'Back', :action => 'list' %> Added: trunk/db/development.db ============================================================================== Binary file - no diff available. Propchange: trunk/db/development.db ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream Added: trunk/db/test.db ============================================================================== Binary file - no diff available. Propchange: trunk/db/test.db ------------------------------------------------------------------------------ svn:mime-type = application/octet-stream Added: trunk/public/stylesheets/scaffold.css ============================================================================== --- trunk/public/stylesheets/scaffold.css (added) +++ trunk/public/stylesheets/scaffold.css Sat Oct 8 03:14:53 2005 @@ -1,0 +1,74 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#ErrorExplanation { + width: 400px; + border: 2px solid 'red'; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#ErrorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#ErrorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#ErrorExplanation ul li { + font-size: 12px; + list-style: square; +} + +div.uploadStatus { + margin: 5px; +} + +div.progressBar { + margin: 5px; +} + +div.progressBar div.border { + background-color: #fff; + border: 1px solid grey; + width: 100%; +} + +div.progressBar div.background { + background-color: #333; + height: 18px; + width: 0%; +} + Added: trunk/test/fixtures/users.yml ============================================================================== --- trunk/test/fixtures/users.yml (added) +++ trunk/test/fixtures/users.yml Sat Oct 8 03:14:53 2005 @@ -1,0 +1,15 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +admin_user: + id: 1 + admin: 1 + login: admin + password: admin + email: admin at localhost + name: Admin +normal_user: + id: 2 + admin: 0 + login: user + password: user + email: user at localhost + name: User Added: trunk/test/functional/users_controller_test.rb ============================================================================== --- trunk/test/functional/users_controller_test.rb (added) +++ trunk/test/functional/users_controller_test.rb Sat Oct 8 03:14:53 2005 @@ -1,0 +1,88 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'users_controller' + +# Re-raise errors caught by the controller. +class UsersController; def rescue_action(e) raise e end; end + +class UsersControllerTest < Test::Unit::TestCase + fixtures :users + + def setup + @controller = UsersController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + def test_index + get :index + assert_response :success + assert_template 'list' + end + + def test_list + get :list + + assert_response :success + assert_template 'list' + + assert_not_nil assigns(:users) + end + + def test_show + get :show, :id => 1 + + assert_response :success + assert_template 'show' + + assert_not_nil assigns(:user) + assert assigns(:user).valid? + end + + def test_new + get :new + + assert_response :success + assert_template 'new' + + assert_not_nil assigns(:user) + end + + def test_create + num_users = User.count + + post :create, :user => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_users + 1, User.count + end + + def test_edit + get :edit, :id => 1 + + assert_response :success + assert_template 'edit' + + assert_not_nil assigns(:user) + assert assigns(:user).valid? + end + + def test_update + post :update, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'show', :id => 1 + end + + def test_destroy + assert_not_nil User.find(1) + + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + User.find(1) + } + end +end Added: trunk/test/unit/user_test.rb ============================================================================== --- trunk/test/unit/user_test.rb (added) +++ trunk/test/unit/user_test.rb Sat Oct 8 03:14:53 2005 @@ -1,0 +1,49 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class UserTest < Test::Unit::TestCase + fixtures :users + + def setup + @admin = User.find(1) + @user = User.find(2) + end + + # Replace this with your real tests. + def test_truth + assert_kind_of User, @user + end + + def test_create_user + new_user = User.new + new_user.id = 3 + new_user.admin = 0 + new_user.login = "user_three" + new_user.password = "user_three" + new_user.email = "userthree at localhost" + new_user.name = "User Three" + assert new_user.save + assert_equal 3, User.find(3).id + assert_equal "User Three", User.find(3).name + end + + def test_read_user + assert_equal "admin", @admin.login + assert_equal 1, @admin.admin + assert_equal "Admin", @admin.name + end + + def test_update_user + assert_equal "user", @user.login + assert_equal "User", @user.name + @user.login = "changed" + @user.name = "Who's name?" + assert @user.save + assert "changed", @user.login + assert "Who's name?", @user.name + end + + def test_destroy_user + @user.destroy + assert_raise(ActiveRecord::RecordNotFound) { User.find(@user.id) } + end +end From edavis10 at gmail.com Sat Oct 8 18:29:52 2005 From: edavis10 at gmail.com (edavis10@gmail.com) Date: Sat, 08 Oct 2005 22:29:52 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r21 - in /trunk: app/controllers/ app/models/ app/views/layouts/ app/views/users/ db/ test/fixtures/ test/functional/ test/unit/ Message-ID: <200510082229.j98MTr0b016173@porkchop.wilger.local> Author: edavis Date: Sat Oct 8 18:29:30 2005 New Revision: 21 Log: Completed SC 2 and SC 21 Added schema_info to the SQLite3 db. Added extra users and admins to the db. Added fixtures for the User Model Full unit and functional tests to cover the logic branches in Users. Added functional tests to MainController to test the logic for an admin login/user login/ nologin. Added ApplicationController.check_authentication to be used as a filter for logins. Added ApplicationController.require_admin to be used as a filter to restrict user access to certain actions. Added checking in UserController.destroy to prevent an admin from deleting their own account and also from deleting the last admin account (though it is redendent). Added UserController.authenticate to use the model to authenticate a login. Added UserController.logout to logout the user. Added UserController.require_admin to override the ApplicationController method. This will allow a user to change their own settings without requiring admin rights (taken from eXPlainPMT 1.3) Added a before_filter in the MainController to check that the user is logged in. Created a simple login page with the login form. Created a simple no_admin.rhtml page to display as an error page. Added the error and status flash[] to the layout for UserController Probably a lot of other minor stuff I forgot. Rake Stats: Code LOC: 124 Test LOC: 270 Code to Test Ratio: 1:2.2 Added: trunk/app/views/users/login.rhtml trunk/app/views/users/no_admin.rhtml Modified: trunk/app/controllers/application.rb trunk/app/controllers/main_controller.rb trunk/app/controllers/users_controller.rb trunk/app/models/user.rb trunk/app/views/layouts/users.rhtml trunk/db/development.db trunk/db/test.db trunk/test/fixtures/users.yml trunk/test/functional/main_controller_test.rb trunk/test/functional/users_controller_test.rb trunk/test/unit/user_test.rb Modified: trunk/app/controllers/application.rb ============================================================================== --- trunk/app/controllers/application.rb (original) +++ trunk/app/controllers/application.rb Sat Oct 8 18:29:30 2005 @@ -1,4 +1,27 @@ -# The filters added to this controller will be run for all controllers in the application. -# Likewise will all the methods added be available for all controllers. +# The filters added to this controller will be run for all controllers in the +# application. Likewise will all the methods added be available for all +# controllers. class ApplicationController < ActionController::Base + + + + # Method used as a before_filter to restrict access to certain actions. + def require_admin + unless @session[:current_user_id].admin? + flash[:error] = "You must be logged in as an administrator to perform"+ + " this action" + redirect_to :controller => 'users', :action => 'no_admin' + return false + end + end + + def check_authentication + unless @session[:current_user_id].kind_of?(User) + + @session[:return_to] = @request.request_uri + flash[:status] = "Please log in, and we'll send you right along." + redirect_to :controller => 'users', :action => 'login' + return false + end + end end Modified: trunk/app/controllers/main_controller.rb ============================================================================== --- trunk/app/controllers/main_controller.rb (original) +++ trunk/app/controllers/main_controller.rb Sat Oct 8 18:29:30 2005 @@ -1,4 +1,5 @@ class MainController < ApplicationController + before_filter :check_authentication def dashboard end end Modified: trunk/app/controllers/users_controller.rb ============================================================================== --- trunk/app/controllers/users_controller.rb (original) +++ trunk/app/controllers/users_controller.rb Sat Oct 8 18:29:30 2005 @@ -1,4 +1,9 @@ class UsersController < ApplicationController + before_filter :check_authentication, :except => [:login, :authenticate] + before_filter :require_admin, :except => [:login, :logout, :authenticate, + :no_admin] + + def index list render :action => 'list' @@ -11,7 +16,7 @@ def show @user = User.find(params[:id]) end - + def new @user = User.new end @@ -41,7 +46,57 @@ end def destroy - User.find(params[:id]).destroy + @user = User.find(@params[:id]) + if @session[ :current_user_id ] == @user + flash[ :error ] = 'You may not delete your own account' + else + if User.count('admin = 1') == 1 + flash[ :error ] = 'You may not delete the last admin account' + else + User.find(@params[:id]).destroy + flash[ :notice ] = 'User has been deleted' + end + end redirect_to :action => 'list' end + + def login + end + + def authenticate + if @session[ :current_user_id ] = User.authenticate(@params[ :login ], + @params[ :password]) + if @session[:return_to] + redirect_to_path @session[:return_to] + @session[:return_to] = nil + else + redirect_to :controller => 'main', :action => 'dashboard' + end + else + flash[ :error ] = 'You entered an invalid username and/or password.' + redirect_to :controller => 'users', :action => 'login' + end + end + + def logout + @session[ :current_user_id ] = nil + redirect_to :controller => 'users', :action => 'login' + flash[ :notice ] = 'You have been logged out' + end + + def no_admin + end + + protected + + # Overrides the ApplicationController#require_admin method so that + # a non-admin user can edit their own account details. + def require_admin + case action_name + when 'edit','update', 'show' + super if @params['id'].to_i != @session[:current_user_id].id + else + super + end + end end Modified: trunk/app/models/user.rb ============================================================================== --- trunk/app/models/user.rb (original) +++ trunk/app/models/user.rb Sat Oct 8 18:29:30 2005 @@ -1,2 +1,12 @@ class User < ActiveRecord::Base + + + class << self + def authenticate( login, password ) + unless login.nil? || password.nil? + find :first, :conditions => [ 'login = ? AND password = ?', + login, password ] + end + end + end end Modified: trunk/app/views/layouts/users.rhtml ============================================================================== --- trunk/app/views/layouts/users.rhtml (original) +++ trunk/app/views/layouts/users.rhtml Sat Oct 8 18:29:30 2005 @@ -6,6 +6,8 @@

    <%= flash[:notice] %>

    +

    <%= flash[:error] %>

    +

    <%= flash[:status] %>

    <%= @content_for_layout %> Added: trunk/app/views/users/login.rhtml ============================================================================== --- trunk/app/views/users/login.rhtml (added) +++ trunk/app/views/users/login.rhtml Sat Oct 8 18:29:30 2005 @@ -1,0 +1,10 @@ +

    Used to login

    +<%= start_form_tag :action => 'authenticate' %> +


    + + +


    + + + <%= submit_tag "Login" %> +<%= end_form_tag %> Added: trunk/app/views/users/no_admin.rhtml ============================================================================== --- trunk/app/views/users/no_admin.rhtml (added) +++ trunk/app/views/users/no_admin.rhtml Sat Oct 8 18:29:30 2005 @@ -1,0 +1,1 @@ +

    Sorry, you need to be an admin

    Modified: trunk/db/development.db ============================================================================== Binary files - no diff available. Modified: trunk/db/test.db ============================================================================== Binary files - no diff available. Modified: trunk/test/fixtures/users.yml ============================================================================== --- trunk/test/fixtures/users.yml (original) +++ trunk/test/fixtures/users.yml Sat Oct 8 18:29:30 2005 @@ -6,10 +6,19 @@ password: admin email: admin at localhost name: Admin + normal_user: id: 2 admin: 0 login: user password: user email: user at localhost - name: User + name: User + +admin_user_two: + id: 3 + admin: 1 + login: anotheradmin + password: admin2 + email: admin at localhost + name: Admin Deux Modified: trunk/test/functional/main_controller_test.rb ============================================================================== --- trunk/test/functional/main_controller_test.rb (original) +++ trunk/test/functional/main_controller_test.rb Sat Oct 8 18:29:30 2005 @@ -5,15 +5,35 @@ class MainController; def rescue_action(e) raise e end; end class MainControllerTest < Test::Unit::TestCase + fixtures :users + def setup @controller = MainController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new + @admin = User.find(1) + @user = User.find(2) end - - def test_dashboard + + def test_dashboard_for_user + @request.session[ :current_user_id ] = @user get :dashboard assert_response :success assert_template 'dashboard' end + + def test_dashboard_for_admin + @request.session[ :current_user_id ] = @admin + get :dashboard + assert_response :success + assert_template 'dashboard' + end + + def test_dashboard_for_not_logged_in + get :dashboard + assert_response :redirect + assert_redirected_to :controller => 'users', :action => 'login' + assert_equal "Please log in, and we'll send you right along.", + flash[ :status ] + end end Modified: trunk/test/functional/users_controller_test.rb ============================================================================== --- trunk/test/functional/users_controller_test.rb (original) +++ trunk/test/functional/users_controller_test.rb Sat Oct 8 18:29:30 2005 @@ -11,15 +11,28 @@ @controller = UsersController.new @request = ActionController::TestRequest.new @response = ActionController::TestResponse.new - end - - def test_index + @admin = User.find(1) + @user = User.find(2) + end + + def test_index_for_admin + @request.session[ :current_user_id ] = @admin get :index assert_response :success assert_template 'list' end + def test_index_for_user + @request.session[ :current_user_id ] = @user + get :index + assert_response :redirect + assert_redirected_to :action => 'no_admin' + assert_equal 'You must be logged in as an administrator to perform'+ + ' this action', flash[ :error ] + end + def test_list + @request.session[ :current_user_id ] = @admin get :list assert_response :success @@ -28,61 +41,193 @@ assert_not_nil assigns(:users) end - def test_show + def test_show_to_user + @request.session[ :current_user_id ] = @user get :show, :id => 1 - + assert_response :redirect + assert_redirected_to :action => 'no_admin' + assert_equal 'You must be logged in as an administrator to perform'+ + ' this action', flash[ :error ] + end + + def test_show_self_user + @request.session[ :current_user_id ] = @user + get :show, :id => 2 assert_response :success assert_template 'show' - + assert_not_nil assigns(:user) + assert assigns(:user).valid? + end + + def test_show_to_admin + @request.session[ :current_user_id ] = @admin + get :show, :id => 1 + assert_response :success + assert_template 'show' + assert_not_nil assigns(:user) + assert assigns(:user).valid? + end + + def test_new_using_admin + @request.session[ :current_user_id ] = @admin + get :new + assert_response :success + assert_template 'new' + assert_not_nil assigns(:user) + end + + def test_new_using_user + @request.session[ :current_user_id ] = @user + get :new + assert_response :redirect + assert_redirected_to :action => 'no_admin' + assert_equal 'You must be logged in as an administrator to perform'+ + ' this action', flash[ :error ] + end + + def test_create + @request.session[ :current_user_id ] = @admin + + num_users = User.count + + post :create, :user => {} + + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_equal num_users + 1, User.count + end + + def test_edit_from_user + @request.session[ :current_user_id ] = @user + get :edit, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'no_admin' + assert_equal 'You must be logged in as an administrator to perform'+ + ' this action', flash[ :error ] + end + + def test_edit_from_admin + @request.session[ :current_user_id ] = @admin + get :edit, :id => 1 + assert_response :success + assert_template 'edit' assert_not_nil assigns(:user) assert assigns(:user).valid? end - - def test_new - get :new - - assert_response :success - assert_template 'new' - - assert_not_nil assigns(:user) - end - - def test_create - num_users = User.count - - post :create, :user => {} - - assert_response :redirect - assert_redirected_to :action => 'list' - - assert_equal num_users + 1, User.count - end - - def test_edit - get :edit, :id => 1 - + + def test_edit_self_from_user + @request.session[ :current_user_id ] = @user + get :edit, :id => 2 assert_response :success assert_template 'edit' - assert_not_nil assigns(:user) assert assigns(:user).valid? end def test_update + @request.session[ :current_user_id ] = @admin post :update, :id => 1 assert_response :redirect assert_redirected_to :action => 'show', :id => 1 end def test_destroy + @request.session[ :current_user_id ] = @admin + + assert_not_nil User.find(3) + + post :destroy, :id => 3 + assert_response :redirect + assert_redirected_to :action => 'list' + + assert_raise(ActiveRecord::RecordNotFound) { + User.find(3) + } + end + + def test_destroy_self_from_user + @request.session[ :current_user_id ] = @user + assert_not_nil User.find(2) + post :destroy, :id => 2 + assert_response :redirect + assert_redirected_to :action => 'no_admin' + assert_equal 'You must be logged in as an administrator to perform'+ + ' this action', flash[ :error ] + assert_equal "user", User.find(2).login + end + + def test_destroy_self_from_admin + @request.session[ :current_user_id ] = @admin assert_not_nil User.find(1) - post :destroy, :id => 1 assert_response :redirect assert_redirected_to :action => 'list' - + assert_equal 'You may not delete your own account', flash[ :error ] + assert_equal "admin", User.find(1).login + end + + def test_destroy_last_admin + # This should never happen as in order to destroy the last admin, an admin + # must be logged in and destroy themselves. This would trigger the branch + # of any user destroying themselves. But it is included here for + # completeness. + @request.session[ :current_user_id ] = @admin + # First remove the first admin of the two + assert_not_nil User.find(3) + post :destroy, :id => 3 + assert_response :redirect + assert_redirected_to :action => 'list' + # Check that the first admin was removed assert_raise(ActiveRecord::RecordNotFound) { - User.find(1) + User.find(3) } + # Now try to remove the final admin + assert_not_nil User.find(1) + post :destroy, :id => 1 + assert_response :redirect + assert_redirected_to :action => 'list' + # Here is where it hits the Destroying of their own account. The next + # commented code would then catch the deletion of the last admin account + # if users were allowed to delete themselves. + assert_equal 'You may not delete your own account', flash[ :error ] + #assert_equal 'You may not delete the last admin account', flash[ :error ] + assert_equal "admin", User.find(1).login + end + + def test_login + get :login + assert_template 'login' + end + + def test_authenticate + post :authenticate, 'login' => @admin.login, 'password' => @admin.password + assert_equal @admin, session[ :current_user_id ] + assert_response :redirect + assert_redirected_to :controller => 'main', :action => 'dashboard' + end + + def test_fail_authenticate + post :authenticate, 'login' => @admin.login, 'password' => 'foo' + assert_nil session[ :current_user_id ] + assert_response :redirect + assert_redirected_to :controller => 'users', :action => 'login' + assert_equal 'You entered an invalid username and/or password.', + flash[ :error] + end + + def test_logout + @request.session[ :current_user_id ] = @user + get :logout + assert_nil session[ :current_user_id ] + assert_response :redirect + assert_redirected_to :controller => 'users', :action => 'login' + assert_equal 'You have been logged out', flash[ :notice ] + end + + def test_no_admin_error + @request.session[ :current_user_id ] = @user + get :no_admin + assert_template 'no_admin' end end Modified: trunk/test/unit/user_test.rb ============================================================================== --- trunk/test/unit/user_test.rb (original) +++ trunk/test/unit/user_test.rb Sat Oct 8 18:29:30 2005 @@ -15,15 +15,15 @@ def test_create_user new_user = User.new - new_user.id = 3 + new_user.id = 4 new_user.admin = 0 - new_user.login = "user_three" - new_user.password = "user_three" - new_user.email = "userthree at localhost" - new_user.name = "User Three" + new_user.login = "user_four" + new_user.password = "user_four" + new_user.email = "userfour at localhost" + new_user.name = "User Four" assert new_user.save - assert_equal 3, User.find(3).id - assert_equal "User Three", User.find(3).name + assert_equal 4, User.find(4).id + assert_equal "User Four", User.find(4).name end def test_read_user @@ -46,4 +46,9 @@ @user.destroy assert_raise(ActiveRecord::RecordNotFound) { User.find(@user.id) } end + + def test_authenticate + assert_equal @admin, User.authenticate( @admin.login, 'admin' ) + assert_equal @user, User.authenticate( @user.login, 'user' ) + end end From edavis10 at gmail.com Sat Oct 8 18:46:15 2005 From: edavis10 at gmail.com (Eric Davis) Date: Sat, 8 Oct 2005 15:46:15 -0700 Subject: [eXPlainPMT Developers] r21 - Works but still needs some refactoring Message-ID: <1A018B62-118D-4166-A728-FE8CF5E55E95@gmail.com> I just checked in r21 which finished SC2 and SC21 but it still needs a bit of cleaning and refactoring. I have done as much as I could but I need to step away to get a new perspective on it. For the development.db SQLite database the logins are: admin/admin user/user admintwo/admin2 user_two/user_two but you can see that from reading the db. Laters all, I will try to knock out another story later tonight and finish the refactoring then. Eric Davis edavis10 at gmail.com | www.theadmin.org =-=-= v2sw5Yhw5ln3pr6Ock4ma3u6MLw3m7l6DiJe6t3b8Oen5g5Ma21s5Mr2p6 hackerkey.com From johnwilger at gmail.com Sun Oct 9 15:50:57 2005 From: johnwilger at gmail.com (John Wilger) Date: Sun, 9 Oct 2005 15:50:57 -0400 Subject: [eXPlainPMT Developers] Fwd: explainPMT for PostgreSQL In-Reply-To: <1e2f945a0510091232v224cc22fga7ada7292d6fd39a@mail.gmail.com> References: <1e2f945a0510091232v224cc22fga7ada7292d6fd39a@mail.gmail.com> Message-ID: ---------- Forwarded message ---------- From: Larry Wright Date: Oct 9, 2005 3:32 PM Subject: explainPMT for PostgreSQL To: johnwilger at gmail.com I downloaded eXplainPMT the other day to try it out (very nice so far), but I don't have MySQL installed. I took the schema and converted it to PostgreSQL, and it seems to work fine. I've attached the updated schema, in case someone else would like it. Thanks for a great tool. Larry -- http://www.approachingnormal.com -- Regards, John Wilger ----------- 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 -------------- next part -------------- A non-text attachment was scrubbed... Name: schema.postgres.sql Type: text/x-sql Size: 1114 bytes Desc: not available Url : http://rubyforge.org/pipermail/explainpmt-developers/attachments/20051009/cf981f84/schema.postgres.bin From johnwilger at gmail.com Sun Oct 9 15:57:07 2005 From: johnwilger at gmail.com (John Wilger) Date: Sun, 9 Oct 2005 15:57:07 -0400 Subject: [eXPlainPMT Developers] r21 - Works but still needs some refactoring In-Reply-To: <1A018B62-118D-4166-A728-FE8CF5E55E95@gmail.com> References: <1A018B62-118D-4166-A728-FE8CF5E55E95@gmail.com> Message-ID: On 10/8/05, Eric Davis wrote: > I just checked in r21 which finished SC2 and SC21 but it still needs > a bit of cleaning and refactoring. I have done as much as I could > but I need to step away to get a new perspective on it. Thanks for all your hard work, Eric. I'm _trying_ to find time for this project myself, but haven't been able to do as much this past iteration as I wanted to. A couple of things that I want to address as far as architecture, but I'll post a seperate message to the list regarding that. -- Regards, John Wilger ----------- 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 johnwilger at gmail.com Sun Oct 9 16:13:33 2005 From: johnwilger at gmail.com (John Wilger) Date: Sun, 9 Oct 2005 16:13:33 -0400 Subject: [eXPlainPMT Developers] Database for 2.0 Message-ID: We had discussed the database for 2.0 development a while ago and had decided to use a sqlite database so that the development database could be checked in to Subversion and all of the developers would be able to automatically have an updated copy of the DB. However, this decision was made without realizing that we could achieve the same goal by using ActiveRecord::Migration library. Unfortunately, Migrations do not currently work with sqlite databases. However, I would like to take the current schema and translate it into migration format. This shouldn't be a big undertaking, currently, since there's only one or two entities described in the database. Using migrations will also make it easier for users to upgrade production databases, because we can include data migrations in addition to schema migrations. -- Regards, John Wilger ----------- 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 johnwilger at gmail.com Sun Oct 9 16:29:25 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Sun, 09 Oct 2005 20:29:25 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r22 - in /trunk: db/ db/development.db db/migrate/ db/migrate/1_add_users_table.rb db/test.db test/unit/user_test.rb Message-ID: <200510092029.j99KTPKG008728@porkchop.wilger.local> Author: jwilger Date: Sun Oct 9 16:29:20 2005 New Revision: 22 Log: Added migration file for the users table, removed the sqlite databases from the repository (and set the repo to ignore db/*.db) and fixed the UserTest unit test so that it works with User#admin as a boolean type. Added: trunk/db/migrate/ trunk/db/migrate/1_add_users_table.rb Removed: trunk/db/development.db trunk/db/test.db Modified: trunk/db/ (props changed) trunk/test/unit/user_test.rb Propchange: trunk/db/ ------------------------------------------------------------------------------ --- svn:ignore (original) +++ svn:ignore Sun Oct 9 16:29:20 2005 @@ -1,1 +1,2 @@ development_structure.sql +*.db Added: trunk/db/migrate/1_add_users_table.rb ============================================================================== --- trunk/db/migrate/1_add_users_table.rb (added) +++ trunk/db/migrate/1_add_users_table.rb Sun Oct 9 16:29:20 2005 @@ -1,0 +1,15 @@ +class AddUsersTable < ActiveRecord::Migration + def self.up + create_table :users do |t| + t.column :admin, :boolean + t.column :login, :string + t.column :email, :string + t.column :password, :string + t.column :name, :string + end + end + + def self.down + drop_table :users + end +end Modified: trunk/test/unit/user_test.rb ============================================================================== --- trunk/test/unit/user_test.rb (original) +++ trunk/test/unit/user_test.rb Sun Oct 9 16:29:20 2005 @@ -28,7 +28,7 @@ def test_read_user assert_equal "admin", @admin.login - assert_equal 1, @admin.admin + assert @admin.admin assert_equal "Admin", @admin.name end From johnwilger at gmail.com Sun Oct 9 16:35:21 2005 From: johnwilger at gmail.com (John Wilger) Date: Sun, 9 Oct 2005 16:35:21 -0400 Subject: [eXPlainPMT Developers] Database for 2.0 In-Reply-To: References: Message-ID: On 10/9/05, John Wilger wrote: > Unfortunately, Migrations do not currently work with sqlite databases. Heh. Well, whadaya know -- the Rails trunk code does seem to support sqlite for migrations. -- Regards, John Wilger ----------- 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 johnwilger at gmail.com Sun Oct 9 16:46:07 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Sun, 09 Oct 2005 20:46:07 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r23 - /trunk/db/migrate/1_add_users_table.rb Message-ID: <200510092046.j99Kk7Ea008736@porkchop.wilger.local> Author: jwilger Date: Sun Oct 9 16:46:05 2005 New Revision: 23 Log: Changed migration #1 so that it creates a defualt admin account. Modified: trunk/db/migrate/1_add_users_table.rb Modified: trunk/db/migrate/1_add_users_table.rb ============================================================================== --- trunk/db/migrate/1_add_users_table.rb (original) +++ trunk/db/migrate/1_add_users_table.rb Sun Oct 9 16:46:05 2005 @@ -7,6 +7,13 @@ t.column :password, :string t.column :name, :string end + first_admin = User.new + first_admin.admin = true + first_admin.login = 'admin' + first_admin.email = 'admin at example.com' + first_admin.password = 'admin' + first_admin.name = 'Admin User' + first_admin.save end def self.down From johnwilger at gmail.com Sun Oct 9 17:13:06 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Sun, 09 Oct 2005 21:13:06 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r24 - in /trunk: app/views/main/dashboard.rhtml public/stylesheets/layout.css Message-ID: <200510092113.j99LD6gg008809@porkchop.wilger.local> Author: jwilger Date: Sun Oct 9 17:13:04 2005 New Revision: 24 Log: Finished basic layout of MainController#dashboard. Modified: trunk/app/views/main/dashboard.rhtml trunk/public/stylesheets/layout.css Modified: trunk/app/views/main/dashboard.rhtml ============================================================================== --- trunk/app/views/main/dashboard.rhtml (original) +++ trunk/app/views/main/dashboard.rhtml Sun Oct 9 17:13:04 2005 @@ -89,20 +89,21 @@

    Upcoming Milstones

    - - + - - + - - +
    ProjectMilestoneName Date
    <%= link_to 'Project Three', :action => 'project_overview' %><%= link_to 'Version 1.6 Release', :action => 'view_milestone' %> - + <%= link_to 'Project Three', :action => 'project_overview' %> + <%= link_to 'Version 1.6 Release', :action => 'view_milestone' %> + 10/31/2005
    <%= link_to 'Project Two', :action => 'project_overview' %><%= link_to 'Project Post-Mortem', :action => 'view_milestone' %> - + <%= link_to 'Project Two', :action => 'project_overview' %> + <%= link_to 'Project Post-Mortem', :action => 'view_milestone' %> + 10/15/2005
    Modified: trunk/public/stylesheets/layout.css ============================================================================== --- trunk/public/stylesheets/layout.css (original) +++ trunk/public/stylesheets/layout.css Sun Oct 9 17:13:04 2005 @@ -6,6 +6,11 @@ h2 { font-size: 1.1em; +} + +th, td { + text-align: left; + vertical-align: top; } #Header { @@ -30,7 +35,8 @@ font-size: 30px; } -#MyProjects { +#MyProjects, #UpcomingMilestones { + clear: right; float: right; width: 15em; margin: 0; @@ -52,3 +58,11 @@ #MyProjects li a:hover { background-color: #EEEEEE; } + +#MyStoryCards, #RecentActivity { + margin: 1em 16em 0 0; +} + +#MyStoryCards table, #RecentActivity table { + width: 100%; +} From edavis10 at gmail.com Sun Oct 9 17:50:23 2005 From: edavis10 at gmail.com (Eric Davis) Date: Sun, 9 Oct 2005 14:50:23 -0700 Subject: [eXPlainPMT Developers] Database for 2.0 In-Reply-To: References: Message-ID: <0D1DA632-2524-4F40-A1AF-57178986CEA8@gmail.com> Yea, I tried to run sqlite on my Typo install and found out on my first svn up that I would have to migrate by hand or use MySQL. I just threw in the SQLite as it was used last time and is quick to setup. > Heh. Well, whadaya know -- the Rails trunk code does seem to support > sqlite for migrations. So are we are running trunk Rails in Vendor? Eric Davis edavis10 at gmail.com | www.theadmin.org =-=-= v2sw5Yhw5ln3pr6Ock4ma3u6MLw3m7l6DiJe6t3b8Oen5g5Ma21s5Mr2p6 hackerkey.com From edavis10 at gmail.com Sun Oct 9 17:59:39 2005 From: edavis10 at gmail.com (Eric Davis) Date: Sun, 9 Oct 2005 14:59:39 -0700 Subject: [eXPlainPMT Developers] Architecture question Message-ID: I just wanted to get an better idea on how we should progress on the architectural layout of 2.0 as in the controllers and such. I just did some user related stuff (login and permissions) and just used a users scaffold for it. Should we follow this norm and have say: controllers/main controllers/users controllers/projects controllers/iterations controllers/stories controllers/...etc... This would be similar to the 1.x branch, which we could use some routing to set it up the way we want. Of course all this can be re-factorable at any time, I just want to know where we stand right now (a user story describing what the url's would be nice for this "ROOT/projects/$project_name/iteration4/ backlog" etc) SIDENOTE: I got a new keyboard, one of those Natural Elite's by MS so I might be a bit slow for a while along witha few typos hree an dthere. Eric Davis edavis10 at gmail.com | www.theadmin.org =-=-= v2sw5Yhw5ln3pr6Ock4ma3u6MLw3m7l6DiJe6t3b8Oen5g5Ma21s5Mr2p6 hackerkey.com From johnwilger at gmail.com Sun Oct 9 18:08:32 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Sun, 09 Oct 2005 22:08:32 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r25 - in /trunk: app/controllers/application.rb app/controllers/users_controller.rb test/functional/main_controller_test.rb test/functional/users_controller_test.rb Message-ID: <200510092208.j99M8WoT008831@porkchop.wilger.local> Author: jwilger Date: Sun Oct 9 18:08:30 2005 New Revision: 25 Log: Refactored so that (in controllers) "session[ :current_user_id ]" holds just the id of the record rather than the object itself (so that changes to that account are reflected in the current_user object. Also added accessors on the ApplicationController for 'current_user' which automatically updates the session[ :current_user_id ] variable. Modified: trunk/app/controllers/application.rb trunk/app/controllers/users_controller.rb trunk/test/functional/main_controller_test.rb trunk/test/functional/users_controller_test.rb Modified: trunk/app/controllers/application.rb ============================================================================== --- trunk/app/controllers/application.rb (original) +++ trunk/app/controllers/application.rb Sun Oct 9 18:08:30 2005 @@ -2,12 +2,11 @@ # application. Likewise will all the methods added be available for all # controllers. class ApplicationController < ActionController::Base - - + attr_accessor :current_user # Method used as a before_filter to restrict access to certain actions. def require_admin - unless @session[:current_user_id].admin? + unless current_user.admin? flash[:error] = "You must be logged in as an administrator to perform"+ " this action" redirect_to :controller => 'users', :action => 'no_admin' @@ -16,12 +15,27 @@ end def check_authentication - unless @session[:current_user_id].kind_of?(User) - - @session[:return_to] = @request.request_uri + if current_user.nil? + session[:return_to] = request.request_uri flash[:status] = "Please log in, and we'll send you right along." redirect_to :controller => 'users', :action => 'login' return false end end -end + + def current_user + unless session[ :current_user_id ].nil? + @current_user ||= User.find session[ :current_user_id ] + end + @current_user + end + + def current_user=( user ) + @current_user = user + if user.nil? + session[ :current_user_id ] = nil + else + session[ :current_user_id ] = user.id + end + end +end Modified: trunk/app/controllers/users_controller.rb ============================================================================== --- trunk/app/controllers/users_controller.rb (original) +++ trunk/app/controllers/users_controller.rb Sun Oct 9 18:08:30 2005 @@ -1,9 +1,8 @@ class UsersController < ApplicationController before_filter :check_authentication, :except => [:login, :authenticate] before_filter :require_admin, :except => [:login, :logout, :authenticate, - :no_admin] - - + :no_admin] + def index list render :action => 'list' @@ -16,7 +15,7 @@ def show @user = User.find(params[:id]) end - + def new @user = User.new end @@ -47,7 +46,7 @@ def destroy @user = User.find(@params[:id]) - if @session[ :current_user_id ] == @user + if session[ :current_user_id ] == @user.id flash[ :error ] = 'You may not delete your own account' else if User.count('admin = 1') == 1 @@ -59,16 +58,16 @@ end redirect_to :action => 'list' end - + def login end - + def authenticate - if @session[ :current_user_id ] = User.authenticate(@params[ :login ], - @params[ :password]) - if @session[:return_to] - redirect_to_path @session[:return_to] - @session[:return_to] = nil + if self.current_user = User.authenticate(@params[ :login ], + @params[ :password]) + if session[:return_to] + redirect_to_path session[:return_to] + session[:return_to] = nil else redirect_to :controller => 'main', :action => 'dashboard' end @@ -79,14 +78,14 @@ end def logout - @session[ :current_user_id ] = nil + self.current_user = nil redirect_to :controller => 'users', :action => 'login' flash[ :notice ] = 'You have been logged out' end - + def no_admin end - + protected # Overrides the ApplicationController#require_admin method so that @@ -94,7 +93,7 @@ def require_admin case action_name when 'edit','update', 'show' - super if @params['id'].to_i != @session[:current_user_id].id + super unless @params['id'].to_i == self.current_user.id else super end Modified: trunk/test/functional/main_controller_test.rb ============================================================================== --- trunk/test/functional/main_controller_test.rb (original) +++ trunk/test/functional/main_controller_test.rb Sun Oct 9 18:08:30 2005 @@ -16,14 +16,14 @@ end def test_dashboard_for_user - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :dashboard assert_response :success assert_template 'dashboard' end def test_dashboard_for_admin - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id get :dashboard assert_response :success assert_template 'dashboard' Modified: trunk/test/functional/users_controller_test.rb ============================================================================== --- trunk/test/functional/users_controller_test.rb (original) +++ trunk/test/functional/users_controller_test.rb Sun Oct 9 18:08:30 2005 @@ -16,14 +16,14 @@ end def test_index_for_admin - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id get :index assert_response :success assert_template 'list' end def test_index_for_user - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :index assert_response :redirect assert_redirected_to :action => 'no_admin' @@ -32,7 +32,7 @@ end def test_list - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id get :list assert_response :success @@ -42,7 +42,7 @@ end def test_show_to_user - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :show, :id => 1 assert_response :redirect assert_redirected_to :action => 'no_admin' @@ -51,7 +51,7 @@ end def test_show_self_user - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :show, :id => 2 assert_response :success assert_template 'show' @@ -60,7 +60,7 @@ end def test_show_to_admin - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id get :show, :id => 1 assert_response :success assert_template 'show' @@ -69,7 +69,7 @@ end def test_new_using_admin - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id get :new assert_response :success assert_template 'new' @@ -77,7 +77,7 @@ end def test_new_using_user - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :new assert_response :redirect assert_redirected_to :action => 'no_admin' @@ -86,7 +86,7 @@ end def test_create - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id num_users = User.count @@ -99,7 +99,7 @@ end def test_edit_from_user - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :edit, :id => 1 assert_response :redirect assert_redirected_to :action => 'no_admin' @@ -108,7 +108,7 @@ end def test_edit_from_admin - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id get :edit, :id => 1 assert_response :success assert_template 'edit' @@ -117,7 +117,7 @@ end def test_edit_self_from_user - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :edit, :id => 2 assert_response :success assert_template 'edit' @@ -126,14 +126,14 @@ end def test_update - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id post :update, :id => 1 assert_response :redirect assert_redirected_to :action => 'show', :id => 1 end def test_destroy - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id assert_not_nil User.find(3) @@ -147,7 +147,7 @@ end def test_destroy_self_from_user - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id assert_not_nil User.find(2) post :destroy, :id => 2 assert_response :redirect @@ -158,7 +158,7 @@ end def test_destroy_self_from_admin - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id assert_not_nil User.find(1) post :destroy, :id => 1 assert_response :redirect @@ -172,7 +172,7 @@ # must be logged in and destroy themselves. This would trigger the branch # of any user destroying themselves. But it is included here for # completeness. - @request.session[ :current_user_id ] = @admin + @request.session[ :current_user_id ] = @admin.id # First remove the first admin of the two assert_not_nil User.find(3) post :destroy, :id => 3 @@ -202,7 +202,7 @@ def test_authenticate post :authenticate, 'login' => @admin.login, 'password' => @admin.password - assert_equal @admin, session[ :current_user_id ] + assert_equal @admin.id, session[ :current_user_id ] assert_response :redirect assert_redirected_to :controller => 'main', :action => 'dashboard' end @@ -217,7 +217,7 @@ end def test_logout - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :logout assert_nil session[ :current_user_id ] assert_response :redirect @@ -226,7 +226,7 @@ end def test_no_admin_error - @request.session[ :current_user_id ] = @user + @request.session[ :current_user_id ] = @user.id get :no_admin assert_template 'no_admin' end From johnwilger at gmail.com Sun Oct 9 18:27:44 2005 From: johnwilger at gmail.com (John Wilger) Date: Sun, 9 Oct 2005 18:27:44 -0400 Subject: [eXPlainPMT Developers] Database for 2.0 In-Reply-To: <0D1DA632-2524-4F40-A1AF-57178986CEA8@gmail.com> References: <0D1DA632-2524-4F40-A1AF-57178986CEA8@gmail.com> Message-ID: On 10/9/05, Eric Davis wrote: > So are we are running trunk Rails in Vendor? Yes. There is a huge difference between trunk Rails and the 0.13.1 release. Since 1.0 will likely be out soon, it makes the most sense to be developing against the trunk. Once 1.0 Rails is released, I will probably switch the tag to the 1.0 release tag. -- Regards, John Wilger ----------- 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 johnwilger at gmail.com Sun Oct 9 18:45:18 2005 From: johnwilger at gmail.com (John Wilger) Date: Sun, 9 Oct 2005 18:45:18 -0400 Subject: [eXPlainPMT Developers] Architecture question In-Reply-To: References: Message-ID: On 10/9/05, Eric Davis wrote: > I just wanted to get an better idea on how we should progress on the > architectural layout of 2.0 as in the controllers and such. I just > did some user related stuff (login and permissions) and just used a > users scaffold for it. Should we follow this norm and have say: > > controllers/main > controllers/users > controllers/projects > controllers/iterations > controllers/stories > controllers/...etc... > > This would be similar to the 1.x branch, which we could use some > routing to set it up the way we want. Yes, that's basically what I'm thinking. However, I'm also thinking (and this is open to discussion) that all of the "main" views will be accessed through the MainController. The other controllers will be responsible for rendering individual "components" that would be included from the "main" views. My thinking is that this would make the AJAX architecture easier to implement. > Of course all this can be re-factorable at any time, I just want to > know where we stand right now (a user story describing what the url's > would be nice for this "ROOT/projects/$project_name/iteration4/ > backlog" etc) As far as the URLs, I agree that we should think about what we want them to look like, but I don't have a good answer on that yet. It's easy enough to modify the way the URLs _look_ using the routing, so I don't think it's a high-priority issue at the moment. -- Regards, John Wilger ----------- 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 johnwilger at gmail.com Sun Oct 9 19:00:26 2005 From: johnwilger at gmail.com (johnwilger@gmail.com) Date: Sun, 09 Oct 2005 23:00:26 -0000 Subject: [eXPlainPMT Developers] [SVN commit] r26 - in /trunk: app/views/main/dashboard.rhtml test/functional/main_controller_test.rb Message-ID: <200510092300.j99N0Q3r008852@porkchop.wilger.local> Author: jwilger Date: Sun Oct 9 19:00:24 2005 New Revision: 26 Log: CurrentUserInfo div now displays the name of the currently logged in user on MainController#dashboard Modified: trunk/app/views/main/dashboard.rhtml trunk/test/functional/main_controller_test.rb Modified: trunk/app/views/main/dashboard.rhtml ============================================================================== --- trunk/app/views/main/dashboard.rhtml (original) +++ trunk/app/views/main/dashboard.rhtml Sun Oct 9 19:00:24 2005 @@ -10,7 +10,7 @@