diff --git a/README b/README
new file mode 100644
index 0000000..7d8965e
--- /dev/null
+++ b/README
@@ -0,0 +1,183 @@
+== Welcome to Rails
+
+Rails is a web-application and persistence framework that includes everything
+needed to create database-backed web-applications according to the
+Model-View-Control pattern of separation. This pattern splits the view (also
+called the presentation) into "dumb" templates that are primarily responsible
+for inserting pre-built data in between HTML tags. The model contains the
+"smart" domain objects (such as Account, Product, Person, Post) that holds all
+the business logic and knows how to persist themselves to a database. The
+controller handles the incoming requests (such as Save New Account, Update
+Product, Show Post) by manipulating the model and directing data to the view.
+
+In Rails, the model is handled by what's called an object-relational mapping
+layer entitled Active Record. This layer allows you to present the data from
+database rows as objects and embellish these data objects with business logic
+methods. You can read more about Active Record in
+link:files/vendor/rails/activerecord/README.html.
+
+The controller and view are handled by the Action Pack, which handles both
+layers by its two parts: Action View and Action Controller. These two layers
+are bundled in a single package due to their heavy interdependence. This is
+unlike the relationship between the Active Record and Action Pack that is much
+more separate. Each of these packages can be used independently outside of
+Rails. You can read more about Action Pack in
+link:files/vendor/rails/actionpack/README.html.
+
+
+== Getting started
+
+1. Start the web server: ruby script/server (run with --help for options)
+2. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!"
+3. Follow the guidelines to start developing your application
+
+
+== Web servers
+
+Rails uses the built-in web server in Ruby called WEBrick by default, so you don't
+have to install or configure anything to play around.
+
+If you have lighttpd installed, though, it'll be used instead when running script/server.
+It's considerably faster than WEBrick and suited for production use, but requires additional
+installation and currently only works well on OS X/Unix (Windows users are encouraged
+to start with WEBrick). We recommend version 1.4.11 and higher. You can download it from
+http://www.lighttpd.net.
+
+If you want something that's halfway between WEBrick and lighttpd, we heartily recommend
+Mongrel. It's a Ruby-based web server with a C-component (so it requires compilation) that
+also works very well with Windows. See more at http://mongrel.rubyforge.org/.
+
+But of course its also possible to run Rails with the premiere open source web server Apache.
+To get decent performance, though, you'll need to install FastCGI. For Apache 1.3, you want
+to use mod_fastcgi. For Apache 2.0+, you want to use mod_fcgid.
+
+See http://wiki.rubyonrails.com/rails/pages/FastCGI for more information on FastCGI.
+
+== Example for Apache conf
+
+
+ ServerName rails
+ DocumentRoot /path/application/public/
+ ErrorLog /path/application/log/server.log
+
+
+ Options ExecCGI FollowSymLinks
+ AllowOverride all
+ Allow from all
+ Order allow,deny
+
+
+
+NOTE: Be sure that CGIs can be executed in that directory as well. So ExecCGI
+should be on and ".cgi" should respond. All requests from 127.0.0.1 go
+through CGI, so no Apache restart is necessary for changes. All other requests
+go through FCGI (or mod_ruby), which requires a restart to show changes.
+
+
+== Debugging Rails
+
+Have "tail -f" commands running on both the server.log, production.log, and
+test.log files. Rails will automatically display debugging and runtime
+information to these files. Debugging info will also be shown in the browser
+on requests from 127.0.0.1.
+
+
+== Breakpoints
+
+Breakpoint support is available through the script/breakpointer client. This
+means that you can break out of execution at any point in the code, investigate
+and change the model, AND then resume execution! Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find_all
+ breakpoint "Breaking out from the list"
+ end
+ end
+
+So the controller will accept the action, run the first line, then present you
+with a IRB prompt in the breakpointer window. Here you can do things like:
+
+Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint'
+
+ >> @posts.inspect
+ => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>,
+ #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
+ >> @posts.first.title = "hello from a breakpoint"
+ => "hello from a breakpoint"
+
+...and even better is that you can examine how your runtime objects actually work:
+
+ >> f = @posts.first
+ => #nil, "body"=>nil, "id"=>"1"}>
+ >> f.
+ Display all 152 possibilities? (y or n)
+
+Finally, when you're ready to resume execution, you press CTRL-D
+
+
+== Console
+
+You can interact with the domain model by starting the console through script/console.
+Here you'll have all parts of the application configured, just like it is when the
+application is running. You can inspect domain models, change values, and save to the
+database. Starting the script without arguments will launch it in the development environment.
+Passing an argument will specify a different environment, like script/console production.
+
+To reload your controllers and models after launching the console run reload!
+
+
+
+== Description of contents
+
+app
+ Holds all the code that's specific to this particular application.
+
+app/controllers
+ Holds controllers that should be named like weblog_controller.rb for
+ automated URL mapping. All controllers should descend from
+ ActionController::Base.
+
+app/models
+ Holds models that should be named like post.rb.
+ Most models will descend from ActiveRecord::Base.
+
+app/views
+ Holds the template files for the view that should be named like
+ weblog/index.rhtml for the WeblogController#index action. All views use eRuby
+ syntax. This directory can also be used to keep stylesheets, images, and so on
+ that can be symlinked to public.
+
+app/helpers
+ Holds view helpers that should be named like weblog_helper.rb.
+
+app/apis
+ Holds API classes for web services.
+
+config
+ Configuration files for the Rails environment, the routing map, the database, and other dependencies.
+
+components
+ Self-contained mini-applications that can bundle together controllers, models, and views.
+
+db
+ Contains the database schema in schema.rb. db/migrate contains all
+ the sequence of Migrations for your schema.
+
+lib
+ Application specific libraries. Basically, any kind of custom code that doesn't
+ belong under controllers, models, or helpers. This directory is in the load path.
+
+public
+ The directory available for the web server. Contains subdirectories for images, stylesheets,
+ and javascripts. Also contains the dispatchers and the default HTML files.
+
+script
+ Helper scripts for automation and generation.
+
+test
+ Unit and functional tests along with fixtures.
+
+vendor
+ External libraries that the application depends on. Also includes the plugins subdirectory.
+ This directory is in the load path.
diff --git a/Rakefile b/Rakefile
new file mode 100644
index 0000000..3bb0e85
--- /dev/null
+++ b/Rakefile
@@ -0,0 +1,10 @@
+# Add your own tasks in files placed in lib/tasks ending in .rake,
+# for example lib/tasks/capistrano.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'
+
+require 'tasks/rails'
diff --git a/app/controllers/application.rb b/app/controllers/application.rb
new file mode 100644
index 0000000..e72e0a4
--- /dev/null
+++ b/app/controllers/application.rb
@@ -0,0 +1,6 @@
+# Filters added to this controller will be run for all controllers in the application.
+# Likewise, all the methods added will be available for all controllers.
+class ApplicationController < ActionController::Base
+ include ExceptionNotifiable
+
+end
\ No newline at end of file
diff --git a/app/controllers/appointments_controller.rb b/app/controllers/appointments_controller.rb
new file mode 100644
index 0000000..73373d9
--- /dev/null
+++ b/app/controllers/appointments_controller.rb
@@ -0,0 +1,87 @@
+class AppointmentsController < ApplicationController
+ # Configuration
+ @@start_hour = 8
+ @@end_hour = 18
+ @@default_column_count = 5
+
+ before_filter :load_first_minute
+
+ def index
+ begin
+ @date = Date.new(params[:year].to_i, params[:month].to_i, params[:day].to_i)
+ rescue ArgumentError
+ @date = Date.today
+ end
+
+ @times = (@@start_hour..(@@end_hour - 1)).collect {|h| @date.to_time + h.hours}
+
+ @providers = HealthcareProvider.find(:all, :order => 'name')
+
+ num_columns = (@providers.length < @@default_column_count ? @providers.length : @@default_column_count)
+ @columns = (0..(num_columns - 1)).collect { |index| column_for_provider(@providers[index], @date, index) }
+ end
+
+ def index_for_provider
+ @date = Date.new(params[:year].to_i, params[:month].to_i, params[:day].to_i)
+ @provider = HealthcareProvider.find(params[:provider_id])
+ @column = column_for_provider(@provider, @date)
+
+ render :layout => false
+ end
+
+ def destroy
+ @appointment = Appointment.find(params[:id])
+ @appointment.destroy
+ end
+
+ def new
+ @date = Date.new(params[:year].to_i, params[:month].to_i, params[:day].to_i)
+ @minute = params[:minute].to_i
+
+ @appointment = Appointment.new
+ @appointment.healthcare_provider = HealthcareProvider.find(params[:provider_id])
+ @appointment.start_time = @date.to_time + @minute.minutes
+
+ render :layout => false
+ end
+
+ def create
+ @appointment = Appointment.new(params[:appointment])
+ if @appointment.valid?
+ @saved = true
+ @appointment.create
+ end
+ end
+
+ def edit
+ @appointment = Appointment.find(params[:id])
+ render :layout => false
+ end
+
+ def update
+ @appointment = Appointment.find(params[:id])
+ @saved = @appointment.update_attributes(params[:appointment])
+ end
+
+ def update_in_place
+ @appointment = Appointment.find(params[:id])
+ @saved = @appointment.update_attributes(params[:appointment])
+ end
+
+private
+
+ def column_for_provider(provider, date, index = nil)
+ {
+ :index => index,
+ :provider => provider,
+ :appointments => provider.appointments_on(date),
+ :slots => TimeSlot.range(@@start_hour * 60, @@end_hour * 60, provider.default_appointment_duration)
+ }
+ end
+
+ def load_first_minute
+ @first_minute = @@start_hour.hours / 60
+ true
+ end
+
+end
diff --git a/app/controllers/patients_controller.rb b/app/controllers/patients_controller.rb
new file mode 100644
index 0000000..e0f8d70
--- /dev/null
+++ b/app/controllers/patients_controller.rb
@@ -0,0 +1,5 @@
+class PatientsController < ApplicationController
+
+ auto_complete_for :patient, :name
+
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
new file mode 100644
index 0000000..d31bd65
--- /dev/null
+++ b/app/helpers/application_helper.rb
@@ -0,0 +1,4 @@
+# Methods added to this helper will be available to all templates in the application.
+module ApplicationHelper
+
+end
diff --git a/app/helpers/appointments_helper.rb b/app/helpers/appointments_helper.rb
new file mode 100644
index 0000000..cd6e1c0
--- /dev/null
+++ b/app/helpers/appointments_helper.rb
@@ -0,0 +1,14 @@
+module AppointmentsHelper
+
+ def format_time(time)
+ twelve_hour = time.hour % 12
+ twelve_hour = 12 if twelve_hour == 0
+ time.strftime("#{twelve_hour}:%M %p").downcase
+ end
+
+ def day_url(date)
+ url_for :controller => 'appointments', :action => 'index', :year => date.year, :month => date.mon, :day => date.day
+ end
+
+
+end
diff --git a/app/helpers/patients_helper.rb b/app/helpers/patients_helper.rb
new file mode 100644
index 0000000..e3835d0
--- /dev/null
+++ b/app/helpers/patients_helper.rb
@@ -0,0 +1,2 @@
+module PatientsHelper
+end
diff --git a/app/models/appointment.rb b/app/models/appointment.rb
new file mode 100644
index 0000000..52e5ac1
--- /dev/null
+++ b/app/models/appointment.rb
@@ -0,0 +1,65 @@
+class Appointment < ActiveRecord::Base
+ belongs_to :patient
+ belongs_to :healthcare_provider
+ belongs_to :appointment_type
+ belongs_to :state, :class_name => 'AppointmentState', :foreign_key => 'appointment_state_id'
+ has_and_belongs_to_many :categories, :class_name => 'AppointmentCategory'
+
+ validates_presence_of :state, :patient, :healthcare_provider, :start_time, :appointment_type
+ validate :provider_is_available
+
+ before_validation :update_end_time
+
+ def initialize(attributes = nil)
+ state = AppointmentState.default
+ super(attributes)
+ end
+
+ def label
+ patient.nil? ? 'new' : patient.name
+ end
+
+ def start_minute
+ unless start_time.nil?
+ start_time.hour * 60 + start_time.min
+ end
+ end
+
+ alias_method :original_healthcare_provider=, :healthcare_provider=
+
+ def healthcare_provider=(provider)
+ self.appointment_type = (provider.nil? ? nil : provider.default_appointment_type)
+ self.original_healthcare_provider = provider
+ end
+
+ def duration
+ !duration_override.blank? ? duration_override : appointment_type.duration
+ end
+
+ def patient_name
+ patient.name unless patient.nil?
+ end
+
+ def patient_name=(value)
+ self.patient = Patient.find(:first, :conditions => ["name = ?", value])
+ end
+
+ def category_ids=(category_id_array)
+ self.categories = category_id_array.reject{ |category_id| category_id == '' }.collect { |category_id| AppointmentCategory.find(category_id.to_i) }
+ end
+
+protected
+
+ def provider_is_available
+ if !healthcare_provider.nil? && !start_time.nil? && !duration.nil? && !healthcare_provider.is_available_between(start_time, end_time, id)
+ errors.add(:start_time, "and Duration clash with another appointment for this Healthcare Provider.")
+ end
+ end
+
+ def update_end_time
+ unless start_time.nil? || duration.nil?
+ self.end_time = start_time + duration.minutes
+ end
+ end
+
+end
diff --git a/app/models/appointment_category.rb b/app/models/appointment_category.rb
new file mode 100644
index 0000000..106d840
--- /dev/null
+++ b/app/models/appointment_category.rb
@@ -0,0 +1,3 @@
+class AppointmentCategory < ActiveRecord::Base
+ validates_presence_of :name
+end
diff --git a/app/models/appointment_state.rb b/app/models/appointment_state.rb
new file mode 100644
index 0000000..c4b2be1
--- /dev/null
+++ b/app/models/appointment_state.rb
@@ -0,0 +1,7 @@
+class AppointmentState < ActiveRecord::Base
+
+ def self.default
+ AppointmentState.find(1)
+ end
+
+end
diff --git a/app/models/appointment_type.rb b/app/models/appointment_type.rb
new file mode 100644
index 0000000..774f994
--- /dev/null
+++ b/app/models/appointment_type.rb
@@ -0,0 +1,6 @@
+class AppointmentType < ActiveRecord::Base
+ belongs_to :healthcare_provider
+ validates_presence_of :healthcare_provider, :duration
+
+
+end
diff --git a/app/models/healthcare_provider.rb b/app/models/healthcare_provider.rb
new file mode 100644
index 0000000..4daac1c
--- /dev/null
+++ b/app/models/healthcare_provider.rb
@@ -0,0 +1,30 @@
+class HealthcareProvider < ActiveRecord::Base
+ has_many :appointments
+ has_many :appointment_types
+ belongs_to :default_appointment_type, :class_name => 'AppointmentType', :foreign_key => 'default_appointment_type_id'
+
+ validates_presence_of :name, :default_appointment_type
+
+ def appointments_on(date)
+ appointments.find(:all, :order => 'start_time ASC', :conditions => ["start_time >= ? and start_time < ?", date.to_time.beginning_of_day, date.to_time.beginning_of_day.tomorrow])
+ end
+
+ def create_default_appointment_types
+ appointment_types = []
+ appointment_types << AppointmentType.create(:name => 'A', :duration => 5, :healthcare_provider => self)
+ appointment_types << AppointmentType.create(:name => 'B', :duration => 20, :healthcare_provider => self)
+ appointment_types << AppointmentType.create(:name => 'C', :duration => 40, :healthcare_provider => self)
+ appointment_types << AppointmentType.create(:name => 'D', :duration => 60, :healthcare_provider => self)
+ default_appointment_type = appointment_types[1]
+ save!
+ end
+
+ def default_appointment_duration
+ default_appointment_type.duration
+ end
+
+ def is_available_between(start_time, end_time, exception_id)
+ appointments.find(:first, :conditions => ["id != ? AND ((start_time >= ? AND start_time < ?) OR (end_time > ? AND end_time <= ?) OR (start_time <= ? AND end_time >= ?))", exception_id, start_time, end_time, start_time, end_time, start_time, end_time]).nil?
+ end
+
+end
diff --git a/app/models/patient.rb b/app/models/patient.rb
new file mode 100644
index 0000000..1854abf
--- /dev/null
+++ b/app/models/patient.rb
@@ -0,0 +1,16 @@
+class Patient < ActiveRecord::Base
+
+ validates_presence_of :first_name, :surname
+ before_save :update_name
+
+ def name
+ first_name.to_s + ' ' + surname.to_s
+ end
+
+private
+
+ def update_name
+ self.name = name
+ end
+
+end
diff --git a/app/models/time_slot.rb b/app/models/time_slot.rb
new file mode 100644
index 0000000..b12e15e
--- /dev/null
+++ b/app/models/time_slot.rb
@@ -0,0 +1,31 @@
+
+class TimeSlot
+ include Reloadable
+
+ def self.range(start_minute, end_minute, duration)
+ results = []
+
+ slot = TimeSlot.new(start_minute, duration)
+ while slot.end_minute <= end_minute
+ results << slot
+ slot = TimeSlot.new(slot.end_minute, duration)
+ end
+ results
+ end
+
+ def initialize(start_minute, duration)
+ @start_minute = start_minute
+ @duration = duration
+ end
+
+ attr_accessor :start_minute, :duration
+
+ def end_minute
+ start_minute + duration
+ end
+
+ def start_time
+ Time.new.midnight + start_minute * 60
+ end
+
+end
\ No newline at end of file
diff --git a/app/views/appointments/_appointment.rhtml b/app/views/appointments/_appointment.rhtml
new file mode 100644
index 0000000..80f3358
--- /dev/null
+++ b/app/views/appointments/_appointment.rhtml
@@ -0,0 +1,43 @@
+
+
+
+
diff --git a/config/boot.rb b/config/boot.rb
new file mode 100644
index 0000000..9a094cb
--- /dev/null
+++ b/config/boot.rb
@@ -0,0 +1,44 @@
+# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb
+
+unless defined?(RAILS_ROOT)
+ root_path = File.join(File.dirname(__FILE__), '..')
+
+ unless RUBY_PLATFORM =~ /mswin32/
+ require 'pathname'
+ root_path = Pathname.new(root_path).cleanpath(true).to_s
+ end
+
+ RAILS_ROOT = root_path
+end
+
+unless defined?(Rails::Initializer)
+ if File.directory?("#{RAILS_ROOT}/vendor/rails")
+ require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
+ else
+ require 'rubygems'
+
+ environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join
+ environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/
+ rails_gem_version = $1
+
+ if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version
+ rails_gem = Gem.cache.search('rails', "=#{version}").first
+
+ if rails_gem
+ require_gem "rails", "=#{version}"
+ require rails_gem.full_gem_path + '/lib/initializer'
+ else
+ STDERR.puts %(Cannot find gem for Rails =#{version}:
+ Install the missing gem with 'gem install -v=#{version} rails', or
+ change environment.rb to define RAILS_GEM_VERSION with your desired version.
+ )
+ exit 1
+ end
+ else
+ require_gem "rails"
+ require 'initializer'
+ end
+ end
+
+ Rails::Initializer.run(:set_load_path)
+end
\ No newline at end of file
diff --git a/config/database.example b/config/database.example
new file mode 100644
index 0000000..954033d
--- /dev/null
+++ b/config/database.example
@@ -0,0 +1,35 @@
+# MySQL (default setup). Versions 4.1 and 5.0 are recommended.
+#
+# Install the MySQL driver:
+# gem install mysql
+# On MacOS X:
+# gem install mysql -- --include=/usr/local/lib
+# On Windows:
+# There is no gem for Windows. Install mysql.so from RubyForApache.
+# http://rubyforge.org/projects/rubyforapache
+#
+# And be sure to use new-style password hashing:
+# http://dev.mysql.com/doc/refman/5.0/en/old-client.html
+development:
+ adapter: mysql
+ database: appointments_development
+ username: root
+ password:
+ host: localhost
+
+# Warning: The database defined as 'test' will be erased and
+# re-generated from your development database when you run 'rake'.
+# Do not set this db to the same as development or production.
+test:
+ adapter: mysql
+ database: appointments_test
+ username: root
+ password:
+ host: localhost
+
+production:
+ adapter: mysql
+ database: appointments_production
+ username: root
+ password:
+ host: localhost
diff --git a/config/database.yml b/config/database.yml
new file mode 100644
index 0000000..bdb7b49
--- /dev/null
+++ b/config/database.yml
@@ -0,0 +1,22 @@
+
+development:
+ adapter: mysql
+ database: appointments_development
+ host: localhost
+
+# Warning: The database defined as 'test' will be erased and
+# re-generated from your development database when you run 'rake'.
+# Do not set this db to the same as development or production.
+test:
+ adapter: mysql
+ database: appointments_test
+ username: root
+ password:
+ host: localhost
+
+production:
+ adapter: mysql
+ database: appointments_production
+ username: appointments
+ password: bootlace
+ host: localhost
diff --git a/config/database.yml.site5 b/config/database.yml.site5
new file mode 100644
index 0000000..24d192b
--- /dev/null
+++ b/config/database.yml.site5
@@ -0,0 +1,15 @@
+
+development:
+ adapter: mysql
+ database: geektrib_appointments
+ username: geektrib_hherb
+ password: bootlace
+ host: localhost
+
+
+production:
+ adapter: mysql
+ database: geektrib_appointments
+ username: geektrib_hherb
+ password: bootlace
+ host: localhost
diff --git a/config/environment.rb b/config/environment.rb
new file mode 100644
index 0000000..971409e
--- /dev/null
+++ b/config/environment.rb
@@ -0,0 +1,68 @@
+# Be sure to restart your web server when you modify this file.
+
+# Uncomment below to force Rails into production mode when
+# you don't control web/app server and can't set it the proper way
+# ENV['RAILS_ENV'] ||= 'production'
+
+# Specifies gem version of Rails to use when vendor/rails is not present
+RAILS_GEM_VERSION = '1.1.6'
+
+# Bootstrap the Rails environment, frameworks, and default configuration
+require File.join(File.dirname(__FILE__), 'boot')
+
+Rails::Initializer.run do |config|
+ # Settings in config/environments/* take precedence those specified here
+
+ # Skip frameworks you're not going to use (only works if using vendor/rails)
+ # config.frameworks -= [ :action_web_service, :action_mailer ]
+
+ # Add additional load paths for your own custom dirs
+ # config.load_paths += %W( #{RAILS_ROOT}/extras )
+
+ # Force all environments to use the same logger level
+ # (by default production uses :info, the others :debug)
+ # config.log_level = :debug
+
+ # Use the database for sessions instead of the file system
+ # (create the session table with 'rake db:sessions:create')
+ # config.action_controller.session_store = :active_record_store
+
+ # Use SQL instead of Active Record's schema dumper when creating the test database.
+ # This is necessary if your schema can't be completely dumped by the schema dumper,
+ # like if you have constraints or database-specific column types
+ # config.active_record.schema_format = :sql
+
+ # Activate observers that should always be running
+ # config.active_record.observers = :cacher, :garbage_collector
+
+ # Make Active Record use UTC-base instead of local time
+ # config.active_record.default_timezone = :utc
+
+ # See Rails::Configuration for more options
+end
+
+# 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
+
+# Include your application configuration below
+ExceptionNotifier.exception_recipients = %w(craig@craigambrose.com)
+
+AutoAdmin.config do |admin|
+ # This information is used by the theme to construct a useful
+ # header; the first parameter is the full URL of the main site, the
+ # second is the displayed name of the site, and the third (optional)
+ # parameter is the title for the administration site.
+ admin.set_site_info 'http://medical.craigambrose.com/', 'Medical Appointments'
+
+ # "Primary Objects" are those for which lists should be directly
+ # accessible from the home page.
+ admin.primary_objects = %w(healthcare_provider patient appointment_type appointment_state appointment_category)
+
+ admin.theme = :django # Optional; this is the default.
+end
\ No newline at end of file
diff --git a/config/environments/development.rb b/config/environments/development.rb
new file mode 100644
index 0000000..0589aa9
--- /dev/null
+++ b/config/environments/development.rb
@@ -0,0 +1,21 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# 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.
+config.whiny_nils = true
+
+# Enable the breakpoint server that script/breakpointer connects to
+config.breakpoint_server = true
+
+# Show full error reports and disable caching
+config.action_controller.consider_all_requests_local = true
+config.action_controller.perform_caching = false
+config.action_view.cache_template_extensions = false
+config.action_view.debug_rjs = true
+
+# Don't care if the mailer can't send
+config.action_mailer.raise_delivery_errors = false
diff --git a/config/environments/production.rb b/config/environments/production.rb
new file mode 100644
index 0000000..5a4e2b1
--- /dev/null
+++ b/config/environments/production.rb
@@ -0,0 +1,18 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The production environment is meant for finished, "live" apps.
+# Code is not reloaded between requests
+config.cache_classes = 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
diff --git a/config/environments/test.rb b/config/environments/test.rb
new file mode 100644
index 0000000..f0689b9
--- /dev/null
+++ b/config/environments/test.rb
@@ -0,0 +1,19 @@
+# Settings specified here will take precedence over those in config/environment.rb
+
+# The test environment is used exclusively to run your application's
+# 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.
+config.whiny_nils = true
+
+# 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.
+config.action_mailer.delivery_method = :test
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
new file mode 100644
index 0000000..5f96b72
--- /dev/null
+++ b/config/routes.rb
@@ -0,0 +1,17 @@
+ActionController::Routing::Routes.draw do |map|
+
+ UJS::routes
+
+ map.connect '', :controller => 'appointments', :action => 'index', :year => Date.today.year, :month => Date.today.month, :day => Date.today.day
+
+ map.connection 'appointments/:action/:year/:month/:day',
+ :controller => 'appointments',
+ :requirements => { :action => /(index|index_for_provider|new)/,
+ :year => /(19|20)\d\d/,
+ :month => /[01]?\d/,
+ :day => /[0-3]?\d/}
+
+ map.connection ':controller/:action/:id'
+
+
+end
diff --git a/db/migrate/001_create_providers.rb b/db/migrate/001_create_providers.rb
new file mode 100644
index 0000000..c41af9b
--- /dev/null
+++ b/db/migrate/001_create_providers.rb
@@ -0,0 +1,11 @@
+class CreateProviders < ActiveRecord::Migration
+ def self.up
+ create_table :providers do |t|
+ t.column :name, :string
+ end
+ end
+
+ def self.down
+ drop_table :providers
+ end
+end
diff --git a/db/migrate/002_add_default_appointment_length_to_providers.rb b/db/migrate/002_add_default_appointment_length_to_providers.rb
new file mode 100644
index 0000000..f7fc1ca
--- /dev/null
+++ b/db/migrate/002_add_default_appointment_length_to_providers.rb
@@ -0,0 +1,9 @@
+class AddDefaultAppointmentLengthToProviders < ActiveRecord::Migration
+ def self.up
+ add_column :providers, :default_appointment_length, :integer, :null => false, :default => 30
+ end
+
+ def self.down
+ remove_column :providers, :default_appointment_length
+ end
+end
diff --git a/db/migrate/003_create_appointments.rb b/db/migrate/003_create_appointments.rb
new file mode 100644
index 0000000..5ac7298
--- /dev/null
+++ b/db/migrate/003_create_appointments.rb
@@ -0,0 +1,14 @@
+class CreateAppointments < ActiveRecord::Migration
+ def self.up
+ create_table :appointments do |t|
+ t.column :patient_id, :integer
+ t.column :start_time, :datetime
+ t.column :duration, :integer
+ t.column :comment, :text
+ end
+ end
+
+ def self.down
+ drop_table :appointments
+ end
+end
diff --git a/db/migrate/004_create_patients.rb b/db/migrate/004_create_patients.rb
new file mode 100644
index 0000000..218152f
--- /dev/null
+++ b/db/migrate/004_create_patients.rb
@@ -0,0 +1,12 @@
+class CreatePatients < ActiveRecord::Migration
+ def self.up
+ create_table :patients do |t|
+ t.column :first_name, :string
+ t.column :surname, :string
+ end
+ end
+
+ def self.down
+ drop_table :patients
+ end
+end
diff --git a/db/migrate/005_add_provider_to_appointments.rb b/db/migrate/005_add_provider_to_appointments.rb
new file mode 100644
index 0000000..64717ef
--- /dev/null
+++ b/db/migrate/005_add_provider_to_appointments.rb
@@ -0,0 +1,9 @@
+class AddProviderToAppointments < ActiveRecord::Migration
+ def self.up
+ add_column :appointments, :provider_id, :integer
+ end
+
+ def self.down
+ remove_column :appointments, :provider_id
+ end
+end
diff --git a/db/migrate/006_rename_provider_to_healthcare_provider.rb b/db/migrate/006_rename_provider_to_healthcare_provider.rb
new file mode 100644
index 0000000..60eafcf
--- /dev/null
+++ b/db/migrate/006_rename_provider_to_healthcare_provider.rb
@@ -0,0 +1,11 @@
+class RenameProviderToHealthcareProvider < ActiveRecord::Migration
+ def self.up
+ rename_table :providers, :healthcare_providers
+ rename_column :appointments, :provider_id, :healthcare_provider_id
+ end
+
+ def self.down
+ rename_table :healthcare_providers, :providers
+ rename_column :appointments, :healthcare_provider_id, :provider_id
+ end
+end
diff --git a/db/migrate/007_create_appointment_types.rb b/db/migrate/007_create_appointment_types.rb
new file mode 100644
index 0000000..9515230
--- /dev/null
+++ b/db/migrate/007_create_appointment_types.rb
@@ -0,0 +1,13 @@
+class CreateAppointmentTypes < ActiveRecord::Migration
+ def self.up
+ create_table :appointment_types do |t|
+ t.column :name, :string
+ t.column :duration, :integer
+ t.column :healthcare_provider_id, :integer
+ end
+ end
+
+ def self.down
+ drop_table :appointment_types
+ end
+end
diff --git a/db/migrate/008_add_default_appointment_type_to_providers.rb b/db/migrate/008_add_default_appointment_type_to_providers.rb
new file mode 100644
index 0000000..307f24d
--- /dev/null
+++ b/db/migrate/008_add_default_appointment_type_to_providers.rb
@@ -0,0 +1,9 @@
+class AddDefaultAppointmentTypeToProviders < ActiveRecord::Migration
+ def self.up
+ add_column :healthcare_providers, :default_appointment_type_id, :integer
+ end
+
+ def self.down
+ remove_column :healthcare_providers, :default_appointment_type_id
+ end
+end
diff --git a/db/migrate/009_add_appointment_type_to_appointment.rb b/db/migrate/009_add_appointment_type_to_appointment.rb
new file mode 100644
index 0000000..3d587c9
--- /dev/null
+++ b/db/migrate/009_add_appointment_type_to_appointment.rb
@@ -0,0 +1,9 @@
+class AddAppointmentTypeToAppointment < ActiveRecord::Migration
+ def self.up
+ add_column :appointments, :appointment_type_id, :integer
+ end
+
+ def self.down
+ remove_column :appointments, :appointment_type_id
+ end
+end
diff --git a/db/migrate/010_rename_appointment_duration.rb b/db/migrate/010_rename_appointment_duration.rb
new file mode 100644
index 0000000..ab14672
--- /dev/null
+++ b/db/migrate/010_rename_appointment_duration.rb
@@ -0,0 +1,9 @@
+class RenameAppointmentDuration < ActiveRecord::Migration
+ def self.up
+ rename_column :appointments, :duration, :duration_override
+ end
+
+ def self.down
+ rename_column :appointments, :duration_override, :duration
+ end
+end
diff --git a/db/migrate/011_add_end_time_to_appointments.rb b/db/migrate/011_add_end_time_to_appointments.rb
new file mode 100644
index 0000000..6103ff7
--- /dev/null
+++ b/db/migrate/011_add_end_time_to_appointments.rb
@@ -0,0 +1,9 @@
+class AddEndTimeToAppointments < ActiveRecord::Migration
+ def self.up
+ add_column :appointments, :end_time, :datetime
+ end
+
+ def self.down
+ remove_column :appointments, :end_time
+ end
+end
diff --git a/db/migrate/012_remove_default_appointment_duration.rb b/db/migrate/012_remove_default_appointment_duration.rb
new file mode 100644
index 0000000..a56acd7
--- /dev/null
+++ b/db/migrate/012_remove_default_appointment_duration.rb
@@ -0,0 +1,9 @@
+class RemoveDefaultAppointmentDuration < ActiveRecord::Migration
+ def self.up
+ remove_column :healthcare_providers, :default_appointment_length
+ end
+
+ def self.down
+ add_column :healthcare_providers, :default_appointment_length, :integer
+ end
+end
diff --git a/db/migrate/013_add_created_on_to_tables.rb b/db/migrate/013_add_created_on_to_tables.rb
new file mode 100644
index 0000000..41a539b
--- /dev/null
+++ b/db/migrate/013_add_created_on_to_tables.rb
@@ -0,0 +1,13 @@
+class AddCreatedOnToTables < ActiveRecord::Migration
+ def self.up
+ add_column :appointments, :created_on, :datetime
+ add_column :healthcare_providers, :created_on, :datetime
+ add_column :patients, :created_on, :datetime
+ end
+
+ def self.down
+ remove_column :appointments, :created_on
+ remove_column :healthcare_providers, :created_on
+ remove_column :patients, :created_on
+ end
+end
diff --git a/db/migrate/014_create_appointment_states.rb b/db/migrate/014_create_appointment_states.rb
new file mode 100644
index 0000000..aed4c54
--- /dev/null
+++ b/db/migrate/014_create_appointment_states.rb
@@ -0,0 +1,13 @@
+class CreateAppointmentStates < ActiveRecord::Migration
+ def self.up
+ create_table :appointment_states do |t|
+ t.column :name, :string
+ t.column :icon, :string
+ t.column :colour, :string
+ end
+ end
+
+ def self.down
+ drop_table :appointment_states
+ end
+end
diff --git a/db/migrate/015_add_appointment_state_to_appointments.rb b/db/migrate/015_add_appointment_state_to_appointments.rb
new file mode 100644
index 0000000..d85326a
--- /dev/null
+++ b/db/migrate/015_add_appointment_state_to_appointments.rb
@@ -0,0 +1,9 @@
+class AddAppointmentStateToAppointments < ActiveRecord::Migration
+ def self.up
+ add_column :appointments, :appointment_state_id, :integer
+ end
+
+ def self.down
+ remove_column :appointments, :appointment_state_id
+ end
+end
diff --git a/db/migrate/016_create_default_appointment_state.rb b/db/migrate/016_create_default_appointment_state.rb
new file mode 100644
index 0000000..184cd03
--- /dev/null
+++ b/db/migrate/016_create_default_appointment_state.rb
@@ -0,0 +1,9 @@
+class CreateDefaultAppointmentState < ActiveRecord::Migration
+ def self.up
+ AppointmentState.create(:name => 'Booked', :icon => 'booked.png', :colour => '#FFCC99')
+ end
+
+ def self.down
+ AppointmentState.destroy_all(:conditions => "name = 'Booked'")
+ end
+end
diff --git a/db/migrate/017_set_all_appointments_to_default_appointment_state.rb b/db/migrate/017_set_all_appointments_to_default_appointment_state.rb
new file mode 100644
index 0000000..220cfbd
--- /dev/null
+++ b/db/migrate/017_set_all_appointments_to_default_appointment_state.rb
@@ -0,0 +1,9 @@
+class SetAllAppointmentsToDefaultAppointmentState < ActiveRecord::Migration
+ def self.up
+ execute "UPDATE appointments SET appointment_state_id = 1"
+ end
+
+ def self.down
+ execute "UPDATE appointments SET appointment_state_id = NULL"
+ end
+end
diff --git a/db/migrate/018_add_name_to_patient.rb b/db/migrate/018_add_name_to_patient.rb
new file mode 100644
index 0000000..2f4f44c
--- /dev/null
+++ b/db/migrate/018_add_name_to_patient.rb
@@ -0,0 +1,9 @@
+class AddNameToPatient < ActiveRecord::Migration
+ def self.up
+ add_column :patients, :name, :string
+ end
+
+ def self.down
+ remove_column :patients, :name
+ end
+end
diff --git a/db/migrate/019_update_patient_names.rb b/db/migrate/019_update_patient_names.rb
new file mode 100644
index 0000000..7468915
--- /dev/null
+++ b/db/migrate/019_update_patient_names.rb
@@ -0,0 +1,10 @@
+class UpdatePatientNames < ActiveRecord::Migration
+ def self.up
+ for patient in Patient.find(:all)
+ patient.save
+ end
+ end
+
+ def self.down
+ end
+end
diff --git a/db/migrate/020_create_appointment_categories.rb b/db/migrate/020_create_appointment_categories.rb
new file mode 100644
index 0000000..31a3e7f
--- /dev/null
+++ b/db/migrate/020_create_appointment_categories.rb
@@ -0,0 +1,11 @@
+class CreateAppointmentCategories < ActiveRecord::Migration
+ def self.up
+ create_table :appointment_categories do |t|
+ t.column :name, :string
+ end
+ end
+
+ def self.down
+ drop_table :appointment_categories
+ end
+end
diff --git a/db/migrate/021_create_appointment_categories_appointments.rb b/db/migrate/021_create_appointment_categories_appointments.rb
new file mode 100644
index 0000000..e96e976
--- /dev/null
+++ b/db/migrate/021_create_appointment_categories_appointments.rb
@@ -0,0 +1,12 @@
+class CreateAppointmentCategoriesAppointments < ActiveRecord::Migration
+ def self.up
+ create_table :appointment_categories_appointments, :id => false do |t|
+ t.column :appointment_category_id, :integer
+ t.column :appointment_id, :integer
+ end
+ end
+
+ def self.down
+ drop_table :appointment_categories_appointments
+ end
+end
diff --git a/db/migrate/022_save_all_appointments.rb b/db/migrate/022_save_all_appointments.rb
new file mode 100644
index 0000000..1bca86f
--- /dev/null
+++ b/db/migrate/022_save_all_appointments.rb
@@ -0,0 +1,10 @@
+class SaveAllAppointments < ActiveRecord::Migration
+ def self.up
+ for appointment in Appointment.find(:all)
+ appointment.save!
+ end
+ end
+
+ def self.down
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 0000000..e5157aa
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,53 @@
+# This file is autogenerated. Instead of editing this file, please use the
+# migrations feature of ActiveRecord to incrementally modify your database, and
+# then regenerate this schema definition.
+
+ActiveRecord::Schema.define(:version => 21) do
+
+ create_table "appointment_categories", :force => true do |t|
+ t.column "name", :string
+ end
+
+ create_table "appointment_categories_appointments", :id => false, :force => true do |t|
+ t.column "appointment_category_id", :integer
+ t.column "appointment_id", :integer
+ end
+
+ create_table "appointment_states", :force => true do |t|
+ t.column "name", :string
+ t.column "icon", :string
+ t.column "colour", :string
+ end
+
+ create_table "appointment_types", :force => true do |t|
+ t.column "name", :string
+ t.column "duration", :integer
+ t.column "healthcare_provider_id", :integer
+ end
+
+ create_table "appointments", :force => true do |t|
+ t.column "patient_id", :integer
+ t.column "start_time", :datetime
+ t.column "duration_override", :integer
+ t.column "comment", :text
+ t.column "healthcare_provider_id", :integer
+ t.column "appointment_type_id", :integer
+ t.column "end_time", :datetime
+ t.column "created_on", :datetime
+ t.column "appointment_state_id", :integer
+ end
+
+ create_table "healthcare_providers", :force => true do |t|
+ t.column "name", :string
+ t.column "default_appointment_type_id", :integer
+ t.column "created_on", :datetime
+ end
+
+ create_table "patients", :force => true do |t|
+ t.column "first_name", :string
+ t.column "surname", :string
+ t.column "created_on", :datetime
+ t.column "name", :string
+ end
+
+end
diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP
new file mode 100644
index 0000000..ac6c149
--- /dev/null
+++ b/doc/README_FOR_APP
@@ -0,0 +1,2 @@
+Use this README file to introduce your application and point to useful places in the API for learning more.
+Run "rake appdoc" to generate API documentation for your models and controllers.
\ No newline at end of file
diff --git a/lib/tasks/capistrano.rake b/lib/tasks/capistrano.rake
new file mode 100644
index 0000000..08642ac
--- /dev/null
+++ b/lib/tasks/capistrano.rake
@@ -0,0 +1,90 @@
+# =============================================================================
+# A set of rake tasks for invoking the Capistrano automation utility.
+# =============================================================================
+
+# Invoke the given actions via Capistrano
+def cap(*parameters)
+ begin
+ require 'rubygems'
+ rescue LoadError
+ # no rubygems to load, so we fail silently
+ end
+
+ require 'capistrano/cli'
+
+ Capistrano::CLI.new(parameters.map { |param| param.to_s }).execute!
+end
+
+namespace :remote do
+ desc "Removes unused releases from the releases directory."
+ task(:cleanup) { cap :cleanup }
+
+ desc "Used only for deploying when the spinner isn't running."
+ task(:cold_deploy) { cap :cold_deploy }
+
+ desc "A macro-task that updates the code, fixes the symlink, and restarts the application servers."
+ task(:deploy) { cap :deploy }
+
+ desc "Similar to deploy, but it runs the migrate task on the new release before updating the symlink."
+ task(:deploy_with_migrations) { cap :deploy_with_migrations }
+
+ desc "Displays the diff between HEAD and what was last deployed."
+ task(:diff_from_last_deploy) { cap :diff_from_last_deploy }
+
+ desc "Disable the web server by writing a \"maintenance.html\" file to the web servers."
+ task(:disable_web) { cap :disable_web }
+
+ desc "Re-enable the web server by deleting any \"maintenance.html\" file."
+ task(:enable_web) { cap :enable_web }
+
+ desc "A simple task for performing one-off commands that may not require a full task to be written for them."
+ task(:invoke) { cap :invoke }
+
+ desc "Run the migrate rake task."
+ task(:migrate) { cap :migrate }
+
+ desc "Restart the FCGI processes on the app server."
+ task(:restart) { cap :restart }
+
+ desc "A macro-task that rolls back the code and restarts the application servers."
+ task(:rollback) { cap :rollback }
+
+ desc "Rollback the latest checked-out version to the previous one by fixing the symlinks and deleting the current release from all servers."
+ task(:rollback_code) { cap :rollback_code }
+
+ desc "Set up the expected application directory structure on all boxes"
+ task(:setup) { cap :setup }
+
+ desc "Enumerate and describe every available task."
+ task(:show_tasks) { cap :show_tasks, '-q' }
+
+ desc "Start the spinner daemon for the application (requires script/spin)."
+ task(:spinner) { cap :spinner }
+
+ desc "Update the 'current' symlink to point to the latest version of the application's code."
+ task(:symlink) { cap :symlink }
+
+ desc "Update all servers with the latest release of the source code."
+ task(:update_code) { cap :update_code }
+
+ desc "Update the currently released version of the software directly via an SCM update operation"
+ task(:update_current) { cap :update_current }
+
+ desc "Execute a specific action using capistrano"
+ task :exec do
+ unless ENV['ACTION']
+ raise "Please specify an action (or comma separated list of actions) via the ACTION environment variable"
+ end
+
+ actions = ENV['ACTION'].split(",")
+ actions.concat(ENV['PARAMS'].split(" ")) if ENV['PARAMS']
+
+ cap(*actions)
+ end
+end
+
+desc "Push the latest revision into production (delegates to remote:deploy)"
+task :deploy => "remote:deploy"
+
+desc "Rollback to the release before the current release in production (delegates to remote:rollback)"
+task :rollback => "remote:rollback"
diff --git a/public/.htaccess b/public/.htaccess
new file mode 100644
index 0000000..b742fc4
--- /dev/null
+++ b/public/.htaccess
@@ -0,0 +1,40 @@
+# General Apache options
+AddHandler fastcgi-script .fcgi
+AddHandler cgi-script .cgi
+Options +FollowSymLinks +ExecCGI
+
+# If you don't want Rails to look in certain directories,
+# use the following rewrite rules so that Apache won't rewrite certain requests
+#
+# Example:
+# RewriteCond %{REQUEST_URI} ^/notrails.*
+# RewriteRule .* - [L]
+
+# Redirect all requests not available on the filesystem to Rails
+# By default the cgi dispatcher is used which is very slow
+#
+# For better performance replace the dispatcher with the fastcgi one
+#
+# 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 /appointments
+
+RewriteRule ^$ index.html [QSA]
+RewriteRule ^([^.]+)$ $1.html [QSA]
+RewriteCond %{REQUEST_FILENAME} !-f
+RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
+
+# In case Rails experiences terminal errors
+# Instead of displaying this message you can supply a file here which will be rendered instead
+#
+# Example:
+# ErrorDocument 500 /500.html
+
+ErrorDocument 500 "
Application error
Rails application failed to start properly"
\ No newline at end of file
diff --git a/public/404.html b/public/404.html
new file mode 100644
index 0000000..0e18456
--- /dev/null
+++ b/public/404.html
@@ -0,0 +1,8 @@
+
+
+
+
File not found
+
Change this error message for pages not found in public/404.html
+
+
\ No newline at end of file
diff --git a/public/500.html b/public/500.html
new file mode 100644
index 0000000..ab95f74
--- /dev/null
+++ b/public/500.html
@@ -0,0 +1,8 @@
+
+
+
+
Application error
+
Change this error message for exceptions thrown outside of an action (like in Dispatcher setups or broken Ruby code) in public/500.html
The DHTML Calendar widget1
+is an (HTML) user interface element that gives end-users a friendly way to
+select date and time. It works in a web browser. The first versions only provided
+support for popup calendars, while starting with version 0.9 it also supports
+``flat'' display. A ``flat'' calendar is a calendar that stays visible in the
+page all the time. In this mode it could be very useful for ``blog'' pages and
+other pages that require the calendar to be always present.
+
+The calendar is compatible with most popular browsers nowadays. While it's
+created using web standards and it should generally work with any compliant
+browser, the following browsers were found to work: Mozilla/Firefox (the
+development platform), Netscape 6.0 or better, all other Gecko-based browsers,
+Internet Explorer 5.0 or better for Windows2, Opera 73, Konqueror 3.1.2 and Apple Safari for
+MacOSX.
+
+You can find the latest info and version at the calendar homepage:
DHTML is not ``another kind of HTML''. It's merely a naming convention. DHTML
+refers to the combination of HTML, CSS, JavaScript and DOM. DOM (Document
+Object Model) is a set of interfaces that glues the other three together. In
+other words, DOM allows dynamic modification of an HTML page through a program.
+JavaScript is our programming language, since that's what browsers like. CSS
+is a way to make it look good ;-). So all this soup is generically known as
+DHTML.
+
+Using DOM calls, the program dynamically creates a <table> element
+that contains a calendar for the given date and then inserts it in the document
+body. Then it shows this table at a specified position. Usually the position
+is related to some element in which the date needs to be displayed/entered,
+such as an input field.
+
+By assigning a certain CSS class to the table we can control the look of the
+calendar through an external CSS file; therefore, in order to change the
+colors, backgrounds, rollover effects and other stuff, you can only change a
+CSS file -- modification of the program itself is not necessary.
Here's a description of the project files, excluding documentation and example
+files.
+
+
+
+
+
the main program file (calendar.js). This defines all the logic
+behind the calendar widget.
+
+
+
the CSS files (calendar-*.css). Loading one of them is
+necessary in order to see the calendar as intended.
+
+
+
the language definition files (lang/calendar-*.js). They are
+plain JavaScript files that contain all texts that are displayed by the
+calendar. Loading one of them is necessary.
+
+
+
helper functions for quick setup of the calendar
+(calendar-setup.js). You can do fine without it, but starting with
+version 0.9.3 this is the recommended way to setup a calendar.
+Installing the calendar used to be quite a task until version 0.9.3. Starting
+with 0.9.3 I have included the file calendar-setup.js whose goal is to
+assist you to setup a popup or flat calendar in minutes. You are
+encouraged to modify this file and not calendar.js if you need
+extra customization, but you're on your own.
+
+First you have to include the needed scripts and style-sheet. Make sure you do
+this in your document's <head> section, also make sure you put the
+correct paths to the scripts.
+You want the button to popup a calendar widget when clicked? Just
+insert the following code immediately after the HTML form:
+
+
+
<script type="text/javascript">
+ Calendar.setup(
+ {
+ inputField : "data", // ID of the input field
+ ifFormat : "%m %d, %Y", // the date format
+ button : "trigger" // ID of the button
+ }
+ );
+</script>
+
+
+The Calendar.setup function, defined in calendar-setup.js
+takes care of ``patching'' the button to display a calendar when clicked. The
+calendar is by default in single-click mode and linked with the given input
+field, so that when the end-user selects a date it will update the input field
+with the date in the given format and close the calendar. If you are a
+long-term user of the calendar you probably remember that for doing this you
+needed to write a couple functions and add an ``onclick'' handler for the
+button by hand.
+
+By looking at the example above we can see that the function
+Calendar.setup receives only one parameter: a JavaScript object.
+Further, that object can have lots of properties that tell to the setup
+function how would we like to have the calendar. For instance, if we would
+like a calendar that closes at double-click instead of single-click we would
+also include the following: singleClick:false.
+
+For a list of all supported parameters please see the section
+2.3.
+Here's how to configure a flat calendar, using the same Calendar.setup
+function. First, you should have an empty element with an ID. This element
+will act as a container for the calendar. It can be any block-level element,
+such as DIV, TABLE, etc. We will use a DIV in this example.
+
+
+
<div id="calendar-container"></div>
+
+
+Then there is the JavaScript code that sets up the calendar into the
+``calendar-container'' DIV. The code can occur anywhere in HTML
+after the DIV element.
+
+
+
<script type="text/javascript">
+ function dateChanged(calendar) {
+ // Beware that this function is called even if the end-user only
+ // changed the month/year. In order to determine if a date was
+ // clicked you can use the dateClicked property of the calendar:
+ if (calendar.dateClicked) {
+ // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
+ var y = calendar.date.getFullYear();
+ var m = calendar.date.getMonth(); // integer, 0..11
+ var d = calendar.date.getDate(); // integer, 1..31
+ // redirect...
+ window.location = "/" + y + "/" + m + "/" + d + "/index.php";
+ }
+ };
+
+ Calendar.setup(
+ {
+ flat : "calendar-container", // ID of the parent element
+ flatCallback : dateChanged // our callback function
+ }
+ );
+</script>
+
+Following there is the complete list of properties interpreted by
+Calendar.setup. All of them have default values, so you can pass only those
+which you would like to customize. Anyway, you must pass at least one
+of inputField, displayArea or button, for a popup
+calendar, or flat for a flat calendar. Otherwise you will get a
+warning message saying that there's nothing to setup.
+
+
+
property
type
description
default
+
+
inputField
+
string
The ID of your input field.
+
null
+
+
displayArea
+
string
This is the ID of a <span>, <div>, or any other element that you would like to use to display the current date. This is generally useful only if the input field is hidden, as an area to display the date.
+
null
+
+
button
+
string
The ID of the calendar ``trigger''. This is an element (ordinarily a button or an image) that will dispatch a certain event (usually ``click'') to the function that creates and displays the calendar.
+
null
+
+
eventName
+
string
The name of the event that will trigger the calendar. The name should be without the ``on'' prefix, such as ``click'' instead of ``onclick''. Virtually all users will want to let this have the default value (``click''). Anyway, it could be useful if, say, you want the calendar to appear when the input field is focused and have no trigger button (in this case use ``focus'' as the event name).
+
``click''
+
+
ifFormat
+
string
The format string that will be used to enter the date in the input field. This format will be honored even if the input field is hidden.
+
``%Y/%m/%d''
+
+
daFormat
+
string
Format of the date displayed in the displayArea (if specified).
+
``%Y/%m/%d''
+
+
singleClick
+
boolean
Wether the calendar is in ``single-click mode'' or ``double-click mode''. If true (the default) the calendar will be created in single-click mode.
+
true
+
+
disableFunc
+
function
A function that receives a JS Date object. It should return
+true if that date has to be disabled, false otherwise.
+DEPRECATED (see below).
+
null
+
+
dateStatusFunc
+
function
A function that receives a JS Date object and returns a boolean
+or a string. This function allows one to set a certain CSS class to some
+date, therefore making it look different. If it returns true then
+the date will be disabled. If it returns false nothing special
+happens with the given date. If it returns a string then that will be taken
+as a CSS class and appended to the date element. If this string is
+``disabled'' then the date is also disabled (therefore is like returning
+true). For more information please also refer to section
+5.3.8.
+
null
+
+
firstDay
+
integer
Specifies which day is to be displayed as the first day of
+week. Possible values are 0 to 6; 0 means Sunday, 1 means Monday, ..., 6
+means Saturday. The end user can easily change this too, by clicking on the
+day name in the calendar header.
+
0
+
+
weekNumbers
+
boolean
If ``true'' then the calendar will display week numbers.
+
true
+
+
align
+
string
Alignment of the calendar, relative to the reference element. The
+reference element is dynamically chosen like this: if a displayArea is
+specified then it will be the reference element. Otherwise, the input field
+is the reference element. For the meaning of the alignment characters
+please section 5.3.11.
+
``Bl''
+
+
range
+
array
An array having exactly 2 elements, integers. (!) The first [0] element is the minimum year that is available, and the second [1] element is the maximum year that the calendar will allow.
+
[1900, 2999]
+
+
flat
+
string
If you want a flat calendar, pass the ID of the parent object in
+this property. If not, pass null here (or nothing at all as
+null is the default value).
+
null
+
+
flatCallback
+
function
You should provide this function if the calendar is flat. It
+will be called when the date in the calendar is changed with a reference to
+the calendar object. See section 2.2 for an example
+of how to setup a flat calendar.
+
null
+
+
onSelect
+
function
If you provide a function handler here then you have to manage
+the ``click-on-date'' event by yourself. Look in the calendar-setup.js and
+take as an example the onSelect handler that you can see there.
+
null
+
+
onClose
+
function
This handler will be called when the calendar needs to close.
+You don't need to provide one, but if you do it's your responsibility to
+hide/destroy the calendar. You're on your own. Check the calendar-setup.js
+file for an example.
+
null
+
+
onUpdate
+
function
If you supply a function handler here, it will be called right
+after the target field is updated with a new date. You can use this to
+chain 2 calendars, for instance to setup a default date in the second just
+after a date was selected in the first.
+
null
+
+
date
+
date
This allows you to setup an initial date where the calendar will be
+positioned to. If absent then the calendar will open to the today date.
+
null
+
+
showsTime
+
boolean
If this is set to true then the calendar will also
+allow time selection.
+
false
+
+
timeFormat
+
string
Set this to ``12'' or ``24'' to configure the way that the
+calendar will display time.
+
``24''
+
+
electric
+
boolean
Set this to ``false'' if you want the calendar to update the
+field only when closed (by default it updates the field at each date change,
+even if the calendar is not closed)
true
+
+
position
+
array
Specifies the [x, y] position, relative to page's top-left corner,
+where the calendar will be displayed. If not passed then the position will
+be computed based on the ``align'' parameter. Defaults to ``null'' (not
+used).
null
+
+
cache
+
boolean
Set this to ``true'' if you want to cache the calendar object.
+This means that a single calendar object will be used for all fields that
+require a popup calendar
false
+
+
showOthers
+
boolean
If set to ``true'' then days belonging to months overlapping
+with the currently displayed month will also be displayed in the calendar
+(but in a ``faded-out'' color)
This section presents some common ways to setup a calendar using the
+Calendar.setup function detailed in the previous section.
+
+We don't discuss here about loading the JS or CSS code -- so make sure you
+add the proper <script> and <style> or <link> elements in your
+HTML code. Also, when we present input fields, please note that they should
+be embedded in some form in order for data to be actually sent to server; we
+don't discuss these things here because they are not related to our
+calendar.
+Note that this code does more actually; the only required fields are
+those marked with “//*” -- that is, the ID of the input field and the ID of
+the button need to be passed to Calendar.setup in order for the
+calendar to be properly assigned to this input field. As one can easily
+guess from the argument names, the other arguments configure a certain date
+format, instruct the calendar to also include a time selector and display
+every year in the drop-down boxes (the “step” parameter) -- instead of showing
+every other year as the default calendar does.
+Note that the same 2 parameters are required as in the previous case; the
+difference is that the 'button' parameter now gets the ID of the image
+instead of the ID of the button. But the event is the same: at 'onclick' on
+the element that is passed as 'button', the calendar will be shown.
+
+The above code additionally sets an alignment mode -- the parameters are
+described in 5.3.11.
Sometimes, to assure that the date is well formatted, you might want not to
+allow the end user to write a date manually. This can easily be achieved
+with an input field by setting its readonly attribute, which is
+defined by the HTML4 standard; however, here's an even nicer approach: our
+calendar widget allows you to use a hidden field as the way to pass data to
+server, and a “display area” to show the end user the selected date. The
+“display area” can be any HTML element, such as a DIV or a SPAN or
+whatever -- we will use a SPAN in our sample.
+The above code will configure a calendar attached to the hidden field and to
+the SPAN having the id=“show_d”. When the SPAN element is clicked, the
+calendar opens and allows the end user to chose a date. When the date is
+chosen, the input field will be updated with the value in the format
+“%Y/%d/%m”, and the SPAN element will display the date in a
+friendlier format (defined by “daFormat”).
+
+Beware that using this approach will make your page unfunctional in browsers
+that do not support JavaScript or our calendar.
Supposing you want to create 2 fields that hold an interval of exactly one
+week. The first is the starting date, and the second is the ending date.
+You want the fields to be automatically updated when some date is clicked in
+one or the other, in order to keep exactly one week difference between them.
+
+
+
<input type="text" name="date" id="f_date_a" />
+<input type="text" name="date" id="f_calcdate" />
+
+<script type="text/javascript">
+ function catcalc(cal) {
+ var date = cal.date;
+ var time = date.getTime()
+ // use the _other_ field
+ var field = document.getElementById("f_calcdate");
+ if (field == cal.params.inputField) {
+ field = document.getElementById("f_date_a");
+ time -= Date.WEEK; // substract one week
+ } else {
+ time += Date.WEEK; // add one week
+ }
+ var date2 = new Date(time);
+ field.value = date2.print("%Y-%m-%d %H:%M");
+ }
+ Calendar.setup({
+ inputField : "f_date_a",
+ ifFormat : "%Y-%m-%d %H:%M",
+ showsTime : true,
+ timeFormat : "24",
+ onUpdate : catcalc
+ });
+ Calendar.setup({
+ inputField : "f_calcdate",
+ ifFormat : "%Y-%m-%d %H:%M",
+ showsTime : true,
+ timeFormat : "24",
+ onUpdate : catcalc
+ });
+</script>
+
+
+The above code will configure 2 input fields with calendars attached, as
+usual. The first thing to note is that there's no trigger button -- in such
+case, the calendar will popup when one clicks into the input field. Using
+the onUpdate parameter, we pass a reference to a function of ours
+that will get called after a date was selected. In that function we
+determine what field was updated and we compute the date in the other input
+field such that it keeps a one week difference between the two. Enjoy! :-)
This sample can be found in “simple-2.html”. It will configure a
+flat calendar that is always displayed in the page, in the DIV having the
+id=“calendar-container”. When a date is clicked our function hander gets
+called (dateChanged) and it will compute an URL to jump to based on
+the selected date, then use window.location to visit the new link.
+
+
+
<div style="float: right; margin-left: 1em; margin-bottom: 1em;"
+id="calendar-container"></div>
+
+<script type="text/javascript">
+ function dateChanged(calendar) {
+ // Beware that this function is called even if the end-user only
+ // changed the month/year. In order to determine if a date was
+ // clicked you can use the dateClicked property of the calendar:
+ if (calendar.dateClicked) {
+ // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
+ var y = calendar.date.getFullYear();
+ var m = calendar.date.getMonth(); // integer, 0..11
+ var d = calendar.date.getDate(); // integer, 1..31
+ // redirect...
+ window.location = "/" + y + "/" + m + "/" + d + "/index.php";
+ }
+ };
+
+ Calendar.setup(
+ {
+ flat : "calendar-container", // ID of the parent element
+ flatCallback : dateChanged // our callback function
+ }
+ );
+</script>
+
So you want to display certain dates in a different color, or with bold
+font, or whatever, right? Well, no problem -- our calendar can do this as
+well. It doesn't matter if it's a flat or popup calendar -- we'll use a flat
+one for this sample. The idea, however, is that you need to have the dates
+in an array or a JavaScript object -- whatever is suitable for your way of
+thinking -- and use it from a function that returns a value, telling the
+calendar what kind of date is the passed one.
+
+Too much talking, here's the code ;-)
+
+
+
<!-- this goes into the <head> tag -->
+<style type="text/css">
+ .special { background-color: #000; color: #fff; }
+</style>
+
+<!-- and the rest inside the <body> -->
+<div style="float: right; margin-left: 1em; margin-bottom: 1em;"
+id="calendar-container"></div>
+
+<script type="text/javascript">
+ var SPECIAL_DAYS = {
+ 0 : [ 13, 24 ], // special days in January
+ 2 : [ 1, 6, 8, 12, 18 ], // special days in March
+ 8 : [ 21, 11 ] // special days in September
+ };
+
+ function dateIsSpecial(year, month, day) {
+ var m = SPECIAL_DAYS[month];
+ if (!m) return false;
+ for (var i in m) if (m[i] == day) return true;
+ return false;
+ };
+
+ function dateChanged(calendar) {
+ // Beware that this function is called even if the end-user only
+ // changed the month/year. In order to determine if a date was
+ // clicked you can use the dateClicked property of the calendar:
+ if (calendar.dateClicked) {
+ // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
+ var y = calendar.date.getFullYear();
+ var m = calendar.date.getMonth(); // integer, 0..11
+ var d = calendar.date.getDate(); // integer, 1..31
+ // redirect...
+ window.location = "/" + y + "/" + m + "/" + d + "/index.php";
+ }
+ };
+
+ function ourDateStatusFunc(date, y, m, d) {
+ if (dateIsSpecial(y, m, d))
+ return "special";
+ else
+ return false; // other dates are enabled
+ // return true if you want to disable other dates
+ };
+
+ Calendar.setup(
+ {
+ flat : "calendar-container", // ID of the parent element
+ flatCallback : dateChanged, // our callback function
+ dateStatusFunc : ourDateStatusFunc
+ }
+ );
+</script>
+
+
+So the above code creates a normal flat calendar, like in the previous
+sample. We hook into it with the function “ourDateStatusFunc”,
+which receives a date object as the first argument, and also the year,
+month, date as the next 3 arguments (normally, you can extract year, month,
+date from the first parameter too, but we pass them separately for
+convenience, as it's very likely that they are going to be used in this
+function).
+
+So, this function receives a date. It can return false if you want
+no special action to be taken on that date, true if that date
+should be disabled (unselectable), or a string if you want to assign a
+special CSS class to that date. We return “special” for the dates that we
+want to highlight -- and note that we defined a “special” look for them in
+the CSS section.
+
+I used a simple approach here to define what dates are special. There's a
+JavaScript object (the SPECIAL_DAYS global variable) which holds an array
+of dates for each month. Month numbers start at zero (January). Months
+that don't contain special dates can be absent from this object. Note that
+the way to implement this is completely separated from the calendar
+code -- therefore, feel free to use your imagination if you have better
+ideas. :-)
Starting version 1.0, the calendar is able to handle multiple dates
+selection. You just need to pass the “multiple” parameter to
+Calendar.setup and add some special code that interprets the
+selection once the calendar is closed.
+
+
+
<a id="trigger" href="#">[open calendar...]</a>
+<div id="output"></div>
+<script type="text/javascript">//<![CDATA[
+ // the default multiple dates selected,
+ // first time the calendar is displayed
+ var MA = [];
+
+ function closed(cal) {
+
+ // here we'll write the output; this is only for example. You
+ // will normally fill an input field or something with the dates.
+ var el = document.getElementById("output");
+
+ // reset initial content.
+ el.innerHTML = "";
+
+ // Reset the "MA", in case one triggers the calendar again.
+ // CAREFUL! You don't want to do "MA = [];". We need to modify
+ // the value of the current array, instead of creating a new one.
+ // Calendar.setup is called only once! :-) So be careful.
+ MA.length = 0;
+
+ // walk the calendar's multiple dates selection hash
+ for (var i in cal.multiple) {
+ var d = cal.multiple[i];
+ // sometimes the date is not actually selected,
+ // so let's check
+ if (d) {
+ // OK, selected. Fill an input field or something.
+ el.innerHTML += d.print("%A, %Y %B %d") + "<br />";
+ // and push it in the "MA", in case one triggers the calendar again.
+ MA[MA.length] = d;
+ }
+ }
+ cal.hide();
+ return true;
+ };
+
+ Calendar.setup({
+ align : "BR",
+ showOthers : true,
+ multiple : MA, // pass the initial or computed array of multiple dates
+ onClose : closed,
+ button : "trigger"
+ });
+//]]></script>
+
+
+The above code creates a popup calendar and passes to it an array of dates,
+which is initially empty, in the “multiple” argument. When the calendar is
+closed it will call our “closed” function handler; in this handler
+we determine what dates were actually selected, inspecting the
+“cal.multiple” property, we display them in a DIV element right
+next to the <a> element that opens the calendar, and we reinitialize the
+global array of selected dates (which will be used if the end user opens the
+calendar again). I guess the code speaks for itself, right? :-)
+Basically you should be able to setup the calendar with the function presented
+in the previous section. However, if for some reason Calendar.setup
+doesn't provide all the functionality that you need and you want to tweak into
+the process of creating and configuring the calendar ``by hand'', then this
+section is the way to go.
+
+The file calendar.js implements the functionality of the calendar.
+All (well, almost all) functions and variables are embedded in the JavaScript
+object ``Calendar''.
+
+You can instantiate a Calendar object by calling the constructor, like
+this: var cal = new Calendar(...). We will discuss the parameters
+later. After creating the object, the variable cal will contain a
+reference to it. You can use this reference to access further options of the
+calendar, for instance:
+
+
+
cal.weekNumbers = false; // do not display week numbers
+cal.showsTime = true; // include a time selector
+cal.setDateFormat("%Y.%m.%d %H:%M"); // set this format: 2003.12.31 23:59
+cal.setDisabledHandler(function(date, year, month, day) {
+ // verify date and return true if it has to be disabled
+ // ``date'' is a JS Date object, but if you only need the
+ // year, month and/or day you can get them separately as
+ // next 3 parameters, as you can see in the declaration
+ if (year == 2004) {
+ // disable all dates from 2004
+ return true;
+ }
+ return false;
+});
+
+
+etc. Prior to version
+0.9.3 this was the only way to configure it. The Calendar.setup
+function, documented in section 2, basically does the same
+things (actually more) in order to setup the calendar, based on the parameters
+that you provided.
The calendar is created by following some steps (even the function
+Calendar.setup, described in section 2, does the
+same). While you can skip optional (marked ``opt'') steps if you're happy with
+the defaults, please respect the order below.
+
+
+
+
+
Instantiate a Calendar object. Details about this in
+section 5.1.
+
+
+
opt Set the weekNumbers property to false if you don't want
+the calendar to display week numbers.
+
+
+
opt Set the showsTime property to true if you
+want the calendar to also provide a time selector.
+
+
+
opt Set the time24 property to false if you want
+the time selector to be in 12-hour format. Default is 24-hour format. This
+property only has effect if you also set showsTime to
+true.
+
+
+
opt Set the range of years available for selection (see section
+5.3.15). The default range is [1970..2050].
+
+
+
opt Set the getDateStatus property. You should pass
+here a function that receives a JavaScript Date object and returns
+true if the given date should be disabled, false otherwise (details in
+section 5.3.7).
+
+
+
opt Set a date format. Your handler function, passed to the
+calendar constructor, will be called when a date is selected with a reference
+to the calendar and a date string in this format.
+
+
+
Create the HTML elements related to the calendar. This step
+practically puts the calendar in your HTML page. You simply call
+Calendar.create(). You can give an optional parameter if you wanna
+create a flat calendar (details in section 5.3.1).
+
+
+
opt Initialize the calendar to a certain date, for instance from
+the input field.
As you could see in the previous section, there are more steps to be followed
+in order to setup the calendar. This happens because there are two different
+things that need to be accomplished: first there is the JavaScript object, that
+is created with new Calendar(...). Secondly there are the HTML
+elements that actually lets you see and manipulate the calendar.
+
+
+[ Those that did UI4 programming, no matter in what
+language and on what platform, may be familiar with this concept. First there
+is the object in memory that lets you manipulate the UI element, and secondly
+there is the UI element (known as ``control'', ``window'', ``widget'', etc.),
+also in memory but you don't usually access it directly. ]
+
+By instantiating the calendar we create the JavaScript object. It lets us
+configure some properties and it also knows how to create the UI element (the
+HTML elements actually) that will eventually be what the end-user sees on
+screen. Creation of the HTML element is accomplished by the function
+Calendar.create. It knows how to create popup or flat calendars.
+This function is described in section 5.3.1.
+
+Some properties need to be set prior to creating the HTML elements, because
+otherwise they wouldn't have any effect. Such a property is
+weekNumbers -- it has the default value ``true'', and if you don't
+want the calendar to display the week numbers you have to set it to false. If,
+however, you do that after calling Calendar.create the calendar
+would still display the week numbers, because the HTML elements are already
+created (including the <td>-s in the <table> element that
+should contain the week numbers). For this reason the order of the steps above
+is important.
+
+Another example is when you want to show the calendar. The ``create'' function
+does create the HTML elements, but they are initially hidden (have the style
+``display: none'') unless the calendar is a flat calendar that should be always
+visible in the page. Obviously, the Calendar.show function should be
+called after calling Calendar.create.
Suppose the end-user has popped up a calendar and selects a date. The calendar
+then closes. What really happens now?
+
+There are two approaches. The first (used in very old versions of the
+calendar) was to drop completely the Calendar object and when the end-user pops
+up the calendar again to create another one. This approach is bad for more
+reasons:
+
+
+
+
+
creating the JavaScript object and HTML elements is time-consuming
+
+
+
we may loose some end-user preferences (i.e. he might prefer to have
+Monday for the first day of week and probably already clicked it the first time
+when the calendar was opened, but now he has to do it again)
+
+
+
+
+The second approach, implemented by the Calendar.setup function, is to
+cache the JavaScript object. It does this by checking the global variable
+window.calendar and if it is not null it assumes it is the created
+Calendar object. When the end-user closes the calendar, our code will only
+call ``hide'' on it, therefore keeping the JavaScript object and the
+HTML elements in place.
+
+CAVEAT: Since time selection support was introduced, this
+``object caching'' mechanism has the following drawback: if you once created
+the calendar with the time selection support, then other items that may not
+require this functionality will still get a calendar with the time selection
+support enabled. And reciprocal. ;-) Hopefully this will be corrected in a
+later version, but for now it doesn't seem such a big problem.
You might rightfully wonder how is the calendar related to the input field?
+Who tells it that it has to update that input field when a date is
+selected, or that it has to jump to that URL when a date is clicked in
+flat mode?
+
+All this magic is done through callback functions. The calendar doesn't know
+anything about the existence of an input field, nor does it know where to
+redirect the browser when a date is clicked in flat mode. It just calls your
+callback when a particular event is happening, and you're responsible to handle
+it from there. For a general purpose library I think this is the best model of
+making a truly reusable thing.
+
+The calendar supports the following user callbacks:
+
+
+
+
+
onSelect -- this gets called when the end-user changes the date in the
+calendar. Documented in section 5.1.
+
+
+
onClose -- this gets called when the calendar should close. It's
+user's responsibility to close the calendar. Details in section
+5.1.
+
+
+
getDateStatus -- this function gets called for any day in a month,
+just before displaying the month. It is called with a JavaScript Date
+object and should return true if that date should be disabled, false
+if it's an ordinary date and no action should be taken, or it can return a
+string in which case the returned value will be appended to the element's CSS
+class (this way it provides a powerful way to make some dates ``special'',
+i.e. highlight them differently). Details in section
+5.3.8.
var calendar = Calendar(firstDayOfWeek, date, onSelect, onClose);
+
+
+Parameters are as follows:
+
+
+
+
+
firstDayOfWeek -- specifies which day is to be displayed as the first
+day of week. Possible values are 0 to 6; 0 means Sunday, 1 means Monday,
+..., 6 means Saturday.
+
+
+
date -- a JavaScript Date object or null. If null
+is passed then the calendar will default to today date. Otherwise it will
+initialize on the given date.
+
+
+
onSelect -- your callback for the ``onChange'' event. See above.
+
+
+
onClose -- your callback for the ``onClose'' event. See above.
+Here is a typical implementation of this function:
+
+
+
function onSelect(calendar, date) {
+ var input_field = document.getElementById("date");
+ input_field.value = date;
+};
+
+
+date is in the format selected with calendar.setDateFormat
+(see section 5.3.5). This code simply updates the
+input field. If you want the calendar to be in single-click mode then you
+should also close the calendar after you updated the input field, so we come to
+the following version:
+
+
+
function onSelect(calendar, date) {
+ var input_field = document.getElementById("date");
+ input_field.value = date;
+ if (calendar.dateClicked) {
+ calendar.callCloseHandler(); // this calls "onClose" (see above)
+ }
+};
+
+
+Note that we checked the member variable dateClicked and
+only hide the calendar if it's true. If this variable is false it
+means that no date was actually selected, but the user only changed the
+month/year using the navigation buttons or the menus. We don't want to hide
+the calendar in that case.
+This event is triggered when the calendar should close. It should hide or
+destroy the calendar object -- the calendar itself just triggers the event, but
+it won't close itself.
+
+A typical implementation of this function is the following:
+
+
+
function onClose(calendar) {
+ calendar.hide();
+ // or calendar.destroy();
+};
+
+After creating the Calendar object you can access the following properties:
+
+
+
+
+
date -- is a JavaScript Date object. It will always
+reflect the date shown in the calendar (yes, even if the calendar is hidden).
+
+
+
isPopup -- if this is true then the current Calendar object is
+a popup calendar. Otherwise (false) we have a flat calendar. This variable is
+set from Calendar.create and has no meaning before this function was
+called.
+
+
+
dateClicked -- particularly useful in the onSelect
+handler, this variable tells us if a date was really clicked. That's because
+the onSelect handler is called even if the end-user only changed the
+month/year but did not select a date. We don't want to close the calendar in
+that case.
+
+
+
weekNumbers -- if true (default) then the calendar
+displays week numbers. If you don't want week numbers you have to set this
+variable to falsebefore calling Calendar.create.
+
+
+
showsTime - if you set this to true (it is
+false by default) then the calendar will also include a time selector.
+
+
+
time24 - if you set this to false then the time
+selector will be in 12-hour format. It is in 24-hour format by default.
+
+
+
firstDayOfWeek -- specifies the first day of week (0 to 6, pass
+0 for Sunday, 1 for Monday, ..., 6 for Saturday). This variable is set from
+constructor, but you still have a chance to modify it before calling
+Calendar.create.
+
+
+
+
+There are lots of other member variables, but one should access them only
+through member functions so I won't document them here.
+This function creates the afferent HTML elements that are needed to display the
+calendar. You should call it after setting the calendar properties. Synopsis:
+
+
calendar.create(); // creates a popup calendar
+ // -- or --
+calendar.create(document.getElementById(parent_id)); // makes a flat calendar
+
+
+It can create a popup calendar or a flat calendar. If the ``parent'' argument
+is present (it should be a reference -- not ID -- to an HTML element) then
+a flat calendar is created and it is inserted in the given element.
+
+At any moment, given a reference to a calendar object, we can inspect if it's a
+popup or a flat calendar by checking the boolean member variable
+isPopup:
+
+
+
if (calendar.isPopup) {
+ // this is a popup calendar
+} else {
+ // this is a flat calendar
+}
+
+This function calls the second user callback (the
+onClose handler). It's useful when you want to have a
+``single-click'' calendar -- just call this in your onSelect handler,
+if a date was clicked.
+Call this function to hide the calendar. The calendar object and HTML elements
+will not be destroyed, thus you can later call one of the show
+functions on the same element.
+This function configures the format in which the calendar reports the date to
+your ``onSelect'' handler. Call it like this:
+
+
+
calendar.setDateFormat("%y/%m/%d");
+
+
+As you can see, it receives only one parameter, the required format. The magic
+characters are the following:
+
+
+
+
%a
abbreviated weekday name
+
%A
full weekday name
+
%b
abbreviated month name
+
%B
full month name
+
%C
century number
+
%d
the day of the month ( 00 .. 31 )
+
%e
the day of the month ( 0 .. 31 )
+
%H
hour ( 00 .. 23 )
+
%I
hour ( 01 .. 12 )
+
%j
day of the year ( 000 .. 366 )
+
%k
hour ( 0 .. 23 )
+
%l
hour ( 1 .. 12 )
+
%m
month ( 01 .. 12 )
+
%M
minute ( 00 .. 59 )
+
%n
a newline character
+
%p
``PM'' or ``AM''
+
%P
``pm'' or ``am''
+
%S
second ( 00 .. 59 )
+
%s
number of seconds since Epoch (since Jan 01 1970 00:00:00 UTC)
+
%t
a tab character
+
%U, %W, %V
the week number
+
%u
the day of the week ( 1 .. 7, 1 = MON )
+
%w
the day of the week ( 0 .. 6, 0 = SUN )
+
%y
year without the century ( 00 .. 99 )
+
%Y
year including the century ( ex. 1979 )
+
%%
a literal % character
+
+There are more algorithms for computing the week number. All
+three specifiers currently implement the same one, as defined by ISO 8601:
+``the week 01 is the week that has the Thursday in the current year, which is
+equivalent to the week that contains the fourth day of January. Weeks start on
+Monday.''
+Has the same prototype as Calendar.setDateFormat, but refers to the
+format of the date displayed in the ``status bar'' when the mouse is over some
+date.
+This function allows you to specify a callback function that checks if a
+certain date must be disabled by the calendar. You are responsible to write
+the callback function. Synopsis:
+
+
+
function disallowDate(date) {
+ // date is a JS Date object
+ if ( date.getFullYear() == 2003 &&
+ date.getMonth() == 6 /* July, it's zero-based */ &&
+ date.getDate() == 5 ) {
+ return true; // disable July 5 2003
+ }
+ return false; // enable other dates
+};
+
+calendar.setDisabledHandler(disallowDate);
+
+
+If you change this function in ``real-time'', meaning, without creating a new
+calendar, then you have to call calendar.refresh() to make it
+redisplay the month and take into account the new disabledHandler.
+Calendar.setup does this, so you have no such trouble with it.
+
+Note that disallowDate should be very fast, as it is called for each
+date in the month. Thus, it gets called, say, 30 times before displaying the
+calendar, and 30 times when the month is changed. Tests I've done so far show
+that it's still good, but in the future I might switch it to a different design
+(for instance, to call it once per month and to return an array of dates that
+must be disabled).
+
+This function should be considered deprecated in the favor of
+Calendar.setDateStatusHandler, described below.
+This function obsoletes Calendar.setDisabledHandler. You call it with
+a function parameter, but this function can return a boolean
+or a string. If the return value is a boolean (true or
+false) then it behaves just like setDisabledHandler,
+therefore disabling the date if the return value is true.
+
+If the returned value is a string then the given date will gain an additional
+CSS class, namely the returned value. You can use this to highlight some dates
+in some way. Note that you are responsible for defining the CSS class that you
+return. If you return the string ``disabled'' then that date will be disabled,
+just as if you returned true.
+
+Here is a simple scenario that shows what you can do with this function. The
+following should be present in some of your styles, or in the document head in
+a STYLE tag (but put it after the place where the calendar styles were
+loaded):
+And you would use the following code before calling Calendar.create():
+
+
+
// this table holds your special days, so that we can automatize
+// things a bit:
+var SPECIAL_DAYS = {
+ 0 : [ 13, 24 ], // special days in January
+ 2 : [ 1, 6, 8, 12, 18 ], // special days in March
+ 8 : [ 21, 11 ], // special days in September
+ 11 : [ 25, 28 ] // special days in December
+};
+
+// this function returns true if the passed date is special
+function dateIsSpecial(year, month, day) {
+ var m = SPECIAL_DAYS[month];
+ if (!m) return false;
+ for (var i in m) if (m[i] == day) return true;
+ return false;
+}
+
+// this is the actual date status handler. Note that it receives the
+// date object as well as separate values of year, month and date, for
+// your confort.
+function dateStatusHandler(date, y, m, d) {
+ if (dateIsSpecial(y, m, d)) return ``special'';
+ else return false;
+ // return true above if you want to disable other dates
+}
+
+// configure it to the calendar
+calendar.setDateStatusHandler(dateStatusHandler);
+
+
+The above code adds the ``special'' class name to some dates that are defined
+in the SPECIAL_DAYS table. Other dates will simply be displayed as default,
+enabled.
+This function is useful if you want to display the calendar near some element.
+You call it like this:
+
+
+
calendar.showAtElement(element, align);
+
+
+where element is a reference to your element (for instance it can be the input
+field that displays the date) and align is an optional parameter, of type string,
+containing one or two characters. For instance, if you pass "Br" as
+align, the calendar will appear below the element and with its right
+margin continuing the element's right margin.
+
+As stated above, align may contain one or two characters. The first character
+dictates the vertical alignment, relative to the element, and the second
+character dictates the horizontal alignment. If the second character is
+missing it will be assumed "l" (the left margin of the calendar will
+be at the same horizontal position as the left margin of the element).
+
+The characters given for the align parameters are case sensitive. This
+function only makes sense when the calendar is in popup mode. After computing
+the position it uses Calendar.showAt to display the calendar there.
+Use this function to parse a date given as string and to move the calendar to
+that date.
+
+The algorithm tries to parse the date according to the format that was
+previously set with Calendar.setDateFormat; if that fails, it still
+tries to get some valid date out of it (it doesn't read your thoughts, though).
The calendar code was intentionally embedded in an object to make it have as
+less as possible side effects. However, there are some -- not harmful, after
+all. Here is a list of side effects; you can count they already happened after
+calendar.js was loaded.
+
+
+
+
+
The global variable window.calendar will be set to null. This
+variable is used by the calendar code, especially when doing drag & drop for
+moving the calendar. In the future I might get rid of it, but for now it
+didn't harm anyone.
+
+
+
The JavaScript Date object is modified. We add some properties
+and functions that are very useful to our calendar. It made more sense to add
+them directly to the Date object than to the calendar itself.
+Complete list:
+
+
+
+
+
Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+
Date.SECOND = 1000 /* milliseconds */;
+
+
Date.MINUTE = 60 * Date.SECOND;
+
+
Date.HOUR = 60 * Date.MINUTE;
+
+
Date.DAY = 24 * Date.HOUR;
+
+
Date.WEEK = 7 * Date.DAY;
+
+
+
Date.prototype.getMonthDays(month) -- returns the number of days
+of the given month, or of the current date object if no month was given.
+
+
+
Date.prototype.getWeekNumber() -- returns the week number of the
+date in the current object.
+
+
+
Date.prototype.equalsTo(other_date) -- compare the current date
+object with other_date and returns true if the dates are
+equal. It ignores time.
+
+
+
Date.prototype.print(format) -- returns a string with the
+current date object represented in the given format. It implements the format
+specified in section 5.3.5.
The following people either sponsored, donated money to the project or bought
+commercial licenses (listed in reverse chronological order). Your name could
+be here too! If you wish to sponsor the project (for instance request a
+feature and pay me for implementing it) or donate some money please
+please contact me at mihai_bazon@yahoo.com.
1
+by the term ``widget'' I understand a single element of user interface.
+But that's in Linux world. For those that did lots of Windows
+programming the term ``control'' might be more familiar
+
+
2 people report that the calendar does
+not work with IE5/Mac. However, this browser was discontinued and we
+believe that supporting it doesn't worth the efforts, given the fact that
+it has the worst, buggiest implementation for DOM I've ever seen.
+
3 under Opera 7 the calendar still lacks some functionality, such as
+keyboard navigation; also Opera doesn't seem to allow disabling text
+selection when one drags the mouse on the page; despite all that, the
+calendar is still highly functional under Opera 7 and looks as good as
+in other supported browsers.
+Last modified: Saturday, March 5th, 2005
+HTML conversion by TeX2page 2004-09-11
+
+
+
diff --git a/public/bundles/dynarch_calendar/doc/reference.pdf b/public/bundles/dynarch_calendar/doc/reference.pdf
new file mode 100644
index 0000000..a09497f
Binary files /dev/null and b/public/bundles/dynarch_calendar/doc/reference.pdf differ
diff --git a/public/bundles/dynarch_calendar/images/calendar_icon1.gif b/public/bundles/dynarch_calendar/images/calendar_icon1.gif
new file mode 100755
index 0000000..cb9aee7
Binary files /dev/null and b/public/bundles/dynarch_calendar/images/calendar_icon1.gif differ
diff --git a/public/bundles/dynarch_calendar/images/calendar_icon2.gif b/public/bundles/dynarch_calendar/images/calendar_icon2.gif
new file mode 100644
index 0000000..8680b8f
Binary files /dev/null and b/public/bundles/dynarch_calendar/images/calendar_icon2.gif differ
diff --git a/public/bundles/dynarch_calendar/images/calendar_icon3.gif b/public/bundles/dynarch_calendar/images/calendar_icon3.gif
new file mode 100644
index 0000000..a549652
Binary files /dev/null and b/public/bundles/dynarch_calendar/images/calendar_icon3.gif differ
diff --git a/public/bundles/dynarch_calendar/images/calendar_icon4.gif b/public/bundles/dynarch_calendar/images/calendar_icon4.gif
new file mode 100755
index 0000000..d7538cb
Binary files /dev/null and b/public/bundles/dynarch_calendar/images/calendar_icon4.gif differ
diff --git a/public/bundles/dynarch_calendar/images/menuarrow.gif b/public/bundles/dynarch_calendar/images/menuarrow.gif
new file mode 100755
index 0000000..ed2dee0
Binary files /dev/null and b/public/bundles/dynarch_calendar/images/menuarrow.gif differ
diff --git a/public/bundles/dynarch_calendar/images/menuarrow2.gif b/public/bundles/dynarch_calendar/images/menuarrow2.gif
new file mode 100644
index 0000000..40c0aad
Binary files /dev/null and b/public/bundles/dynarch_calendar/images/menuarrow2.gif differ
diff --git a/public/bundles/dynarch_calendar/images/pixel.gif b/public/bundles/dynarch_calendar/images/pixel.gif
new file mode 100755
index 0000000..b740647
Binary files /dev/null and b/public/bundles/dynarch_calendar/images/pixel.gif differ
diff --git a/public/bundles/dynarch_calendar/javascripts/calendar-en.js b/public/bundles/dynarch_calendar/javascripts/calendar-en.js
new file mode 100755
index 0000000..0dbde79
--- /dev/null
+++ b/public/bundles/dynarch_calendar/javascripts/calendar-en.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar EN language
+// Author: Mihai Bazon,
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 0;
+
+// full month names
+Calendar._MN = new Array
+("January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "About the calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Date selection:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
+"- Hold mouse button on any of the above buttons for faster selection.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- Click on any of the time parts to increase it\n" +
+"- or Shift-click to decrease it\n" +
+"- or click and drag for faster selection.";
+
+Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Select date";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
+Calendar._TT["PART_TODAY"] = " (today)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Display %s first";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "Today";
+Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/public/bundles/dynarch_calendar/javascripts/calendar-setup.js b/public/bundles/dynarch_calendar/javascripts/calendar-setup.js
new file mode 100755
index 0000000..f2b4854
--- /dev/null
+++ b/public/bundles/dynarch_calendar/javascripts/calendar-setup.js
@@ -0,0 +1,200 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ---------------------------------------------------------------------------
+ *
+ * The DHTML Calendar
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ *
+ * This file defines helper functions for setting up the calendar. They are
+ * intended to help non-programmers get a working calendar on their site
+ * quickly. This script should not be seen as part of the calendar. It just
+ * shows you what one can do with the calendar, while in the same time
+ * providing a quick and simple method for setting it up. If you need
+ * exhaustive customization of the calendar creation process feel free to
+ * modify this code to suit your needs (this is recommended and much better
+ * than modifying calendar.js itself).
+ */
+
+// $Id: calendar-setup.js,v 1.25 2005/03/07 09:51:33 mishoo Exp $
+
+/**
+ * This function "patches" an input field (or other element) to use a calendar
+ * widget for date selection.
+ *
+ * The "params" is a single object that can have the following properties:
+ *
+ * prop. name | description
+ * -------------------------------------------------------------------------------------------------
+ * inputField | the ID of an input field to store the date
+ * displayArea | the ID of a DIV or other element to show the date
+ * button | ID of a button or other element that will trigger the calendar
+ * eventName | event that will trigger the calendar, without the "on" prefix (default: "click")
+ * ifFormat | date format that will be stored in the input field
+ * daFormat | the date format that will be used to display the date in displayArea
+ * singleClick | (true/false) wether the calendar is in single click mode or not (default: true)
+ * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc.
+ * align | alignment (default: "Br"); if you don't know what's this see the calendar documentation
+ * range | array with 2 elements. Default: [1900, 2999] -- the range of years available
+ * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers
+ * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID
+ * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar)
+ * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar
+ * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay)
+ * onClose | function that gets called when the calendar is closed. [default]
+ * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar.
+ * date | the date that the calendar will be initially displayed to
+ * showsTime | default: false; if true the calendar will include a time selector
+ * timeFormat | the time format; can be "12" or "24", default is "12"
+ * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close
+ * step | configures the step of the years in drop-down boxes; default: 2
+ * position | configures the calendar absolute position; default: null
+ * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible
+ * showOthers | if "true" (but default: "false") it will show days from other months too
+ *
+ * None of them is required, they all have default values. However, if you
+ * pass none of "inputField", "displayArea" or "button" you'll get a warning
+ * saying "nothing to setup".
+ */
+Calendar.setup = function (params) {
+ function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
+
+ param_default("inputField", null);
+ param_default("displayArea", null);
+ param_default("button", null);
+ param_default("eventName", "click");
+ param_default("ifFormat", "%Y/%m/%d");
+ param_default("daFormat", "%Y/%m/%d");
+ param_default("singleClick", true);
+ param_default("disableFunc", null);
+ param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined
+ param_default("dateText", null);
+ param_default("firstDay", null);
+ param_default("align", "Br");
+ param_default("range", [1900, 2999]);
+ param_default("weekNumbers", true);
+ param_default("flat", null);
+ param_default("flatCallback", null);
+ param_default("onSelect", null);
+ param_default("onClose", null);
+ param_default("onUpdate", null);
+ param_default("date", null);
+ param_default("showsTime", false);
+ param_default("timeFormat", "24");
+ param_default("electric", true);
+ param_default("step", 2);
+ param_default("position", null);
+ param_default("cache", false);
+ param_default("showOthers", false);
+ param_default("multiple", null);
+
+ var tmp = ["inputField", "displayArea", "button"];
+ for (var i in tmp) {
+ if (typeof params[tmp[i]] == "string") {
+ params[tmp[i]] = document.getElementById(params[tmp[i]]);
+ }
+ }
+ if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) {
+ alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");
+ return false;
+ }
+
+ function onSelect(cal) {
+ var p = cal.params;
+ var update = (cal.dateClicked || p.electric);
+ if (update && p.inputField) {
+ p.inputField.value = cal.date.print(p.ifFormat);
+ if (typeof p.inputField.onchange == "function")
+ p.inputField.onchange();
+ }
+ if (update && p.displayArea)
+ p.displayArea.innerHTML = cal.date.print(p.daFormat);
+ if (update && typeof p.onUpdate == "function")
+ p.onUpdate(cal);
+ if (update && p.flat) {
+ if (typeof p.flatCallback == "function")
+ p.flatCallback(cal);
+ }
+ if (update && p.singleClick && cal.dateClicked)
+ cal.callCloseHandler();
+ };
+
+ if (params.flat != null) {
+ if (typeof params.flat == "string")
+ params.flat = document.getElementById(params.flat);
+ if (!params.flat) {
+ alert("Calendar.setup:\n Flat specified but can't find parent.");
+ return false;
+ }
+ var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect);
+ cal.showsOtherMonths = params.showOthers;
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.params = params;
+ cal.weekNumbers = params.weekNumbers;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.getDateText = params.dateText;
+ if (params.ifFormat) {
+ cal.setDateFormat(params.ifFormat);
+ }
+ if (params.inputField && typeof params.inputField.value == "string") {
+ cal.parseDate(params.inputField.value);
+ }
+ cal.create(params.flat);
+ cal.show();
+ return false;
+ }
+
+ var triggerEl = params.button || params.displayArea || params.inputField;
+ triggerEl["on" + params.eventName] = function() {
+ var dateEl = params.inputField || params.displayArea;
+ var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
+ var mustCreate = false;
+ var cal = window.calendar;
+ if (dateEl)
+ params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
+ if (!(cal && params.cache)) {
+ window.calendar = cal = new Calendar(params.firstDay,
+ params.date,
+ params.onSelect || onSelect,
+ params.onClose || function(cal) { cal.hide(); });
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.weekNumbers = params.weekNumbers;
+ mustCreate = true;
+ } else {
+ if (params.date)
+ cal.setDate(params.date);
+ cal.hide();
+ }
+ if (params.multiple) {
+ cal.multiple = {};
+ for (var i = params.multiple.length; --i >= 0;) {
+ var d = params.multiple[i];
+ var ds = d.print("%Y%m%d");
+ cal.multiple[ds] = d;
+ }
+ }
+ cal.showsOtherMonths = params.showOthers;
+ cal.yearStep = params.step;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.params = params;
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.getDateText = params.dateText;
+ cal.setDateFormat(dateFmt);
+ if (mustCreate)
+ cal.create();
+ cal.refresh();
+ if (!params.position)
+ cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
+ else
+ cal.showAt(params.position[0], params.position[1]);
+ return false;
+ };
+
+ return cal;
+};
diff --git a/public/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js b/public/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js
new file mode 100755
index 0000000..91c927f
--- /dev/null
+++ b/public/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js
@@ -0,0 +1,21 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ---------------------------------------------------------------------------
+ *
+ * The DHTML Calendar
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ *
+ * This file defines helper functions for setting up the calendar. They are
+ * intended to help non-programmers get a working calendar on their site
+ * quickly. This script should not be seen as part of the calendar. It just
+ * shows you what one can do with the calendar, while in the same time
+ * providing a quick and simple method for setting it up. If you need
+ * exhaustive customization of the calendar creation process feel free to
+ * modify this code to suit your needs (this is recommended and much better
+ * than modifying calendar.js itself).
+ */
+ Calendar.setup=function(params){function param_default(pname,def){if(typeof params[pname]=="undefined"){params[pname]=def;}};param_default("inputField",null);param_default("displayArea",null);param_default("button",null);param_default("eventName","click");param_default("ifFormat","%Y/%m/%d");param_default("daFormat","%Y/%m/%d");param_default("singleClick",true);param_default("disableFunc",null);param_default("dateStatusFunc",params["disableFunc"]);param_default("dateText",null);param_default("firstDay",null);param_default("align","Br");param_default("range",[1900,2999]);param_default("weekNumbers",true);param_default("flat",null);param_default("flatCallback",null);param_default("onSelect",null);param_default("onClose",null);param_default("onUpdate",null);param_default("date",null);param_default("showsTime",false);param_default("timeFormat","24");param_default("electric",true);param_default("step",2);param_default("position",null);param_default("cache",false);param_default("showOthers",false);param_default("multiple",null);var tmp=["inputField","displayArea","button"];for(var i in tmp){if(typeof params[tmp[i]]=="string"){params[tmp[i]]=document.getElementById(params[tmp[i]]);}}if(!(params.flat||params.multiple||params.inputField||params.displayArea||params.button)){alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");return false;}function onSelect(cal){var p=cal.params;var update=(cal.dateClicked||p.electric);if(update&&p.inputField){p.inputField.value=cal.date.print(p.ifFormat);if(typeof p.inputField.onchange=="function")p.inputField.onchange();}if(update&&p.displayArea)p.displayArea.innerHTML=cal.date.print(p.daFormat);if(update&&typeof p.onUpdate=="function")p.onUpdate(cal);if(update&&p.flat){if(typeof p.flatCallback=="function")p.flatCallback(cal);}if(update&&p.singleClick&&cal.dateClicked)cal.callCloseHandler();};if(params.flat!=null){if(typeof params.flat=="string")params.flat=document.getElementById(params.flat);if(!params.flat){alert("Calendar.setup:\n Flat specified but can't find parent.");return false;}var cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect);cal.showsOtherMonths=params.showOthers;cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.params=params;cal.weekNumbers=params.weekNumbers;cal.setRange(params.range[0],params.range[1]);cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;if(params.ifFormat){cal.setDateFormat(params.ifFormat);}if(params.inputField&&typeof params.inputField.value=="string"){cal.parseDate(params.inputField.value);}cal.create(params.flat);cal.show();return false;}var triggerEl=params.button||params.displayArea||params.inputField;triggerEl["on"+params.eventName]=function(){var dateEl=params.inputField||params.displayArea;var dateFmt=params.inputField?params.ifFormat:params.daFormat;var mustCreate=false;var cal=window.calendar;if(dateEl)params.date=Date.parseDate(dateEl.value||dateEl.innerHTML,dateFmt);if(!(cal&¶ms.cache)){window.calendar=cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect,params.onClose||function(cal){cal.hide();});cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.weekNumbers=params.weekNumbers;mustCreate=true;}else{if(params.date)cal.setDate(params.date);cal.hide();}if(params.multiple){cal.multiple={};for(var i=params.multiple.length;--i>=0;){var d=params.multiple[i];var ds=d.print("%Y%m%d");cal.multiple[ds]=d;}}cal.showsOtherMonths=params.showOthers;cal.yearStep=params.step;cal.setRange(params.range[0],params.range[1]);cal.params=params;cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;cal.setDateFormat(dateFmt);if(mustCreate)cal.create();cal.refresh();if(!params.position)cal.showAtElement(params.button||params.displayArea||params.inputField,params.align);else cal.showAt(params.position[0],params.position[1]);return false;};return cal;};
\ No newline at end of file
diff --git a/public/bundles/dynarch_calendar/javascripts/calendar.js b/public/bundles/dynarch_calendar/javascripts/calendar.js
new file mode 100755
index 0000000..9088e0e
--- /dev/null
+++ b/public/bundles/dynarch_calendar/javascripts/calendar.js
@@ -0,0 +1,1806 @@
+/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com. Visit us at www.dynarch.com.
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+
+// $Id: calendar.js,v 1.51 2005/03/07 16:44:31 mishoo Exp $
+
+/** The Calendar object constructor. */
+Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) {
+ // member variables
+ this.activeDiv = null;
+ this.currentDateEl = null;
+ this.getDateStatus = null;
+ this.getDateToolTip = null;
+ this.getDateText = null;
+ this.timeout = null;
+ this.onSelected = onSelected || null;
+ this.onClose = onClose || null;
+ this.dragging = false;
+ this.hidden = false;
+ this.minYear = 1970;
+ this.maxYear = 2050;
+ this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
+ this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
+ this.isPopup = true;
+ this.weekNumbers = true;
+ this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc.
+ this.showsOtherMonths = false;
+ this.dateStr = dateStr;
+ this.ar_days = null;
+ this.showsTime = false;
+ this.time24 = true;
+ this.yearStep = 2;
+ this.hiliteToday = true;
+ this.multiple = null;
+ // HTML elements
+ this.table = null;
+ this.element = null;
+ this.tbody = null;
+ this.firstdayname = null;
+ // Combo boxes
+ this.monthsCombo = null;
+ this.yearsCombo = null;
+ this.hilitedMonth = null;
+ this.activeMonth = null;
+ this.hilitedYear = null;
+ this.activeYear = null;
+ // Information
+ this.dateClicked = false;
+
+ // one-time initializations
+ if (typeof Calendar._SDN == "undefined") {
+ // table of short day names
+ if (typeof Calendar._SDN_len == "undefined")
+ Calendar._SDN_len = 3;
+ var ar = new Array();
+ for (var i = 8; i > 0;) {
+ ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
+ }
+ Calendar._SDN = ar;
+ // table of short month names
+ if (typeof Calendar._SMN_len == "undefined")
+ Calendar._SMN_len = 3;
+ ar = new Array();
+ for (var i = 12; i > 0;) {
+ ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
+ }
+ Calendar._SMN = ar;
+ }
+};
+
+// ** constants
+
+/// "static", needed for event handlers.
+Calendar._C = null;
+
+/// detect a special case of "web browser"
+Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
+ !/opera/i.test(navigator.userAgent) );
+
+Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) );
+
+/// detect Opera browser
+Calendar.is_opera = /opera/i.test(navigator.userAgent);
+
+/// detect KHTML-based browsers
+Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);
+
+// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
+// library, at some point.
+
+Calendar.getAbsolutePos = function(el) {
+ var SL = 0, ST = 0;
+ var is_div = /^div$/i.test(el.tagName);
+ if (is_div && el.scrollLeft)
+ SL = el.scrollLeft;
+ if (is_div && el.scrollTop)
+ ST = el.scrollTop;
+ var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
+ if (el.offsetParent) {
+ var tmp = this.getAbsolutePos(el.offsetParent);
+ r.x += tmp.x;
+ r.y += tmp.y;
+ }
+ return r;
+};
+
+Calendar.isRelated = function (el, evt) {
+ var related = evt.relatedTarget;
+ if (!related) {
+ var type = evt.type;
+ if (type == "mouseover") {
+ related = evt.fromElement;
+ } else if (type == "mouseout") {
+ related = evt.toElement;
+ }
+ }
+ while (related) {
+ if (related == el) {
+ return true;
+ }
+ related = related.parentNode;
+ }
+ return false;
+};
+
+Calendar.removeClass = function(el, className) {
+ if (!(el && el.className)) {
+ return;
+ }
+ var cls = el.className.split(" ");
+ var ar = new Array();
+ for (var i = cls.length; i > 0;) {
+ if (cls[--i] != className) {
+ ar[ar.length] = cls[i];
+ }
+ }
+ el.className = ar.join(" ");
+};
+
+Calendar.addClass = function(el, className) {
+ Calendar.removeClass(el, className);
+ el.className += " " + className;
+};
+
+// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately.
+Calendar.getElement = function(ev) {
+ var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget;
+ while (f.nodeType != 1 || /^div$/i.test(f.tagName))
+ f = f.parentNode;
+ return f;
+};
+
+Calendar.getTargetElement = function(ev) {
+ var f = Calendar.is_ie ? window.event.srcElement : ev.target;
+ while (f.nodeType != 1)
+ f = f.parentNode;
+ return f;
+};
+
+Calendar.stopEvent = function(ev) {
+ ev || (ev = window.event);
+ if (Calendar.is_ie) {
+ ev.cancelBubble = true;
+ ev.returnValue = false;
+ } else {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+ return false;
+};
+
+Calendar.addEvent = function(el, evname, func) {
+ if (el.attachEvent) { // IE
+ el.attachEvent("on" + evname, func);
+ } else if (el.addEventListener) { // Gecko / W3C
+ el.addEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = func;
+ }
+};
+
+Calendar.removeEvent = function(el, evname, func) {
+ if (el.detachEvent) { // IE
+ el.detachEvent("on" + evname, func);
+ } else if (el.removeEventListener) { // Gecko / W3C
+ el.removeEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = null;
+ }
+};
+
+Calendar.createElement = function(type, parent) {
+ var el = null;
+ if (document.createElementNS) {
+ // use the XHTML namespace; IE won't normally get here unless
+ // _they_ "fix" the DOM2 implementation.
+ el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
+ } else {
+ el = document.createElement(type);
+ }
+ if (typeof parent != "undefined") {
+ parent.appendChild(el);
+ }
+ return el;
+};
+
+// END: UTILITY FUNCTIONS
+
+// BEGIN: CALENDAR STATIC FUNCTIONS
+
+/** Internal -- adds a set of events to make some element behave like a button. */
+Calendar._add_evs = function(el) {
+ with (Calendar) {
+ addEvent(el, "mouseover", dayMouseOver);
+ addEvent(el, "mousedown", dayMouseDown);
+ addEvent(el, "mouseout", dayMouseOut);
+ if (is_ie) {
+ addEvent(el, "dblclick", dayMouseDblClick);
+ el.setAttribute("unselectable", true);
+ }
+ }
+};
+
+Calendar.findMonth = function(el) {
+ if (typeof el.month != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.month != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.findYear = function(el) {
+ if (typeof el.year != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.year != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.showMonthsCombo = function () {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var mc = cal.monthsCombo;
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ if (cal.activeMonth) {
+ Calendar.removeClass(cal.activeMonth, "active");
+ }
+ var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
+ Calendar.addClass(mon, "active");
+ cal.activeMonth = mon;
+ var s = mc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else {
+ var mcw = mc.offsetWidth;
+ if (typeof mcw == "undefined")
+ // Konqueror brain-dead techniques
+ mcw = 50;
+ s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px";
+ }
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+};
+
+Calendar.showYearsCombo = function (fwd) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var yc = cal.yearsCombo;
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ if (cal.activeYear) {
+ Calendar.removeClass(cal.activeYear, "active");
+ }
+ cal.activeYear = null;
+ var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
+ var yr = yc.firstChild;
+ var show = false;
+ for (var i = 12; i > 0; --i) {
+ if (Y >= cal.minYear && Y <= cal.maxYear) {
+ yr.innerHTML = Y;
+ yr.year = Y;
+ yr.style.display = "block";
+ show = true;
+ } else {
+ yr.style.display = "none";
+ }
+ yr = yr.nextSibling;
+ Y += fwd ? cal.yearStep : -cal.yearStep;
+ }
+ if (show) {
+ var s = yc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else {
+ var ycw = yc.offsetWidth;
+ if (typeof ycw == "undefined")
+ // Konqueror brain-dead techniques
+ ycw = 50;
+ s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px";
+ }
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+ }
+};
+
+// event handlers
+
+Calendar.tableMouseUp = function(ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ if (cal.timeout) {
+ clearTimeout(cal.timeout);
+ }
+ var el = cal.activeDiv;
+ if (!el) {
+ return false;
+ }
+ var target = Calendar.getTargetElement(ev);
+ ev || (ev = window.event);
+ Calendar.removeClass(el, "active");
+ if (target == el || target.parentNode == el) {
+ Calendar.cellClick(el, ev);
+ }
+ var mon = Calendar.findMonth(target);
+ var date = null;
+ if (mon) {
+ date = new Date(cal.date);
+ if (mon.month != date.getMonth()) {
+ date.setMonth(mon.month);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ } else {
+ var year = Calendar.findYear(target);
+ if (year) {
+ date = new Date(cal.date);
+ if (year.year != date.getFullYear()) {
+ date.setFullYear(year.year);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ }
+ }
+ with (Calendar) {
+ removeEvent(document, "mouseup", tableMouseUp);
+ removeEvent(document, "mouseover", tableMouseOver);
+ removeEvent(document, "mousemove", tableMouseOver);
+ cal._hideCombos();
+ _C = null;
+ return stopEvent(ev);
+ }
+};
+
+Calendar.tableMouseOver = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return;
+ }
+ var el = cal.activeDiv;
+ var target = Calendar.getTargetElement(ev);
+ if (target == el || target.parentNode == el) {
+ Calendar.addClass(el, "hilite active");
+ Calendar.addClass(el.parentNode, "rowhilite");
+ } else {
+ if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
+ Calendar.removeClass(el, "active");
+ Calendar.removeClass(el, "hilite");
+ Calendar.removeClass(el.parentNode, "rowhilite");
+ }
+ ev || (ev = window.event);
+ if (el.navtype == 50 && target != el) {
+ var pos = Calendar.getAbsolutePos(el);
+ var w = el.offsetWidth;
+ var x = ev.clientX;
+ var dx;
+ var decrease = true;
+ if (x > pos.x + w) {
+ dx = x - pos.x - w;
+ decrease = false;
+ } else
+ dx = pos.x - x;
+
+ if (dx < 0) dx = 0;
+ var range = el._range;
+ var current = el._current;
+ var count = Math.floor(dx / 10) % range.length;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ while (count-- > 0)
+ if (decrease) {
+ if (--i < 0)
+ i = range.length - 1;
+ } else if ( ++i >= range.length )
+ i = 0;
+ var newval = range[i];
+ el.innerHTML = newval;
+
+ cal.onUpdateTime();
+ }
+ var mon = Calendar.findMonth(target);
+ if (mon) {
+ if (mon.month != cal.date.getMonth()) {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ Calendar.addClass(mon, "hilite");
+ cal.hilitedMonth = mon;
+ } else if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ } else {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ var year = Calendar.findYear(target);
+ if (year) {
+ if (year.year != cal.date.getFullYear()) {
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ Calendar.addClass(year, "hilite");
+ cal.hilitedYear = year;
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.tableMouseDown = function (ev) {
+ if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
+ return Calendar.stopEvent(ev);
+ }
+};
+
+Calendar.calDragIt = function (ev) {
+ var cal = Calendar._C;
+ if (!(cal && cal.dragging)) {
+ return false;
+ }
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posX = ev.pageX;
+ posY = ev.pageY;
+ }
+ cal.hideShowCovered();
+ var st = cal.element.style;
+ st.left = (posX - cal.xOffs) + "px";
+ st.top = (posY - cal.yOffs) + "px";
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.calDragEnd = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ cal.dragging = false;
+ with (Calendar) {
+ removeEvent(document, "mousemove", calDragIt);
+ removeEvent(document, "mouseup", calDragEnd);
+ tableMouseUp(ev);
+ }
+ cal.hideShowCovered();
+};
+
+Calendar.dayMouseDown = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (el.disabled) {
+ return false;
+ }
+ var cal = el.calendar;
+ cal.activeDiv = el;
+ Calendar._C = cal;
+ if (el.navtype != 300) with (Calendar) {
+ if (el.navtype == 50) {
+ el._current = el.innerHTML;
+ addEvent(document, "mousemove", tableMouseOver);
+ } else
+ addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver);
+ addClass(el, "hilite active");
+ addEvent(document, "mouseup", tableMouseUp);
+ } else if (cal.isPopup) {
+ cal._dragStart(ev);
+ }
+ if (el.navtype == -1 || el.navtype == 1) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
+ } else if (el.navtype == -2 || el.navtype == 2) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
+ } else {
+ cal.timeout = null;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseDblClick = function(ev) {
+ Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
+ if (Calendar.is_ie) {
+ document.selection.empty();
+ }
+};
+
+Calendar.dayMouseOver = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
+ return false;
+ }
+ if (el.ttip) {
+ if (el.ttip.substr(0, 1) == "_") {
+ el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
+ }
+ el.calendar.tooltips.innerHTML = el.ttip;
+ }
+ if (el.navtype != 300) {
+ Calendar.addClass(el, "hilite");
+ if (el.caldate) {
+ Calendar.addClass(el.parentNode, "rowhilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseOut = function(ev) {
+ with (Calendar) {
+ var el = getElement(ev);
+ if (isRelated(el, ev) || _C || el.disabled)
+ return false;
+ removeClass(el, "hilite");
+ if (el.caldate)
+ removeClass(el.parentNode, "rowhilite");
+ if (el.calendar)
+ el.calendar.tooltips.innerHTML = _TT["SEL_DATE"];
+ return stopEvent(ev);
+ }
+};
+
+/**
+ * A generic "click" handler :) handles all types of buttons defined in this
+ * calendar.
+ */
+Calendar.cellClick = function(el, ev) {
+ var cal = el.calendar;
+ var closing = false;
+ var newdate = false;
+ var date = null;
+ if (typeof el.navtype == "undefined") {
+ if (cal.currentDateEl) {
+ Calendar.removeClass(cal.currentDateEl, "selected");
+ Calendar.addClass(el, "selected");
+ closing = (cal.currentDateEl == el);
+ if (!closing) {
+ cal.currentDateEl = el;
+ }
+ }
+ cal.date.setDateOnly(el.caldate);
+ date = cal.date;
+ var other_month = !(cal.dateClicked = !el.otherMonth);
+ if (!other_month && !cal.currentDateEl)
+ cal._toggleMultipleDate(new Date(date));
+ else
+ newdate = !el.disabled;
+ // a date was clicked
+ if (other_month)
+ cal._init(cal.firstDayOfWeek, date);
+ } else {
+ if (el.navtype == 200) {
+ Calendar.removeClass(el, "hilite");
+ cal.callCloseHandler();
+ return;
+ }
+ date = new Date(cal.date);
+ if (el.navtype == 0)
+ date.setDateOnly(new Date()); // TODAY
+ // unless "today" was clicked, we assume no date was clicked so
+ // the selected handler will know not to close the calenar when
+ // in single-click mode.
+ // cal.dateClicked = (el.navtype == 0);
+ cal.dateClicked = false;
+ var year = date.getFullYear();
+ var mon = date.getMonth();
+ function setMonth(m) {
+ var day = date.getDate();
+ var max = date.getMonthDays(m);
+ if (day > max) {
+ date.setDate(max);
+ }
+ date.setMonth(m);
+ };
+ switch (el.navtype) {
+ case 400:
+ Calendar.removeClass(el, "hilite");
+ var text = Calendar._TT["ABOUT"];
+ if (typeof text != "undefined") {
+ text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
+ } else {
+ // FIXME: this should be removed as soon as lang files get updated!
+ text = "Help and about box text is not translated into this language.\n" +
+ "If you know this language and you feel generous please update\n" +
+ "the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
+ "and send it back to to get it into the distribution ;-)\n\n" +
+ "Thank you!\n" +
+ "http://dynarch.com/mishoo/calendar.epl\n";
+ }
+ alert(text);
+ return;
+ case -2:
+ if (year > cal.minYear) {
+ date.setFullYear(year - 1);
+ }
+ break;
+ case -1:
+ if (mon > 0) {
+ setMonth(mon - 1);
+ } else if (year-- > cal.minYear) {
+ date.setFullYear(year);
+ setMonth(11);
+ }
+ break;
+ case 1:
+ if (mon < 11) {
+ setMonth(mon + 1);
+ } else if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ setMonth(0);
+ }
+ break;
+ case 2:
+ if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ }
+ break;
+ case 100:
+ cal.setFirstDayOfWeek(el.fdow);
+ return;
+ case 50:
+ var range = el._range;
+ var current = el.innerHTML;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ if (ev && ev.shiftKey) {
+ if (--i < 0)
+ i = range.length - 1;
+ } else if ( ++i >= range.length )
+ i = 0;
+ var newval = range[i];
+ el.innerHTML = newval;
+ cal.onUpdateTime();
+ return;
+ case 0:
+ // TODAY will bring us here
+ if ((typeof cal.getDateStatus == "function") &&
+ cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
+ return false;
+ }
+ break;
+ }
+ if (!date.equalsTo(cal.date)) {
+ cal.setDate(date);
+ newdate = true;
+ } else if (el.navtype == 0)
+ newdate = closing = true;
+ }
+ if (newdate) {
+ ev && cal.callHandler();
+ }
+ if (closing) {
+ Calendar.removeClass(el, "hilite");
+ ev && cal.callCloseHandler();
+ }
+};
+
+// END: CALENDAR STATIC FUNCTIONS
+
+// BEGIN: CALENDAR OBJECT FUNCTIONS
+
+/**
+ * This function creates the calendar inside the given parent. If _par is
+ * null than it creates a popup calendar inside the BODY element. If _par is
+ * an element, be it BODY, then it creates a non-popup calendar (still
+ * hidden). Some properties need to be set before calling this function.
+ */
+Calendar.prototype.create = function (_par) {
+ var parent = null;
+ if (! _par) {
+ // default parent is the document body, in which case we create
+ // a popup calendar.
+ parent = document.getElementsByTagName("body")[0];
+ this.isPopup = true;
+ } else {
+ parent = _par;
+ this.isPopup = false;
+ }
+ this.date = this.dateStr ? new Date(this.dateStr) : new Date();
+
+ var table = Calendar.createElement("table");
+ this.table = table;
+ table.cellSpacing = 0;
+ table.cellPadding = 0;
+ table.calendar = this;
+ Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);
+
+ var div = Calendar.createElement("div");
+ this.element = div;
+ div.className = "calendar";
+ if (this.isPopup) {
+ div.style.position = "absolute";
+ div.style.display = "none";
+ }
+ div.appendChild(table);
+
+ var thead = Calendar.createElement("thead", table);
+ var cell = null;
+ var row = null;
+
+ var cal = this;
+ var hh = function (text, cs, navtype) {
+ cell = Calendar.createElement("td", row);
+ cell.colSpan = cs;
+ cell.className = "button";
+ if (navtype != 0 && Math.abs(navtype) <= 2)
+ cell.className += " nav";
+ Calendar._add_evs(cell);
+ cell.calendar = cal;
+ cell.navtype = navtype;
+ cell.innerHTML = "
" + text + "
";
+ return cell;
+ };
+
+ row = Calendar.createElement("tr", thead);
+ var title_length = 6;
+ (this.isPopup) && --title_length;
+ (this.weekNumbers) && ++title_length;
+
+ hh("?", 1, 400).ttip = Calendar._TT["INFO"];
+ this.title = hh("", title_length, 300);
+ this.title.className = "title";
+ if (this.isPopup) {
+ this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ this.title.style.cursor = "move";
+ hh("×", 1, 200).ttip = Calendar._TT["CLOSE"];
+ }
+
+ row = Calendar.createElement("tr", thead);
+ row.className = "headrow";
+
+ this._nav_py = hh("«", 1, -2);
+ this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
+
+ this._nav_pm = hh("‹", 1, -1);
+ this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
+
+ this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
+ this._nav_now.ttip = Calendar._TT["GO_TODAY"];
+
+ this._nav_nm = hh("›", 1, 1);
+ this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
+
+ this._nav_ny = hh("»", 1, 2);
+ this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
+
+ // day names
+ row = Calendar.createElement("tr", thead);
+ row.className = "daynames";
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ cell.className = "name wn";
+ cell.innerHTML = Calendar._TT["WK"];
+ }
+ for (var i = 7; i > 0; --i) {
+ cell = Calendar.createElement("td", row);
+ if (!i) {
+ cell.navtype = 100;
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+ this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
+ this._displayWeekdays();
+
+ var tbody = Calendar.createElement("tbody", table);
+ this.tbody = tbody;
+
+ for (i = 6; i > 0; --i) {
+ row = Calendar.createElement("tr", tbody);
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ }
+ for (var j = 7; j > 0; --j) {
+ cell = Calendar.createElement("td", row);
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+
+ if (this.showsTime) {
+ row = Calendar.createElement("tr", tbody);
+ row.className = "time";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ cell.innerHTML = Calendar._TT["TIME"] || " ";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = this.weekNumbers ? 4 : 3;
+
+ (function(){
+ function makeTimePart(className, init, range_start, range_end) {
+ var part = Calendar.createElement("span", cell);
+ part.className = className;
+ part.innerHTML = init;
+ part.calendar = cal;
+ part.ttip = Calendar._TT["TIME_PART"];
+ part.navtype = 50;
+ part._range = [];
+ if (typeof range_start != "number")
+ part._range = range_start;
+ else {
+ for (var i = range_start; i <= range_end; ++i) {
+ var txt;
+ if (i < 10 && range_end >= 10) txt = '0' + i;
+ else txt = '' + i;
+ part._range[part._range.length] = txt;
+ }
+ }
+ Calendar._add_evs(part);
+ return part;
+ };
+ var hrs = cal.date.getHours();
+ var mins = cal.date.getMinutes();
+ var t12 = !cal.time24;
+ var pm = (hrs > 12);
+ if (t12 && pm) hrs -= 12;
+ var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
+ var span = Calendar.createElement("span", cell);
+ span.innerHTML = ":";
+ span.className = "colon";
+ var M = makeTimePart("minute", mins, 0, 59);
+ var AP = null;
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ if (t12)
+ AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
+ else
+ cell.innerHTML = " ";
+
+ cal.onSetTime = function() {
+ var pm, hrs = this.date.getHours(),
+ mins = this.date.getMinutes();
+ if (t12) {
+ pm = (hrs >= 12);
+ if (pm) hrs -= 12;
+ if (hrs == 0) hrs = 12;
+ AP.innerHTML = pm ? "pm" : "am";
+ }
+ H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs;
+ M.innerHTML = (mins < 10) ? ("0" + mins) : mins;
+ };
+
+ cal.onUpdateTime = function() {
+ var date = this.date;
+ var h = parseInt(H.innerHTML, 10);
+ if (t12) {
+ if (/pm/i.test(AP.innerHTML) && h < 12)
+ h += 12;
+ else if (/am/i.test(AP.innerHTML) && h == 12)
+ h = 0;
+ }
+ var d = date.getDate();
+ var m = date.getMonth();
+ var y = date.getFullYear();
+ date.setHours(h);
+ date.setMinutes(parseInt(M.innerHTML, 10));
+ date.setFullYear(y);
+ date.setMonth(m);
+ date.setDate(d);
+ this.dateClicked = false;
+ this.callHandler();
+ };
+ })();
+ } else {
+ this.onSetTime = this.onUpdateTime = function() {};
+ }
+
+ var tfoot = Calendar.createElement("tfoot", table);
+
+ row = Calendar.createElement("tr", tfoot);
+ row.className = "footrow";
+
+ cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
+ cell.className = "ttip";
+ if (this.isPopup) {
+ cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ cell.style.cursor = "move";
+ }
+ this.tooltips = cell;
+
+ div = Calendar.createElement("div", this.element);
+ this.monthsCombo = div;
+ div.className = "combo";
+ for (i = 0; i < Calendar._MN.length; ++i) {
+ var mn = Calendar.createElement("div");
+ mn.className = Calendar.is_ie ? "label-IEfix" : "label";
+ mn.month = i;
+ mn.innerHTML = Calendar._SMN[i];
+ div.appendChild(mn);
+ }
+
+ div = Calendar.createElement("div", this.element);
+ this.yearsCombo = div;
+ div.className = "combo";
+ for (i = 12; i > 0; --i) {
+ var yr = Calendar.createElement("div");
+ yr.className = Calendar.is_ie ? "label-IEfix" : "label";
+ div.appendChild(yr);
+ }
+
+ this._init(this.firstDayOfWeek, this.date);
+ parent.appendChild(this.element);
+};
+
+/** keyboard navigation, only for popup calendars */
+Calendar._keyEvent = function(ev) {
+ var cal = window._dynarch_popupCalendar;
+ if (!cal || cal.multiple)
+ return false;
+ (Calendar.is_ie) && (ev = window.event);
+ var act = (Calendar.is_ie || ev.type == "keypress"),
+ K = ev.keyCode;
+ if (ev.ctrlKey) {
+ switch (K) {
+ case 37: // KEY left
+ act && Calendar.cellClick(cal._nav_pm);
+ break;
+ case 38: // KEY up
+ act && Calendar.cellClick(cal._nav_py);
+ break;
+ case 39: // KEY right
+ act && Calendar.cellClick(cal._nav_nm);
+ break;
+ case 40: // KEY down
+ act && Calendar.cellClick(cal._nav_ny);
+ break;
+ default:
+ return false;
+ }
+ } else switch (K) {
+ case 32: // KEY space (now)
+ Calendar.cellClick(cal._nav_now);
+ break;
+ case 27: // KEY esc
+ act && cal.callCloseHandler();
+ break;
+ case 37: // KEY left
+ case 38: // KEY up
+ case 39: // KEY right
+ case 40: // KEY down
+ if (act) {
+ var prev, x, y, ne, el, step;
+ prev = K == 37 || K == 38;
+ step = (K == 37 || K == 39) ? 1 : 7;
+ function setVars() {
+ el = cal.currentDateEl;
+ var p = el.pos;
+ x = p & 15;
+ y = p >> 4;
+ ne = cal.ar_days[y][x];
+ };setVars();
+ function prevMonth() {
+ var date = new Date(cal.date);
+ date.setDate(date.getDate() - step);
+ cal.setDate(date);
+ };
+ function nextMonth() {
+ var date = new Date(cal.date);
+ date.setDate(date.getDate() + step);
+ cal.setDate(date);
+ };
+ while (1) {
+ switch (K) {
+ case 37: // KEY left
+ if (--x >= 0)
+ ne = cal.ar_days[y][x];
+ else {
+ x = 6;
+ K = 38;
+ continue;
+ }
+ break;
+ case 38: // KEY up
+ if (--y >= 0)
+ ne = cal.ar_days[y][x];
+ else {
+ prevMonth();
+ setVars();
+ }
+ break;
+ case 39: // KEY right
+ if (++x < 7)
+ ne = cal.ar_days[y][x];
+ else {
+ x = 0;
+ K = 40;
+ continue;
+ }
+ break;
+ case 40: // KEY down
+ if (++y < cal.ar_days.length)
+ ne = cal.ar_days[y][x];
+ else {
+ nextMonth();
+ setVars();
+ }
+ break;
+ }
+ break;
+ }
+ if (ne) {
+ if (!ne.disabled)
+ Calendar.cellClick(ne);
+ else if (prev)
+ prevMonth();
+ else
+ nextMonth();
+ }
+ }
+ break;
+ case 13: // KEY enter
+ if (act)
+ Calendar.cellClick(cal.currentDateEl, ev);
+ break;
+ default:
+ return false;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+/**
+ * (RE)Initializes the calendar to the given date and firstDayOfWeek
+ */
+Calendar.prototype._init = function (firstDayOfWeek, date) {
+ var today = new Date(),
+ TY = today.getFullYear(),
+ TM = today.getMonth(),
+ TD = today.getDate();
+ this.table.style.visibility = "hidden";
+ var year = date.getFullYear();
+ if (year < this.minYear) {
+ year = this.minYear;
+ date.setFullYear(year);
+ } else if (year > this.maxYear) {
+ year = this.maxYear;
+ date.setFullYear(year);
+ }
+ this.firstDayOfWeek = firstDayOfWeek;
+ this.date = new Date(date);
+ var month = date.getMonth();
+ var mday = date.getDate();
+ var no_days = date.getMonthDays();
+
+ // calendar voodoo for computing the first day that would actually be
+ // displayed in the calendar, even if it's from the previous month.
+ // WARNING: this is magic. ;-)
+ date.setDate(1);
+ var day1 = (date.getDay() - this.firstDayOfWeek) % 7;
+ if (day1 < 0)
+ day1 += 7;
+ date.setDate(-day1);
+ date.setDate(date.getDate() + 1);
+
+ var row = this.tbody.firstChild;
+ var MN = Calendar._SMN[month];
+ var ar_days = this.ar_days = new Array();
+ var weekend = Calendar._TT["WEEKEND"];
+ var dates = this.multiple ? (this.datesCells = {}) : null;
+ for (var i = 0; i < 6; ++i, row = row.nextSibling) {
+ var cell = row.firstChild;
+ if (this.weekNumbers) {
+ cell.className = "day wn";
+ cell.innerHTML = date.getWeekNumber();
+ cell = cell.nextSibling;
+ }
+ row.className = "daysrow";
+ var hasdays = false, iday, dpos = ar_days[i] = [];
+ for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) {
+ iday = date.getDate();
+ var wday = date.getDay();
+ cell.className = "day";
+ cell.pos = i << 4 | j;
+ dpos[j] = cell;
+ var current_month = (date.getMonth() == month);
+ if (!current_month) {
+ if (this.showsOtherMonths) {
+ cell.className += " othermonth";
+ cell.otherMonth = true;
+ } else {
+ cell.className = "emptycell";
+ cell.innerHTML = " ";
+ cell.disabled = true;
+ continue;
+ }
+ } else {
+ cell.otherMonth = false;
+ hasdays = true;
+ }
+ cell.disabled = false;
+ cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday;
+ if (dates)
+ dates[date.print("%Y%m%d")] = cell;
+ if (this.getDateStatus) {
+ var status = this.getDateStatus(date, year, month, iday);
+ if (this.getDateToolTip) {
+ var toolTip = this.getDateToolTip(date, year, month, iday);
+ if (toolTip)
+ cell.title = toolTip;
+ }
+ if (status === true) {
+ cell.className += " disabled";
+ cell.disabled = true;
+ } else {
+ if (/disabled/i.test(status))
+ cell.disabled = true;
+ cell.className += " " + status;
+ }
+ }
+ if (!cell.disabled) {
+ cell.caldate = new Date(date);
+ cell.ttip = "_";
+ if (!this.multiple && current_month
+ && iday == mday && this.hiliteToday) {
+ cell.className += " selected";
+ this.currentDateEl = cell;
+ }
+ if (date.getFullYear() == TY &&
+ date.getMonth() == TM &&
+ iday == TD) {
+ cell.className += " today";
+ cell.ttip += Calendar._TT["PART_TODAY"];
+ }
+ if (weekend.indexOf(wday.toString()) != -1)
+ cell.className += cell.otherMonth ? " oweekend" : " weekend";
+ }
+ }
+ if (!(hasdays || this.showsOtherMonths))
+ row.className = "emptyrow";
+ }
+ this.title.innerHTML = Calendar._MN[month] + ", " + year;
+ this.onSetTime();
+ this.table.style.visibility = "visible";
+ this._initMultipleDates();
+ // PROFILE
+ // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms";
+};
+
+Calendar.prototype._initMultipleDates = function() {
+ if (this.multiple) {
+ for (var i in this.multiple) {
+ var cell = this.datesCells[i];
+ var d = this.multiple[i];
+ if (!d)
+ continue;
+ if (cell)
+ cell.className += " selected";
+ }
+ }
+};
+
+Calendar.prototype._toggleMultipleDate = function(date) {
+ if (this.multiple) {
+ var ds = date.print("%Y%m%d");
+ var cell = this.datesCells[ds];
+ if (cell) {
+ var d = this.multiple[ds];
+ if (!d) {
+ Calendar.addClass(cell, "selected");
+ this.multiple[ds] = date;
+ } else {
+ Calendar.removeClass(cell, "selected");
+ delete this.multiple[ds];
+ }
+ }
+ }
+};
+
+Calendar.prototype.setDateToolTipHandler = function (unaryFunction) {
+ this.getDateToolTip = unaryFunction;
+};
+
+/**
+ * Calls _init function above for going to a certain date (but only if the
+ * date is different than the currently selected one).
+ */
+Calendar.prototype.setDate = function (date) {
+ if (!date.equalsTo(this.date)) {
+ this._init(this.firstDayOfWeek, date);
+ }
+};
+
+/**
+ * Refreshes the calendar. Useful if the "disabledHandler" function is
+ * dynamic, meaning that the list of disabled date can change at runtime.
+ * Just * call this function if you think that the list of disabled dates
+ * should * change.
+ */
+Calendar.prototype.refresh = function () {
+ this._init(this.firstDayOfWeek, this.date);
+};
+
+/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */
+Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) {
+ this._init(firstDayOfWeek, this.date);
+ this._displayWeekdays();
+};
+
+/**
+ * Allows customization of what dates are enabled. The "unaryFunction"
+ * parameter must be a function object that receives the date (as a JS Date
+ * object) and returns a boolean value. If the returned value is true then
+ * the passed date will be marked as disabled.
+ */
+Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
+ this.getDateStatus = unaryFunction;
+};
+
+/** Customization of allowed year range for the calendar. */
+Calendar.prototype.setRange = function (a, z) {
+ this.minYear = a;
+ this.maxYear = z;
+};
+
+/** Calls the first user handler (selectedHandler). */
+Calendar.prototype.callHandler = function () {
+ if (this.onSelected) {
+ this.onSelected(this, this.date.print(this.dateFormat));
+ }
+};
+
+/** Calls the second user handler (closeHandler). */
+Calendar.prototype.callCloseHandler = function () {
+ if (this.onClose) {
+ this.onClose(this);
+ }
+ this.hideShowCovered();
+};
+
+/** Removes the calendar object from the DOM tree and destroys it. */
+Calendar.prototype.destroy = function () {
+ var el = this.element.parentNode;
+ el.removeChild(this.element);
+ Calendar._C = null;
+ window._dynarch_popupCalendar = null;
+};
+
+/**
+ * Moves the calendar element to a different section in the DOM tree (changes
+ * its parent).
+ */
+Calendar.prototype.reparent = function (new_parent) {
+ var el = this.element;
+ el.parentNode.removeChild(el);
+ new_parent.appendChild(el);
+};
+
+// This gets called when the user presses a mouse button anywhere in the
+// document, if the calendar is shown. If the click was outside the open
+// calendar this function closes it.
+Calendar._checkCalendar = function(ev) {
+ var calendar = window._dynarch_popupCalendar;
+ if (!calendar) {
+ return false;
+ }
+ var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
+ for (; el != null && el != calendar.element; el = el.parentNode);
+ if (el == null) {
+ // calls closeHandler which should hide the calendar.
+ window._dynarch_popupCalendar.callCloseHandler();
+ return Calendar.stopEvent(ev);
+ }
+};
+
+/** Shows the calendar. */
+Calendar.prototype.show = function () {
+ var rows = this.table.getElementsByTagName("tr");
+ for (var i = rows.length; i > 0;) {
+ var row = rows[--i];
+ Calendar.removeClass(row, "rowhilite");
+ var cells = row.getElementsByTagName("td");
+ for (var j = cells.length; j > 0;) {
+ var cell = cells[--j];
+ Calendar.removeClass(cell, "hilite");
+ Calendar.removeClass(cell, "active");
+ }
+ }
+ this.element.style.display = "block";
+ this.hidden = false;
+ if (this.isPopup) {
+ window._dynarch_popupCalendar = this;
+ Calendar.addEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.addEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.hideShowCovered();
+};
+
+/**
+ * Hides the calendar. Also removes any "hilite" from the class of any TD
+ * element.
+ */
+Calendar.prototype.hide = function () {
+ if (this.isPopup) {
+ Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.element.style.display = "none";
+ this.hidden = true;
+ this.hideShowCovered();
+};
+
+/**
+ * Shows the calendar at a given absolute position (beware that, depending on
+ * the calendar element style -- position property -- this might be relative
+ * to the parent's containing rectangle).
+ */
+Calendar.prototype.showAt = function (x, y) {
+ var s = this.element.style;
+ s.left = x + "px";
+ s.top = y + "px";
+ this.show();
+};
+
+/** Shows the calendar near a given element. */
+Calendar.prototype.showAtElement = function (el, opts) {
+ var self = this;
+ var p = Calendar.getAbsolutePos(el);
+ if (!opts || typeof opts != "string") {
+ this.showAt(p.x, p.y + el.offsetHeight);
+ return true;
+ }
+ function fixPosition(box) {
+ if (box.x < 0)
+ box.x = 0;
+ if (box.y < 0)
+ box.y = 0;
+ var cp = document.createElement("div");
+ var s = cp.style;
+ s.position = "absolute";
+ s.right = s.bottom = s.width = s.height = "0px";
+ document.body.appendChild(cp);
+ var br = Calendar.getAbsolutePos(cp);
+ document.body.removeChild(cp);
+ if (Calendar.is_ie) {
+ br.y += document.body.scrollTop;
+ br.x += document.body.scrollLeft;
+ } else {
+ br.y += window.scrollY;
+ br.x += window.scrollX;
+ }
+ var tmp = box.x + box.width - br.x;
+ if (tmp > 0) box.x -= tmp;
+ tmp = box.y + box.height - br.y;
+ if (tmp > 0) box.y -= tmp;
+ };
+ this.element.style.display = "block";
+ Calendar.continuation_for_the_fucking_khtml_browser = function() {
+ var w = self.element.offsetWidth;
+ var h = self.element.offsetHeight;
+ self.element.style.display = "none";
+ var valign = opts.substr(0, 1);
+ var halign = "l";
+ if (opts.length > 1) {
+ halign = opts.substr(1, 1);
+ }
+ // vertical alignment
+ switch (valign) {
+ case "T": p.y -= h; break;
+ case "B": p.y += el.offsetHeight; break;
+ case "C": p.y += (el.offsetHeight - h) / 2; break;
+ case "t": p.y += el.offsetHeight - h; break;
+ case "b": break; // already there
+ }
+ // horizontal alignment
+ switch (halign) {
+ case "L": p.x -= w; break;
+ case "R": p.x += el.offsetWidth; break;
+ case "C": p.x += (el.offsetWidth - w) / 2; break;
+ case "l": p.x += el.offsetWidth - w; break;
+ case "r": break; // already there
+ }
+ p.width = w;
+ p.height = h + 40;
+ self.monthsCombo.style.display = "none";
+ fixPosition(p);
+ self.showAt(p.x, p.y);
+ };
+ if (Calendar.is_khtml)
+ setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
+ else
+ Calendar.continuation_for_the_fucking_khtml_browser();
+};
+
+/** Customizes the date format. */
+Calendar.prototype.setDateFormat = function (str) {
+ this.dateFormat = str;
+};
+
+/** Customizes the tooltip date format. */
+Calendar.prototype.setTtDateFormat = function (str) {
+ this.ttDateFormat = str;
+};
+
+/**
+ * Tries to identify the date represented in a string. If successful it also
+ * calls this.setDate which moves the calendar to the given date.
+ */
+Calendar.prototype.parseDate = function(str, fmt) {
+ if (!fmt)
+ fmt = this.dateFormat;
+ this.setDate(Date.parseDate(str, fmt));
+};
+
+Calendar.prototype.hideShowCovered = function () {
+ if (!Calendar.is_ie && !Calendar.is_opera)
+ return;
+ function getVisib(obj){
+ var value = obj.style.visibility;
+ if (!value) {
+ if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
+ if (!Calendar.is_khtml)
+ value = document.defaultView.
+ getComputedStyle(obj, "").getPropertyValue("visibility");
+ else
+ value = '';
+ } else if (obj.currentStyle) { // IE
+ value = obj.currentStyle.visibility;
+ } else
+ value = '';
+ }
+ return value;
+ };
+
+ var tags = new Array("applet", "iframe", "select");
+ var el = this.element;
+
+ var p = Calendar.getAbsolutePos(el);
+ var EX1 = p.x;
+ var EX2 = el.offsetWidth + EX1;
+ var EY1 = p.y;
+ var EY2 = el.offsetHeight + EY1;
+
+ for (var k = tags.length; k > 0; ) {
+ var ar = document.getElementsByTagName(tags[--k]);
+ var cc = null;
+
+ for (var i = ar.length; i > 0;) {
+ cc = ar[--i];
+
+ p = Calendar.getAbsolutePos(cc);
+ var CX1 = p.x;
+ var CX2 = cc.offsetWidth + CX1;
+ var CY1 = p.y;
+ var CY2 = cc.offsetHeight + CY1;
+
+ if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = cc.__msh_save_visibility;
+ } else {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = "hidden";
+ }
+ }
+ }
+};
+
+/** Internal function; it displays the bar with the names of the weekday. */
+Calendar.prototype._displayWeekdays = function () {
+ var fdow = this.firstDayOfWeek;
+ var cell = this.firstdayname;
+ var weekend = Calendar._TT["WEEKEND"];
+ for (var i = 0; i < 7; ++i) {
+ cell.className = "day name";
+ var realday = (i + fdow) % 7;
+ if (i) {
+ cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]);
+ cell.navtype = 100;
+ cell.calendar = this;
+ cell.fdow = realday;
+ Calendar._add_evs(cell);
+ }
+ if (weekend.indexOf(realday.toString()) != -1) {
+ Calendar.addClass(cell, "weekend");
+ }
+ cell.innerHTML = Calendar._SDN[(i + fdow) % 7];
+ cell = cell.nextSibling;
+ }
+};
+
+/** Internal function. Hides all combo boxes that might be displayed. */
+Calendar.prototype._hideCombos = function () {
+ this.monthsCombo.style.display = "none";
+ this.yearsCombo.style.display = "none";
+};
+
+/** Internal function. Starts dragging the element. */
+Calendar.prototype._dragStart = function (ev) {
+ if (this.dragging) {
+ return;
+ }
+ this.dragging = true;
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posY = ev.clientY + window.scrollY;
+ posX = ev.clientX + window.scrollX;
+ }
+ var st = this.element.style;
+ this.xOffs = posX - parseInt(st.left);
+ this.yOffs = posY - parseInt(st.top);
+ with (Calendar) {
+ addEvent(document, "mousemove", calDragIt);
+ addEvent(document, "mouseup", calDragEnd);
+ }
+};
+
+// BEGIN: DATE OBJECT PATCHES
+
+/** Adds the number of days array to the Date object. */
+Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+/** Constants used for time computations */
+Date.SECOND = 1000 /* milliseconds */;
+Date.MINUTE = 60 * Date.SECOND;
+Date.HOUR = 60 * Date.MINUTE;
+Date.DAY = 24 * Date.HOUR;
+Date.WEEK = 7 * Date.DAY;
+
+Date.parseDate = function(str, fmt) {
+ var today = new Date();
+ var y = 0;
+ var m = -1;
+ var d = 0;
+ var a = str.split(/\W+/);
+ var b = fmt.match(/%./g);
+ var i = 0, j = 0;
+ var hr = 0;
+ var min = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (!a[i])
+ continue;
+ switch (b[i]) {
+ case "%d":
+ case "%e":
+ d = parseInt(a[i], 10);
+ break;
+
+ case "%m":
+ m = parseInt(a[i], 10) - 1;
+ break;
+
+ case "%Y":
+ case "%y":
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ break;
+
+ case "%b":
+ case "%B":
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
+ }
+ break;
+
+ case "%H":
+ case "%I":
+ case "%k":
+ case "%l":
+ hr = parseInt(a[i], 10);
+ break;
+
+ case "%P":
+ case "%p":
+ if (/pm/i.test(a[i]) && hr < 12)
+ hr += 12;
+ else if (/am/i.test(a[i]) && hr >= 12)
+ hr -= 12;
+ break;
+
+ case "%M":
+ min = parseInt(a[i], 10);
+ break;
+ }
+ }
+ if (isNaN(y)) y = today.getFullYear();
+ if (isNaN(m)) m = today.getMonth();
+ if (isNaN(d)) d = today.getDate();
+ if (isNaN(hr)) hr = today.getHours();
+ if (isNaN(min)) min = today.getMinutes();
+ if (y != 0 && m != -1 && d != 0)
+ return new Date(y, m, d, hr, min, 0);
+ y = 0; m = -1; d = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (a[i].search(/[a-zA-Z]+/) != -1) {
+ var t = -1;
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
+ }
+ if (t != -1) {
+ if (m != -1) {
+ d = m+1;
+ }
+ m = t;
+ }
+ } else if (parseInt(a[i], 10) <= 12 && m == -1) {
+ m = a[i]-1;
+ } else if (parseInt(a[i], 10) > 31 && y == 0) {
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ } else if (d == 0) {
+ d = a[i];
+ }
+ }
+ if (y == 0)
+ y = today.getFullYear();
+ if (m != -1 && d != 0)
+ return new Date(y, m, d, hr, min, 0);
+ return today;
+};
+
+/** Returns the number of days in the current month */
+Date.prototype.getMonthDays = function(month) {
+ var year = this.getFullYear();
+ if (typeof month == "undefined") {
+ month = this.getMonth();
+ }
+ if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
+ return 29;
+ } else {
+ return Date._MD[month];
+ }
+};
+
+/** Returns the number of day in the year. */
+Date.prototype.getDayOfYear = function() {
+ var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
+ var time = now - then;
+ return Math.floor(time / Date.DAY);
+};
+
+/** Returns the number of the week in year, as defined in ISO 8601. */
+Date.prototype.getWeekNumber = function() {
+ var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var DoW = d.getDay();
+ d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
+ var ms = d.valueOf(); // GMT
+ d.setMonth(0);
+ d.setDate(4); // Thu in Week 1
+ return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
+};
+
+/** Checks date and time equality */
+Date.prototype.equalsTo = function(date) {
+ return ((this.getFullYear() == date.getFullYear()) &&
+ (this.getMonth() == date.getMonth()) &&
+ (this.getDate() == date.getDate()) &&
+ (this.getHours() == date.getHours()) &&
+ (this.getMinutes() == date.getMinutes()));
+};
+
+/** Set only the year, month, date parts (keep existing time) */
+Date.prototype.setDateOnly = function(date) {
+ var tmp = new Date(date);
+ this.setDate(1);
+ this.setFullYear(tmp.getFullYear());
+ this.setMonth(tmp.getMonth());
+ this.setDate(tmp.getDate());
+};
+
+/** Prints the date in a string according to the given format. */
+Date.prototype.print = function (str) {
+ var m = this.getMonth();
+ var d = this.getDate();
+ var y = this.getFullYear();
+ var wn = this.getWeekNumber();
+ var w = this.getDay();
+ var s = {};
+ var hr = this.getHours();
+ var pm = (hr >= 12);
+ var ir = (pm) ? (hr - 12) : hr;
+ var dy = this.getDayOfYear();
+ if (ir == 0)
+ ir = 12;
+ var min = this.getMinutes();
+ var sec = this.getSeconds();
+ s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
+ s["%A"] = Calendar._DN[w]; // full weekday name
+ s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
+ s["%B"] = Calendar._MN[m]; // full month name
+ // FIXME: %c : preferred date and time representation for the current locale
+ s["%C"] = 1 + Math.floor(y / 100); // the century number
+ s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
+ s["%e"] = d; // the day of the month (range 1 to 31)
+ // FIXME: %D : american date style: %m/%d/%y
+ // FIXME: %E, %F, %G, %g, %h (man strftime)
+ s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
+ s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
+ s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
+ s["%k"] = hr; // hour, range 0 to 23 (24h format)
+ s["%l"] = ir; // hour, range 1 to 12 (12h format)
+ s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
+ s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
+ s["%n"] = "\n"; // a newline character
+ s["%p"] = pm ? "PM" : "AM";
+ s["%P"] = pm ? "pm" : "am";
+ // FIXME: %r : the time in am/pm notation %I:%M:%S %p
+ // FIXME: %R : the time in 24-hour notation %H:%M
+ s["%s"] = Math.floor(this.getTime() / 1000);
+ s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
+ s["%t"] = "\t"; // a tab character
+ // FIXME: %T : the time in 24-hour notation (%H:%M:%S)
+ s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
+ s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
+ s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
+ // FIXME: %x : preferred date representation for the current locale without the time
+ // FIXME: %X : preferred time representation for the current locale without the date
+ s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
+ s["%Y"] = y; // year with the century
+ s["%%"] = "%"; // a literal '%' character
+
+ var re = /%./g;
+ if (!Calendar.is_ie5 && !Calendar.is_khtml)
+ return str.replace(re, function (par) { return s[par] || par; });
+
+ var a = str.match(re);
+ for (var i = 0; i < a.length; i++) {
+ var tmp = s[a[i]];
+ if (tmp) {
+ re = new RegExp(a[i], 'g');
+ str = str.replace(re, tmp);
+ }
+ }
+
+ return str;
+};
+
+Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
+Date.prototype.setFullYear = function(y) {
+ var d = new Date(this);
+ d.__msh_oldSetFullYear(y);
+ if (d.getMonth() != this.getMonth())
+ this.setDate(28);
+ this.__msh_oldSetFullYear(y);
+};
+
+// END: DATE OBJECT PATCHES
+
+
+// global object that remembers the calendar
+window._dynarch_popupCalendar = null;
diff --git a/public/bundles/dynarch_calendar/javascripts/calendar_stripped.js b/public/bundles/dynarch_calendar/javascripts/calendar_stripped.js
new file mode 100755
index 0000000..4fe03f1
--- /dev/null
+++ b/public/bundles/dynarch_calendar/javascripts/calendar_stripped.js
@@ -0,0 +1,14 @@
+/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com. Visit us at www.dynarch.com.
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+ Calendar=function(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.getDateToolTip=null;this.getDateText=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.firstDayOfWeek=typeof firstDayOfWeek=="number"?firstDayOfWeek:Calendar._FD;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.hiliteToday=true;this.multiple=null;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_ie5=(Calendar.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.innerHTML=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50){el._current=el.innerHTML;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.innerHTML=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled)return false;removeClass(el,"hilite");if(el.caldate)removeClass(el.parentNode,"rowhilite");if(el.calendar)el.calendar.tooltips.innerHTML=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new Date());cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;cell.innerHTML="
+<% end %>
+
+<%
+admin_table( :model => model ) do |builder|
+ builder.outer do
+ builder.prologue do
+ model.list_columns.build builder
+ end
+ @objects.each do |obj|
+ builder.with_object(obj) do
+ model.list_columns.build builder
+ end
+ end
+ builder.epilogue do
+ end
+ end
+end
+%>
+
+
+
+<%=
+pagination_links_each(@pages, :link_to_current_page => true, :window_size => 3) do |num|
+ "\n " + case num
+ when @pages.current_page.number
+ %(#{num})
+ when @pages.last_page.number
+ link_to num.to_s, similar_list_page(:page => num), { :class => 'end' }
+ else
+ link_to num.to_s, similar_list_page(:page => num)
+ end
+end
+%>
+
+<%= @pages.item_count %> <%=h human_model( nil, @pages.item_count ).downcase %>
+
The DHTML Calendar widget1
+is an (HTML) user interface element that gives end-users a friendly way to
+select date and time. It works in a web browser. The first versions only provided
+support for popup calendars, while starting with version 0.9 it also supports
+``flat'' display. A ``flat'' calendar is a calendar that stays visible in the
+page all the time. In this mode it could be very useful for ``blog'' pages and
+other pages that require the calendar to be always present.
+
+The calendar is compatible with most popular browsers nowadays. While it's
+created using web standards and it should generally work with any compliant
+browser, the following browsers were found to work: Mozilla/Firefox (the
+development platform), Netscape 6.0 or better, all other Gecko-based browsers,
+Internet Explorer 5.0 or better for Windows2, Opera 73, Konqueror 3.1.2 and Apple Safari for
+MacOSX.
+
+You can find the latest info and version at the calendar homepage:
DHTML is not ``another kind of HTML''. It's merely a naming convention. DHTML
+refers to the combination of HTML, CSS, JavaScript and DOM. DOM (Document
+Object Model) is a set of interfaces that glues the other three together. In
+other words, DOM allows dynamic modification of an HTML page through a program.
+JavaScript is our programming language, since that's what browsers like. CSS
+is a way to make it look good ;-). So all this soup is generically known as
+DHTML.
+
+Using DOM calls, the program dynamically creates a <table> element
+that contains a calendar for the given date and then inserts it in the document
+body. Then it shows this table at a specified position. Usually the position
+is related to some element in which the date needs to be displayed/entered,
+such as an input field.
+
+By assigning a certain CSS class to the table we can control the look of the
+calendar through an external CSS file; therefore, in order to change the
+colors, backgrounds, rollover effects and other stuff, you can only change a
+CSS file -- modification of the program itself is not necessary.
Here's a description of the project files, excluding documentation and example
+files.
+
+
+
+
+
the main program file (calendar.js). This defines all the logic
+behind the calendar widget.
+
+
+
the CSS files (calendar-*.css). Loading one of them is
+necessary in order to see the calendar as intended.
+
+
+
the language definition files (lang/calendar-*.js). They are
+plain JavaScript files that contain all texts that are displayed by the
+calendar. Loading one of them is necessary.
+
+
+
helper functions for quick setup of the calendar
+(calendar-setup.js). You can do fine without it, but starting with
+version 0.9.3 this is the recommended way to setup a calendar.
+Installing the calendar used to be quite a task until version 0.9.3. Starting
+with 0.9.3 I have included the file calendar-setup.js whose goal is to
+assist you to setup a popup or flat calendar in minutes. You are
+encouraged to modify this file and not calendar.js if you need
+extra customization, but you're on your own.
+
+First you have to include the needed scripts and style-sheet. Make sure you do
+this in your document's <head> section, also make sure you put the
+correct paths to the scripts.
+You want the button to popup a calendar widget when clicked? Just
+insert the following code immediately after the HTML form:
+
+
+
<script type="text/javascript">
+ Calendar.setup(
+ {
+ inputField : "data", // ID of the input field
+ ifFormat : "%m %d, %Y", // the date format
+ button : "trigger" // ID of the button
+ }
+ );
+</script>
+
+
+The Calendar.setup function, defined in calendar-setup.js
+takes care of ``patching'' the button to display a calendar when clicked. The
+calendar is by default in single-click mode and linked with the given input
+field, so that when the end-user selects a date it will update the input field
+with the date in the given format and close the calendar. If you are a
+long-term user of the calendar you probably remember that for doing this you
+needed to write a couple functions and add an ``onclick'' handler for the
+button by hand.
+
+By looking at the example above we can see that the function
+Calendar.setup receives only one parameter: a JavaScript object.
+Further, that object can have lots of properties that tell to the setup
+function how would we like to have the calendar. For instance, if we would
+like a calendar that closes at double-click instead of single-click we would
+also include the following: singleClick:false.
+
+For a list of all supported parameters please see the section
+2.3.
+Here's how to configure a flat calendar, using the same Calendar.setup
+function. First, you should have an empty element with an ID. This element
+will act as a container for the calendar. It can be any block-level element,
+such as DIV, TABLE, etc. We will use a DIV in this example.
+
+
+
<div id="calendar-container"></div>
+
+
+Then there is the JavaScript code that sets up the calendar into the
+``calendar-container'' DIV. The code can occur anywhere in HTML
+after the DIV element.
+
+
+
<script type="text/javascript">
+ function dateChanged(calendar) {
+ // Beware that this function is called even if the end-user only
+ // changed the month/year. In order to determine if a date was
+ // clicked you can use the dateClicked property of the calendar:
+ if (calendar.dateClicked) {
+ // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
+ var y = calendar.date.getFullYear();
+ var m = calendar.date.getMonth(); // integer, 0..11
+ var d = calendar.date.getDate(); // integer, 1..31
+ // redirect...
+ window.location = "/" + y + "/" + m + "/" + d + "/index.php";
+ }
+ };
+
+ Calendar.setup(
+ {
+ flat : "calendar-container", // ID of the parent element
+ flatCallback : dateChanged // our callback function
+ }
+ );
+</script>
+
+Following there is the complete list of properties interpreted by
+Calendar.setup. All of them have default values, so you can pass only those
+which you would like to customize. Anyway, you must pass at least one
+of inputField, displayArea or button, for a popup
+calendar, or flat for a flat calendar. Otherwise you will get a
+warning message saying that there's nothing to setup.
+
+
+
property
type
description
default
+
+
inputField
+
string
The ID of your input field.
+
null
+
+
displayArea
+
string
This is the ID of a <span>, <div>, or any other element that you would like to use to display the current date. This is generally useful only if the input field is hidden, as an area to display the date.
+
null
+
+
button
+
string
The ID of the calendar ``trigger''. This is an element (ordinarily a button or an image) that will dispatch a certain event (usually ``click'') to the function that creates and displays the calendar.
+
null
+
+
eventName
+
string
The name of the event that will trigger the calendar. The name should be without the ``on'' prefix, such as ``click'' instead of ``onclick''. Virtually all users will want to let this have the default value (``click''). Anyway, it could be useful if, say, you want the calendar to appear when the input field is focused and have no trigger button (in this case use ``focus'' as the event name).
+
``click''
+
+
ifFormat
+
string
The format string that will be used to enter the date in the input field. This format will be honored even if the input field is hidden.
+
``%Y/%m/%d''
+
+
daFormat
+
string
Format of the date displayed in the displayArea (if specified).
+
``%Y/%m/%d''
+
+
singleClick
+
boolean
Wether the calendar is in ``single-click mode'' or ``double-click mode''. If true (the default) the calendar will be created in single-click mode.
+
true
+
+
disableFunc
+
function
A function that receives a JS Date object. It should return
+true if that date has to be disabled, false otherwise.
+DEPRECATED (see below).
+
null
+
+
dateStatusFunc
+
function
A function that receives a JS Date object and returns a boolean
+or a string. This function allows one to set a certain CSS class to some
+date, therefore making it look different. If it returns true then
+the date will be disabled. If it returns false nothing special
+happens with the given date. If it returns a string then that will be taken
+as a CSS class and appended to the date element. If this string is
+``disabled'' then the date is also disabled (therefore is like returning
+true). For more information please also refer to section
+5.3.8.
+
null
+
+
firstDay
+
integer
Specifies which day is to be displayed as the first day of
+week. Possible values are 0 to 6; 0 means Sunday, 1 means Monday, ..., 6
+means Saturday. The end user can easily change this too, by clicking on the
+day name in the calendar header.
+
0
+
+
weekNumbers
+
boolean
If ``true'' then the calendar will display week numbers.
+
true
+
+
align
+
string
Alignment of the calendar, relative to the reference element. The
+reference element is dynamically chosen like this: if a displayArea is
+specified then it will be the reference element. Otherwise, the input field
+is the reference element. For the meaning of the alignment characters
+please section 5.3.11.
+
``Bl''
+
+
range
+
array
An array having exactly 2 elements, integers. (!) The first [0] element is the minimum year that is available, and the second [1] element is the maximum year that the calendar will allow.
+
[1900, 2999]
+
+
flat
+
string
If you want a flat calendar, pass the ID of the parent object in
+this property. If not, pass null here (or nothing at all as
+null is the default value).
+
null
+
+
flatCallback
+
function
You should provide this function if the calendar is flat. It
+will be called when the date in the calendar is changed with a reference to
+the calendar object. See section 2.2 for an example
+of how to setup a flat calendar.
+
null
+
+
onSelect
+
function
If you provide a function handler here then you have to manage
+the ``click-on-date'' event by yourself. Look in the calendar-setup.js and
+take as an example the onSelect handler that you can see there.
+
null
+
+
onClose
+
function
This handler will be called when the calendar needs to close.
+You don't need to provide one, but if you do it's your responsibility to
+hide/destroy the calendar. You're on your own. Check the calendar-setup.js
+file for an example.
+
null
+
+
onUpdate
+
function
If you supply a function handler here, it will be called right
+after the target field is updated with a new date. You can use this to
+chain 2 calendars, for instance to setup a default date in the second just
+after a date was selected in the first.
+
null
+
+
date
+
date
This allows you to setup an initial date where the calendar will be
+positioned to. If absent then the calendar will open to the today date.
+
null
+
+
showsTime
+
boolean
If this is set to true then the calendar will also
+allow time selection.
+
false
+
+
timeFormat
+
string
Set this to ``12'' or ``24'' to configure the way that the
+calendar will display time.
+
``24''
+
+
electric
+
boolean
Set this to ``false'' if you want the calendar to update the
+field only when closed (by default it updates the field at each date change,
+even if the calendar is not closed)
true
+
+
position
+
array
Specifies the [x, y] position, relative to page's top-left corner,
+where the calendar will be displayed. If not passed then the position will
+be computed based on the ``align'' parameter. Defaults to ``null'' (not
+used).
null
+
+
cache
+
boolean
Set this to ``true'' if you want to cache the calendar object.
+This means that a single calendar object will be used for all fields that
+require a popup calendar
false
+
+
showOthers
+
boolean
If set to ``true'' then days belonging to months overlapping
+with the currently displayed month will also be displayed in the calendar
+(but in a ``faded-out'' color)
This section presents some common ways to setup a calendar using the
+Calendar.setup function detailed in the previous section.
+
+We don't discuss here about loading the JS or CSS code -- so make sure you
+add the proper <script> and <style> or <link> elements in your
+HTML code. Also, when we present input fields, please note that they should
+be embedded in some form in order for data to be actually sent to server; we
+don't discuss these things here because they are not related to our
+calendar.
+Note that this code does more actually; the only required fields are
+those marked with “//*” -- that is, the ID of the input field and the ID of
+the button need to be passed to Calendar.setup in order for the
+calendar to be properly assigned to this input field. As one can easily
+guess from the argument names, the other arguments configure a certain date
+format, instruct the calendar to also include a time selector and display
+every year in the drop-down boxes (the “step” parameter) -- instead of showing
+every other year as the default calendar does.
+Note that the same 2 parameters are required as in the previous case; the
+difference is that the 'button' parameter now gets the ID of the image
+instead of the ID of the button. But the event is the same: at 'onclick' on
+the element that is passed as 'button', the calendar will be shown.
+
+The above code additionally sets an alignment mode -- the parameters are
+described in 5.3.11.
Sometimes, to assure that the date is well formatted, you might want not to
+allow the end user to write a date manually. This can easily be achieved
+with an input field by setting its readonly attribute, which is
+defined by the HTML4 standard; however, here's an even nicer approach: our
+calendar widget allows you to use a hidden field as the way to pass data to
+server, and a “display area” to show the end user the selected date. The
+“display area” can be any HTML element, such as a DIV or a SPAN or
+whatever -- we will use a SPAN in our sample.
+The above code will configure a calendar attached to the hidden field and to
+the SPAN having the id=“show_d”. When the SPAN element is clicked, the
+calendar opens and allows the end user to chose a date. When the date is
+chosen, the input field will be updated with the value in the format
+“%Y/%d/%m”, and the SPAN element will display the date in a
+friendlier format (defined by “daFormat”).
+
+Beware that using this approach will make your page unfunctional in browsers
+that do not support JavaScript or our calendar.
Supposing you want to create 2 fields that hold an interval of exactly one
+week. The first is the starting date, and the second is the ending date.
+You want the fields to be automatically updated when some date is clicked in
+one or the other, in order to keep exactly one week difference between them.
+
+
+
<input type="text" name="date" id="f_date_a" />
+<input type="text" name="date" id="f_calcdate" />
+
+<script type="text/javascript">
+ function catcalc(cal) {
+ var date = cal.date;
+ var time = date.getTime()
+ // use the _other_ field
+ var field = document.getElementById("f_calcdate");
+ if (field == cal.params.inputField) {
+ field = document.getElementById("f_date_a");
+ time -= Date.WEEK; // substract one week
+ } else {
+ time += Date.WEEK; // add one week
+ }
+ var date2 = new Date(time);
+ field.value = date2.print("%Y-%m-%d %H:%M");
+ }
+ Calendar.setup({
+ inputField : "f_date_a",
+ ifFormat : "%Y-%m-%d %H:%M",
+ showsTime : true,
+ timeFormat : "24",
+ onUpdate : catcalc
+ });
+ Calendar.setup({
+ inputField : "f_calcdate",
+ ifFormat : "%Y-%m-%d %H:%M",
+ showsTime : true,
+ timeFormat : "24",
+ onUpdate : catcalc
+ });
+</script>
+
+
+The above code will configure 2 input fields with calendars attached, as
+usual. The first thing to note is that there's no trigger button -- in such
+case, the calendar will popup when one clicks into the input field. Using
+the onUpdate parameter, we pass a reference to a function of ours
+that will get called after a date was selected. In that function we
+determine what field was updated and we compute the date in the other input
+field such that it keeps a one week difference between the two. Enjoy! :-)
This sample can be found in “simple-2.html”. It will configure a
+flat calendar that is always displayed in the page, in the DIV having the
+id=“calendar-container”. When a date is clicked our function hander gets
+called (dateChanged) and it will compute an URL to jump to based on
+the selected date, then use window.location to visit the new link.
+
+
+
<div style="float: right; margin-left: 1em; margin-bottom: 1em;"
+id="calendar-container"></div>
+
+<script type="text/javascript">
+ function dateChanged(calendar) {
+ // Beware that this function is called even if the end-user only
+ // changed the month/year. In order to determine if a date was
+ // clicked you can use the dateClicked property of the calendar:
+ if (calendar.dateClicked) {
+ // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
+ var y = calendar.date.getFullYear();
+ var m = calendar.date.getMonth(); // integer, 0..11
+ var d = calendar.date.getDate(); // integer, 1..31
+ // redirect...
+ window.location = "/" + y + "/" + m + "/" + d + "/index.php";
+ }
+ };
+
+ Calendar.setup(
+ {
+ flat : "calendar-container", // ID of the parent element
+ flatCallback : dateChanged // our callback function
+ }
+ );
+</script>
+
So you want to display certain dates in a different color, or with bold
+font, or whatever, right? Well, no problem -- our calendar can do this as
+well. It doesn't matter if it's a flat or popup calendar -- we'll use a flat
+one for this sample. The idea, however, is that you need to have the dates
+in an array or a JavaScript object -- whatever is suitable for your way of
+thinking -- and use it from a function that returns a value, telling the
+calendar what kind of date is the passed one.
+
+Too much talking, here's the code ;-)
+
+
+
<!-- this goes into the <head> tag -->
+<style type="text/css">
+ .special { background-color: #000; color: #fff; }
+</style>
+
+<!-- and the rest inside the <body> -->
+<div style="float: right; margin-left: 1em; margin-bottom: 1em;"
+id="calendar-container"></div>
+
+<script type="text/javascript">
+ var SPECIAL_DAYS = {
+ 0 : [ 13, 24 ], // special days in January
+ 2 : [ 1, 6, 8, 12, 18 ], // special days in March
+ 8 : [ 21, 11 ] // special days in September
+ };
+
+ function dateIsSpecial(year, month, day) {
+ var m = SPECIAL_DAYS[month];
+ if (!m) return false;
+ for (var i in m) if (m[i] == day) return true;
+ return false;
+ };
+
+ function dateChanged(calendar) {
+ // Beware that this function is called even if the end-user only
+ // changed the month/year. In order to determine if a date was
+ // clicked you can use the dateClicked property of the calendar:
+ if (calendar.dateClicked) {
+ // OK, a date was clicked, redirect to /yyyy/mm/dd/index.php
+ var y = calendar.date.getFullYear();
+ var m = calendar.date.getMonth(); // integer, 0..11
+ var d = calendar.date.getDate(); // integer, 1..31
+ // redirect...
+ window.location = "/" + y + "/" + m + "/" + d + "/index.php";
+ }
+ };
+
+ function ourDateStatusFunc(date, y, m, d) {
+ if (dateIsSpecial(y, m, d))
+ return "special";
+ else
+ return false; // other dates are enabled
+ // return true if you want to disable other dates
+ };
+
+ Calendar.setup(
+ {
+ flat : "calendar-container", // ID of the parent element
+ flatCallback : dateChanged, // our callback function
+ dateStatusFunc : ourDateStatusFunc
+ }
+ );
+</script>
+
+
+So the above code creates a normal flat calendar, like in the previous
+sample. We hook into it with the function “ourDateStatusFunc”,
+which receives a date object as the first argument, and also the year,
+month, date as the next 3 arguments (normally, you can extract year, month,
+date from the first parameter too, but we pass them separately for
+convenience, as it's very likely that they are going to be used in this
+function).
+
+So, this function receives a date. It can return false if you want
+no special action to be taken on that date, true if that date
+should be disabled (unselectable), or a string if you want to assign a
+special CSS class to that date. We return “special” for the dates that we
+want to highlight -- and note that we defined a “special” look for them in
+the CSS section.
+
+I used a simple approach here to define what dates are special. There's a
+JavaScript object (the SPECIAL_DAYS global variable) which holds an array
+of dates for each month. Month numbers start at zero (January). Months
+that don't contain special dates can be absent from this object. Note that
+the way to implement this is completely separated from the calendar
+code -- therefore, feel free to use your imagination if you have better
+ideas. :-)
Starting version 1.0, the calendar is able to handle multiple dates
+selection. You just need to pass the “multiple” parameter to
+Calendar.setup and add some special code that interprets the
+selection once the calendar is closed.
+
+
+
<a id="trigger" href="#">[open calendar...]</a>
+<div id="output"></div>
+<script type="text/javascript">//<![CDATA[
+ // the default multiple dates selected,
+ // first time the calendar is displayed
+ var MA = [];
+
+ function closed(cal) {
+
+ // here we'll write the output; this is only for example. You
+ // will normally fill an input field or something with the dates.
+ var el = document.getElementById("output");
+
+ // reset initial content.
+ el.innerHTML = "";
+
+ // Reset the "MA", in case one triggers the calendar again.
+ // CAREFUL! You don't want to do "MA = [];". We need to modify
+ // the value of the current array, instead of creating a new one.
+ // Calendar.setup is called only once! :-) So be careful.
+ MA.length = 0;
+
+ // walk the calendar's multiple dates selection hash
+ for (var i in cal.multiple) {
+ var d = cal.multiple[i];
+ // sometimes the date is not actually selected,
+ // so let's check
+ if (d) {
+ // OK, selected. Fill an input field or something.
+ el.innerHTML += d.print("%A, %Y %B %d") + "<br />";
+ // and push it in the "MA", in case one triggers the calendar again.
+ MA[MA.length] = d;
+ }
+ }
+ cal.hide();
+ return true;
+ };
+
+ Calendar.setup({
+ align : "BR",
+ showOthers : true,
+ multiple : MA, // pass the initial or computed array of multiple dates
+ onClose : closed,
+ button : "trigger"
+ });
+//]]></script>
+
+
+The above code creates a popup calendar and passes to it an array of dates,
+which is initially empty, in the “multiple” argument. When the calendar is
+closed it will call our “closed” function handler; in this handler
+we determine what dates were actually selected, inspecting the
+“cal.multiple” property, we display them in a DIV element right
+next to the <a> element that opens the calendar, and we reinitialize the
+global array of selected dates (which will be used if the end user opens the
+calendar again). I guess the code speaks for itself, right? :-)
+Basically you should be able to setup the calendar with the function presented
+in the previous section. However, if for some reason Calendar.setup
+doesn't provide all the functionality that you need and you want to tweak into
+the process of creating and configuring the calendar ``by hand'', then this
+section is the way to go.
+
+The file calendar.js implements the functionality of the calendar.
+All (well, almost all) functions and variables are embedded in the JavaScript
+object ``Calendar''.
+
+You can instantiate a Calendar object by calling the constructor, like
+this: var cal = new Calendar(...). We will discuss the parameters
+later. After creating the object, the variable cal will contain a
+reference to it. You can use this reference to access further options of the
+calendar, for instance:
+
+
+
cal.weekNumbers = false; // do not display week numbers
+cal.showsTime = true; // include a time selector
+cal.setDateFormat("%Y.%m.%d %H:%M"); // set this format: 2003.12.31 23:59
+cal.setDisabledHandler(function(date, year, month, day) {
+ // verify date and return true if it has to be disabled
+ // ``date'' is a JS Date object, but if you only need the
+ // year, month and/or day you can get them separately as
+ // next 3 parameters, as you can see in the declaration
+ if (year == 2004) {
+ // disable all dates from 2004
+ return true;
+ }
+ return false;
+});
+
+
+etc. Prior to version
+0.9.3 this was the only way to configure it. The Calendar.setup
+function, documented in section 2, basically does the same
+things (actually more) in order to setup the calendar, based on the parameters
+that you provided.
The calendar is created by following some steps (even the function
+Calendar.setup, described in section 2, does the
+same). While you can skip optional (marked ``opt'') steps if you're happy with
+the defaults, please respect the order below.
+
+
+
+
+
Instantiate a Calendar object. Details about this in
+section 5.1.
+
+
+
opt Set the weekNumbers property to false if you don't want
+the calendar to display week numbers.
+
+
+
opt Set the showsTime property to true if you
+want the calendar to also provide a time selector.
+
+
+
opt Set the time24 property to false if you want
+the time selector to be in 12-hour format. Default is 24-hour format. This
+property only has effect if you also set showsTime to
+true.
+
+
+
opt Set the range of years available for selection (see section
+5.3.15). The default range is [1970..2050].
+
+
+
opt Set the getDateStatus property. You should pass
+here a function that receives a JavaScript Date object and returns
+true if the given date should be disabled, false otherwise (details in
+section 5.3.7).
+
+
+
opt Set a date format. Your handler function, passed to the
+calendar constructor, will be called when a date is selected with a reference
+to the calendar and a date string in this format.
+
+
+
Create the HTML elements related to the calendar. This step
+practically puts the calendar in your HTML page. You simply call
+Calendar.create(). You can give an optional parameter if you wanna
+create a flat calendar (details in section 5.3.1).
+
+
+
opt Initialize the calendar to a certain date, for instance from
+the input field.
As you could see in the previous section, there are more steps to be followed
+in order to setup the calendar. This happens because there are two different
+things that need to be accomplished: first there is the JavaScript object, that
+is created with new Calendar(...). Secondly there are the HTML
+elements that actually lets you see and manipulate the calendar.
+
+
+[ Those that did UI4 programming, no matter in what
+language and on what platform, may be familiar with this concept. First there
+is the object in memory that lets you manipulate the UI element, and secondly
+there is the UI element (known as ``control'', ``window'', ``widget'', etc.),
+also in memory but you don't usually access it directly. ]
+
+By instantiating the calendar we create the JavaScript object. It lets us
+configure some properties and it also knows how to create the UI element (the
+HTML elements actually) that will eventually be what the end-user sees on
+screen. Creation of the HTML element is accomplished by the function
+Calendar.create. It knows how to create popup or flat calendars.
+This function is described in section 5.3.1.
+
+Some properties need to be set prior to creating the HTML elements, because
+otherwise they wouldn't have any effect. Such a property is
+weekNumbers -- it has the default value ``true'', and if you don't
+want the calendar to display the week numbers you have to set it to false. If,
+however, you do that after calling Calendar.create the calendar
+would still display the week numbers, because the HTML elements are already
+created (including the <td>-s in the <table> element that
+should contain the week numbers). For this reason the order of the steps above
+is important.
+
+Another example is when you want to show the calendar. The ``create'' function
+does create the HTML elements, but they are initially hidden (have the style
+``display: none'') unless the calendar is a flat calendar that should be always
+visible in the page. Obviously, the Calendar.show function should be
+called after calling Calendar.create.
Suppose the end-user has popped up a calendar and selects a date. The calendar
+then closes. What really happens now?
+
+There are two approaches. The first (used in very old versions of the
+calendar) was to drop completely the Calendar object and when the end-user pops
+up the calendar again to create another one. This approach is bad for more
+reasons:
+
+
+
+
+
creating the JavaScript object and HTML elements is time-consuming
+
+
+
we may loose some end-user preferences (i.e. he might prefer to have
+Monday for the first day of week and probably already clicked it the first time
+when the calendar was opened, but now he has to do it again)
+
+
+
+
+The second approach, implemented by the Calendar.setup function, is to
+cache the JavaScript object. It does this by checking the global variable
+window.calendar and if it is not null it assumes it is the created
+Calendar object. When the end-user closes the calendar, our code will only
+call ``hide'' on it, therefore keeping the JavaScript object and the
+HTML elements in place.
+
+CAVEAT: Since time selection support was introduced, this
+``object caching'' mechanism has the following drawback: if you once created
+the calendar with the time selection support, then other items that may not
+require this functionality will still get a calendar with the time selection
+support enabled. And reciprocal. ;-) Hopefully this will be corrected in a
+later version, but for now it doesn't seem such a big problem.
You might rightfully wonder how is the calendar related to the input field?
+Who tells it that it has to update that input field when a date is
+selected, or that it has to jump to that URL when a date is clicked in
+flat mode?
+
+All this magic is done through callback functions. The calendar doesn't know
+anything about the existence of an input field, nor does it know where to
+redirect the browser when a date is clicked in flat mode. It just calls your
+callback when a particular event is happening, and you're responsible to handle
+it from there. For a general purpose library I think this is the best model of
+making a truly reusable thing.
+
+The calendar supports the following user callbacks:
+
+
+
+
+
onSelect -- this gets called when the end-user changes the date in the
+calendar. Documented in section 5.1.
+
+
+
onClose -- this gets called when the calendar should close. It's
+user's responsibility to close the calendar. Details in section
+5.1.
+
+
+
getDateStatus -- this function gets called for any day in a month,
+just before displaying the month. It is called with a JavaScript Date
+object and should return true if that date should be disabled, false
+if it's an ordinary date and no action should be taken, or it can return a
+string in which case the returned value will be appended to the element's CSS
+class (this way it provides a powerful way to make some dates ``special'',
+i.e. highlight them differently). Details in section
+5.3.8.
var calendar = Calendar(firstDayOfWeek, date, onSelect, onClose);
+
+
+Parameters are as follows:
+
+
+
+
+
firstDayOfWeek -- specifies which day is to be displayed as the first
+day of week. Possible values are 0 to 6; 0 means Sunday, 1 means Monday,
+..., 6 means Saturday.
+
+
+
date -- a JavaScript Date object or null. If null
+is passed then the calendar will default to today date. Otherwise it will
+initialize on the given date.
+
+
+
onSelect -- your callback for the ``onChange'' event. See above.
+
+
+
onClose -- your callback for the ``onClose'' event. See above.
+Here is a typical implementation of this function:
+
+
+
function onSelect(calendar, date) {
+ var input_field = document.getElementById("date");
+ input_field.value = date;
+};
+
+
+date is in the format selected with calendar.setDateFormat
+(see section 5.3.5). This code simply updates the
+input field. If you want the calendar to be in single-click mode then you
+should also close the calendar after you updated the input field, so we come to
+the following version:
+
+
+
function onSelect(calendar, date) {
+ var input_field = document.getElementById("date");
+ input_field.value = date;
+ if (calendar.dateClicked) {
+ calendar.callCloseHandler(); // this calls "onClose" (see above)
+ }
+};
+
+
+Note that we checked the member variable dateClicked and
+only hide the calendar if it's true. If this variable is false it
+means that no date was actually selected, but the user only changed the
+month/year using the navigation buttons or the menus. We don't want to hide
+the calendar in that case.
+This event is triggered when the calendar should close. It should hide or
+destroy the calendar object -- the calendar itself just triggers the event, but
+it won't close itself.
+
+A typical implementation of this function is the following:
+
+
+
function onClose(calendar) {
+ calendar.hide();
+ // or calendar.destroy();
+};
+
+After creating the Calendar object you can access the following properties:
+
+
+
+
+
date -- is a JavaScript Date object. It will always
+reflect the date shown in the calendar (yes, even if the calendar is hidden).
+
+
+
isPopup -- if this is true then the current Calendar object is
+a popup calendar. Otherwise (false) we have a flat calendar. This variable is
+set from Calendar.create and has no meaning before this function was
+called.
+
+
+
dateClicked -- particularly useful in the onSelect
+handler, this variable tells us if a date was really clicked. That's because
+the onSelect handler is called even if the end-user only changed the
+month/year but did not select a date. We don't want to close the calendar in
+that case.
+
+
+
weekNumbers -- if true (default) then the calendar
+displays week numbers. If you don't want week numbers you have to set this
+variable to falsebefore calling Calendar.create.
+
+
+
showsTime - if you set this to true (it is
+false by default) then the calendar will also include a time selector.
+
+
+
time24 - if you set this to false then the time
+selector will be in 12-hour format. It is in 24-hour format by default.
+
+
+
firstDayOfWeek -- specifies the first day of week (0 to 6, pass
+0 for Sunday, 1 for Monday, ..., 6 for Saturday). This variable is set from
+constructor, but you still have a chance to modify it before calling
+Calendar.create.
+
+
+
+
+There are lots of other member variables, but one should access them only
+through member functions so I won't document them here.
+This function creates the afferent HTML elements that are needed to display the
+calendar. You should call it after setting the calendar properties. Synopsis:
+
+
calendar.create(); // creates a popup calendar
+ // -- or --
+calendar.create(document.getElementById(parent_id)); // makes a flat calendar
+
+
+It can create a popup calendar or a flat calendar. If the ``parent'' argument
+is present (it should be a reference -- not ID -- to an HTML element) then
+a flat calendar is created and it is inserted in the given element.
+
+At any moment, given a reference to a calendar object, we can inspect if it's a
+popup or a flat calendar by checking the boolean member variable
+isPopup:
+
+
+
if (calendar.isPopup) {
+ // this is a popup calendar
+} else {
+ // this is a flat calendar
+}
+
+This function calls the second user callback (the
+onClose handler). It's useful when you want to have a
+``single-click'' calendar -- just call this in your onSelect handler,
+if a date was clicked.
+Call this function to hide the calendar. The calendar object and HTML elements
+will not be destroyed, thus you can later call one of the show
+functions on the same element.
+This function configures the format in which the calendar reports the date to
+your ``onSelect'' handler. Call it like this:
+
+
+
calendar.setDateFormat("%y/%m/%d");
+
+
+As you can see, it receives only one parameter, the required format. The magic
+characters are the following:
+
+
+
+
%a
abbreviated weekday name
+
%A
full weekday name
+
%b
abbreviated month name
+
%B
full month name
+
%C
century number
+
%d
the day of the month ( 00 .. 31 )
+
%e
the day of the month ( 0 .. 31 )
+
%H
hour ( 00 .. 23 )
+
%I
hour ( 01 .. 12 )
+
%j
day of the year ( 000 .. 366 )
+
%k
hour ( 0 .. 23 )
+
%l
hour ( 1 .. 12 )
+
%m
month ( 01 .. 12 )
+
%M
minute ( 00 .. 59 )
+
%n
a newline character
+
%p
``PM'' or ``AM''
+
%P
``pm'' or ``am''
+
%S
second ( 00 .. 59 )
+
%s
number of seconds since Epoch (since Jan 01 1970 00:00:00 UTC)
+
%t
a tab character
+
%U, %W, %V
the week number
+
%u
the day of the week ( 1 .. 7, 1 = MON )
+
%w
the day of the week ( 0 .. 6, 0 = SUN )
+
%y
year without the century ( 00 .. 99 )
+
%Y
year including the century ( ex. 1979 )
+
%%
a literal % character
+
+There are more algorithms for computing the week number. All
+three specifiers currently implement the same one, as defined by ISO 8601:
+``the week 01 is the week that has the Thursday in the current year, which is
+equivalent to the week that contains the fourth day of January. Weeks start on
+Monday.''
+Has the same prototype as Calendar.setDateFormat, but refers to the
+format of the date displayed in the ``status bar'' when the mouse is over some
+date.
+This function allows you to specify a callback function that checks if a
+certain date must be disabled by the calendar. You are responsible to write
+the callback function. Synopsis:
+
+
+
function disallowDate(date) {
+ // date is a JS Date object
+ if ( date.getFullYear() == 2003 &&
+ date.getMonth() == 6 /* July, it's zero-based */ &&
+ date.getDate() == 5 ) {
+ return true; // disable July 5 2003
+ }
+ return false; // enable other dates
+};
+
+calendar.setDisabledHandler(disallowDate);
+
+
+If you change this function in ``real-time'', meaning, without creating a new
+calendar, then you have to call calendar.refresh() to make it
+redisplay the month and take into account the new disabledHandler.
+Calendar.setup does this, so you have no such trouble with it.
+
+Note that disallowDate should be very fast, as it is called for each
+date in the month. Thus, it gets called, say, 30 times before displaying the
+calendar, and 30 times when the month is changed. Tests I've done so far show
+that it's still good, but in the future I might switch it to a different design
+(for instance, to call it once per month and to return an array of dates that
+must be disabled).
+
+This function should be considered deprecated in the favor of
+Calendar.setDateStatusHandler, described below.
+This function obsoletes Calendar.setDisabledHandler. You call it with
+a function parameter, but this function can return a boolean
+or a string. If the return value is a boolean (true or
+false) then it behaves just like setDisabledHandler,
+therefore disabling the date if the return value is true.
+
+If the returned value is a string then the given date will gain an additional
+CSS class, namely the returned value. You can use this to highlight some dates
+in some way. Note that you are responsible for defining the CSS class that you
+return. If you return the string ``disabled'' then that date will be disabled,
+just as if you returned true.
+
+Here is a simple scenario that shows what you can do with this function. The
+following should be present in some of your styles, or in the document head in
+a STYLE tag (but put it after the place where the calendar styles were
+loaded):
+And you would use the following code before calling Calendar.create():
+
+
+
// this table holds your special days, so that we can automatize
+// things a bit:
+var SPECIAL_DAYS = {
+ 0 : [ 13, 24 ], // special days in January
+ 2 : [ 1, 6, 8, 12, 18 ], // special days in March
+ 8 : [ 21, 11 ], // special days in September
+ 11 : [ 25, 28 ] // special days in December
+};
+
+// this function returns true if the passed date is special
+function dateIsSpecial(year, month, day) {
+ var m = SPECIAL_DAYS[month];
+ if (!m) return false;
+ for (var i in m) if (m[i] == day) return true;
+ return false;
+}
+
+// this is the actual date status handler. Note that it receives the
+// date object as well as separate values of year, month and date, for
+// your confort.
+function dateStatusHandler(date, y, m, d) {
+ if (dateIsSpecial(y, m, d)) return ``special'';
+ else return false;
+ // return true above if you want to disable other dates
+}
+
+// configure it to the calendar
+calendar.setDateStatusHandler(dateStatusHandler);
+
+
+The above code adds the ``special'' class name to some dates that are defined
+in the SPECIAL_DAYS table. Other dates will simply be displayed as default,
+enabled.
+This function is useful if you want to display the calendar near some element.
+You call it like this:
+
+
+
calendar.showAtElement(element, align);
+
+
+where element is a reference to your element (for instance it can be the input
+field that displays the date) and align is an optional parameter, of type string,
+containing one or two characters. For instance, if you pass "Br" as
+align, the calendar will appear below the element and with its right
+margin continuing the element's right margin.
+
+As stated above, align may contain one or two characters. The first character
+dictates the vertical alignment, relative to the element, and the second
+character dictates the horizontal alignment. If the second character is
+missing it will be assumed "l" (the left margin of the calendar will
+be at the same horizontal position as the left margin of the element).
+
+The characters given for the align parameters are case sensitive. This
+function only makes sense when the calendar is in popup mode. After computing
+the position it uses Calendar.showAt to display the calendar there.
+Use this function to parse a date given as string and to move the calendar to
+that date.
+
+The algorithm tries to parse the date according to the format that was
+previously set with Calendar.setDateFormat; if that fails, it still
+tries to get some valid date out of it (it doesn't read your thoughts, though).
The calendar code was intentionally embedded in an object to make it have as
+less as possible side effects. However, there are some -- not harmful, after
+all. Here is a list of side effects; you can count they already happened after
+calendar.js was loaded.
+
+
+
+
+
The global variable window.calendar will be set to null. This
+variable is used by the calendar code, especially when doing drag & drop for
+moving the calendar. In the future I might get rid of it, but for now it
+didn't harm anyone.
+
+
+
The JavaScript Date object is modified. We add some properties
+and functions that are very useful to our calendar. It made more sense to add
+them directly to the Date object than to the calendar itself.
+Complete list:
+
+
+
+
+
Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+
Date.SECOND = 1000 /* milliseconds */;
+
+
Date.MINUTE = 60 * Date.SECOND;
+
+
Date.HOUR = 60 * Date.MINUTE;
+
+
Date.DAY = 24 * Date.HOUR;
+
+
Date.WEEK = 7 * Date.DAY;
+
+
+
Date.prototype.getMonthDays(month) -- returns the number of days
+of the given month, or of the current date object if no month was given.
+
+
+
Date.prototype.getWeekNumber() -- returns the week number of the
+date in the current object.
+
+
+
Date.prototype.equalsTo(other_date) -- compare the current date
+object with other_date and returns true if the dates are
+equal. It ignores time.
+
+
+
Date.prototype.print(format) -- returns a string with the
+current date object represented in the given format. It implements the format
+specified in section 5.3.5.
The following people either sponsored, donated money to the project or bought
+commercial licenses (listed in reverse chronological order). Your name could
+be here too! If you wish to sponsor the project (for instance request a
+feature and pay me for implementing it) or donate some money please
+please contact me at mihai_bazon@yahoo.com.
1
+by the term ``widget'' I understand a single element of user interface.
+But that's in Linux world. For those that did lots of Windows
+programming the term ``control'' might be more familiar
+
+
2 people report that the calendar does
+not work with IE5/Mac. However, this browser was discontinued and we
+believe that supporting it doesn't worth the efforts, given the fact that
+it has the worst, buggiest implementation for DOM I've ever seen.
+
3 under Opera 7 the calendar still lacks some functionality, such as
+keyboard navigation; also Opera doesn't seem to allow disabling text
+selection when one drags the mouse on the page; despite all that, the
+calendar is still highly functional under Opera 7 and looks as good as
+in other supported browsers.
+Last modified: Saturday, March 5th, 2005
+HTML conversion by TeX2page 2004-09-11
+
+
+
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/reference.pdf b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/reference.pdf
new file mode 100644
index 0000000..a09497f
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/doc/reference.pdf differ
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon1.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon1.gif
new file mode 100755
index 0000000..cb9aee7
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon1.gif differ
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon2.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon2.gif
new file mode 100644
index 0000000..8680b8f
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon2.gif differ
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon3.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon3.gif
new file mode 100644
index 0000000..a549652
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon3.gif differ
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon4.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon4.gif
new file mode 100755
index 0000000..d7538cb
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/calendar_icon4.gif differ
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow.gif
new file mode 100755
index 0000000..ed2dee0
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow.gif differ
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow2.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow2.gif
new file mode 100644
index 0000000..40c0aad
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/menuarrow2.gif differ
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/pixel.gif b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/pixel.gif
new file mode 100755
index 0000000..b740647
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/images/pixel.gif differ
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-en.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-en.js
new file mode 100755
index 0000000..0dbde79
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-en.js
@@ -0,0 +1,127 @@
+// ** I18N
+
+// Calendar EN language
+// Author: Mihai Bazon,
+// Encoding: any
+// Distributed under the same terms as the calendar itself.
+
+// For translators: please use UTF-8 if possible. We strongly believe that
+// Unicode is the answer to a real internationalized world. Also please
+// include your contact information in the header, as can be seen above.
+
+// full day names
+Calendar._DN = new Array
+("Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+ "Sunday");
+
+// Please note that the following array of short day names (and the same goes
+// for short month names, _SMN) isn't absolutely necessary. We give it here
+// for exemplification on how one can customize the short day names, but if
+// they are simply the first N letters of the full name you can simply say:
+//
+// Calendar._SDN_len = N; // short day name length
+// Calendar._SMN_len = N; // short month name length
+//
+// If N = 3 then this is not needed either since we assume a value of 3 if not
+// present, to be compatible with translation files that were written before
+// this feature.
+
+// short day names
+Calendar._SDN = new Array
+("Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+ "Sun");
+
+// First day of the week. "0" means display Sunday first, "1" means display
+// Monday first, etc.
+Calendar._FD = 0;
+
+// full month names
+Calendar._MN = new Array
+("January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December");
+
+// short month names
+Calendar._SMN = new Array
+("Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec");
+
+// tooltips
+Calendar._TT = {};
+Calendar._TT["INFO"] = "About the calendar";
+
+Calendar._TT["ABOUT"] =
+"DHTML Date/Time Selector\n" +
+"(c) dynarch.com 2002-2005 / Author: Mihai Bazon\n" + // don't translate this this ;-)
+"For latest version visit: http://www.dynarch.com/projects/calendar/\n" +
+"Distributed under GNU LGPL. See http://gnu.org/licenses/lgpl.html for details." +
+"\n\n" +
+"Date selection:\n" +
+"- Use the \xab, \xbb buttons to select year\n" +
+"- Use the " + String.fromCharCode(0x2039) + ", " + String.fromCharCode(0x203a) + " buttons to select month\n" +
+"- Hold mouse button on any of the above buttons for faster selection.";
+Calendar._TT["ABOUT_TIME"] = "\n\n" +
+"Time selection:\n" +
+"- Click on any of the time parts to increase it\n" +
+"- or Shift-click to decrease it\n" +
+"- or click and drag for faster selection.";
+
+Calendar._TT["PREV_YEAR"] = "Prev. year (hold for menu)";
+Calendar._TT["PREV_MONTH"] = "Prev. month (hold for menu)";
+Calendar._TT["GO_TODAY"] = "Go Today";
+Calendar._TT["NEXT_MONTH"] = "Next month (hold for menu)";
+Calendar._TT["NEXT_YEAR"] = "Next year (hold for menu)";
+Calendar._TT["SEL_DATE"] = "Select date";
+Calendar._TT["DRAG_TO_MOVE"] = "Drag to move";
+Calendar._TT["PART_TODAY"] = " (today)";
+
+// the following is to inform that "%s" is to be the first day of week
+// %s will be replaced with the day name.
+Calendar._TT["DAY_FIRST"] = "Display %s first";
+
+// This may be locale-dependent. It specifies the week-end days, as an array
+// of comma-separated numbers. The numbers are from 0 to 6: 0 means Sunday, 1
+// means Monday, etc.
+Calendar._TT["WEEKEND"] = "0,6";
+
+Calendar._TT["CLOSE"] = "Close";
+Calendar._TT["TODAY"] = "Today";
+Calendar._TT["TIME_PART"] = "(Shift-)Click or drag to change value";
+
+// date formats
+Calendar._TT["DEF_DATE_FORMAT"] = "%Y-%m-%d";
+Calendar._TT["TT_DATE_FORMAT"] = "%a, %b %e";
+
+Calendar._TT["WK"] = "wk";
+Calendar._TT["TIME"] = "Time:";
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup.js
new file mode 100755
index 0000000..f2b4854
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup.js
@@ -0,0 +1,200 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ---------------------------------------------------------------------------
+ *
+ * The DHTML Calendar
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ *
+ * This file defines helper functions for setting up the calendar. They are
+ * intended to help non-programmers get a working calendar on their site
+ * quickly. This script should not be seen as part of the calendar. It just
+ * shows you what one can do with the calendar, while in the same time
+ * providing a quick and simple method for setting it up. If you need
+ * exhaustive customization of the calendar creation process feel free to
+ * modify this code to suit your needs (this is recommended and much better
+ * than modifying calendar.js itself).
+ */
+
+// $Id: calendar-setup.js,v 1.25 2005/03/07 09:51:33 mishoo Exp $
+
+/**
+ * This function "patches" an input field (or other element) to use a calendar
+ * widget for date selection.
+ *
+ * The "params" is a single object that can have the following properties:
+ *
+ * prop. name | description
+ * -------------------------------------------------------------------------------------------------
+ * inputField | the ID of an input field to store the date
+ * displayArea | the ID of a DIV or other element to show the date
+ * button | ID of a button or other element that will trigger the calendar
+ * eventName | event that will trigger the calendar, without the "on" prefix (default: "click")
+ * ifFormat | date format that will be stored in the input field
+ * daFormat | the date format that will be used to display the date in displayArea
+ * singleClick | (true/false) wether the calendar is in single click mode or not (default: true)
+ * firstDay | numeric: 0 to 6. "0" means display Sunday first, "1" means display Monday first, etc.
+ * align | alignment (default: "Br"); if you don't know what's this see the calendar documentation
+ * range | array with 2 elements. Default: [1900, 2999] -- the range of years available
+ * weekNumbers | (true/false) if it's true (default) the calendar will display week numbers
+ * flat | null or element ID; if not null the calendar will be a flat calendar having the parent with the given ID
+ * flatCallback | function that receives a JS Date object and returns an URL to point the browser to (for flat calendar)
+ * disableFunc | function that receives a JS Date object and should return true if that date has to be disabled in the calendar
+ * onSelect | function that gets called when a date is selected. You don't _have_ to supply this (the default is generally okay)
+ * onClose | function that gets called when the calendar is closed. [default]
+ * onUpdate | function that gets called after the date is updated in the input field. Receives a reference to the calendar.
+ * date | the date that the calendar will be initially displayed to
+ * showsTime | default: false; if true the calendar will include a time selector
+ * timeFormat | the time format; can be "12" or "24", default is "12"
+ * electric | if true (default) then given fields/date areas are updated for each move; otherwise they're updated only on close
+ * step | configures the step of the years in drop-down boxes; default: 2
+ * position | configures the calendar absolute position; default: null
+ * cache | if "true" (but default: "false") it will reuse the same calendar object, where possible
+ * showOthers | if "true" (but default: "false") it will show days from other months too
+ *
+ * None of them is required, they all have default values. However, if you
+ * pass none of "inputField", "displayArea" or "button" you'll get a warning
+ * saying "nothing to setup".
+ */
+Calendar.setup = function (params) {
+ function param_default(pname, def) { if (typeof params[pname] == "undefined") { params[pname] = def; } };
+
+ param_default("inputField", null);
+ param_default("displayArea", null);
+ param_default("button", null);
+ param_default("eventName", "click");
+ param_default("ifFormat", "%Y/%m/%d");
+ param_default("daFormat", "%Y/%m/%d");
+ param_default("singleClick", true);
+ param_default("disableFunc", null);
+ param_default("dateStatusFunc", params["disableFunc"]); // takes precedence if both are defined
+ param_default("dateText", null);
+ param_default("firstDay", null);
+ param_default("align", "Br");
+ param_default("range", [1900, 2999]);
+ param_default("weekNumbers", true);
+ param_default("flat", null);
+ param_default("flatCallback", null);
+ param_default("onSelect", null);
+ param_default("onClose", null);
+ param_default("onUpdate", null);
+ param_default("date", null);
+ param_default("showsTime", false);
+ param_default("timeFormat", "24");
+ param_default("electric", true);
+ param_default("step", 2);
+ param_default("position", null);
+ param_default("cache", false);
+ param_default("showOthers", false);
+ param_default("multiple", null);
+
+ var tmp = ["inputField", "displayArea", "button"];
+ for (var i in tmp) {
+ if (typeof params[tmp[i]] == "string") {
+ params[tmp[i]] = document.getElementById(params[tmp[i]]);
+ }
+ }
+ if (!(params.flat || params.multiple || params.inputField || params.displayArea || params.button)) {
+ alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");
+ return false;
+ }
+
+ function onSelect(cal) {
+ var p = cal.params;
+ var update = (cal.dateClicked || p.electric);
+ if (update && p.inputField) {
+ p.inputField.value = cal.date.print(p.ifFormat);
+ if (typeof p.inputField.onchange == "function")
+ p.inputField.onchange();
+ }
+ if (update && p.displayArea)
+ p.displayArea.innerHTML = cal.date.print(p.daFormat);
+ if (update && typeof p.onUpdate == "function")
+ p.onUpdate(cal);
+ if (update && p.flat) {
+ if (typeof p.flatCallback == "function")
+ p.flatCallback(cal);
+ }
+ if (update && p.singleClick && cal.dateClicked)
+ cal.callCloseHandler();
+ };
+
+ if (params.flat != null) {
+ if (typeof params.flat == "string")
+ params.flat = document.getElementById(params.flat);
+ if (!params.flat) {
+ alert("Calendar.setup:\n Flat specified but can't find parent.");
+ return false;
+ }
+ var cal = new Calendar(params.firstDay, params.date, params.onSelect || onSelect);
+ cal.showsOtherMonths = params.showOthers;
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.params = params;
+ cal.weekNumbers = params.weekNumbers;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.getDateText = params.dateText;
+ if (params.ifFormat) {
+ cal.setDateFormat(params.ifFormat);
+ }
+ if (params.inputField && typeof params.inputField.value == "string") {
+ cal.parseDate(params.inputField.value);
+ }
+ cal.create(params.flat);
+ cal.show();
+ return false;
+ }
+
+ var triggerEl = params.button || params.displayArea || params.inputField;
+ triggerEl["on" + params.eventName] = function() {
+ var dateEl = params.inputField || params.displayArea;
+ var dateFmt = params.inputField ? params.ifFormat : params.daFormat;
+ var mustCreate = false;
+ var cal = window.calendar;
+ if (dateEl)
+ params.date = Date.parseDate(dateEl.value || dateEl.innerHTML, dateFmt);
+ if (!(cal && params.cache)) {
+ window.calendar = cal = new Calendar(params.firstDay,
+ params.date,
+ params.onSelect || onSelect,
+ params.onClose || function(cal) { cal.hide(); });
+ cal.showsTime = params.showsTime;
+ cal.time24 = (params.timeFormat == "24");
+ cal.weekNumbers = params.weekNumbers;
+ mustCreate = true;
+ } else {
+ if (params.date)
+ cal.setDate(params.date);
+ cal.hide();
+ }
+ if (params.multiple) {
+ cal.multiple = {};
+ for (var i = params.multiple.length; --i >= 0;) {
+ var d = params.multiple[i];
+ var ds = d.print("%Y%m%d");
+ cal.multiple[ds] = d;
+ }
+ }
+ cal.showsOtherMonths = params.showOthers;
+ cal.yearStep = params.step;
+ cal.setRange(params.range[0], params.range[1]);
+ cal.params = params;
+ cal.setDateStatusHandler(params.dateStatusFunc);
+ cal.getDateText = params.dateText;
+ cal.setDateFormat(dateFmt);
+ if (mustCreate)
+ cal.create();
+ cal.refresh();
+ if (!params.position)
+ cal.showAtElement(params.button || params.displayArea || params.inputField, params.align);
+ else
+ cal.showAt(params.position[0], params.position[1]);
+ return false;
+ };
+
+ return cal;
+};
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js
new file mode 100755
index 0000000..91c927f
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar-setup_stripped.js
@@ -0,0 +1,21 @@
+/* Copyright Mihai Bazon, 2002, 2003 | http://dynarch.com/mishoo/
+ * ---------------------------------------------------------------------------
+ *
+ * The DHTML Calendar
+ *
+ * Details and latest version at:
+ * http://dynarch.com/mishoo/calendar.epl
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ *
+ * This file defines helper functions for setting up the calendar. They are
+ * intended to help non-programmers get a working calendar on their site
+ * quickly. This script should not be seen as part of the calendar. It just
+ * shows you what one can do with the calendar, while in the same time
+ * providing a quick and simple method for setting it up. If you need
+ * exhaustive customization of the calendar creation process feel free to
+ * modify this code to suit your needs (this is recommended and much better
+ * than modifying calendar.js itself).
+ */
+ Calendar.setup=function(params){function param_default(pname,def){if(typeof params[pname]=="undefined"){params[pname]=def;}};param_default("inputField",null);param_default("displayArea",null);param_default("button",null);param_default("eventName","click");param_default("ifFormat","%Y/%m/%d");param_default("daFormat","%Y/%m/%d");param_default("singleClick",true);param_default("disableFunc",null);param_default("dateStatusFunc",params["disableFunc"]);param_default("dateText",null);param_default("firstDay",null);param_default("align","Br");param_default("range",[1900,2999]);param_default("weekNumbers",true);param_default("flat",null);param_default("flatCallback",null);param_default("onSelect",null);param_default("onClose",null);param_default("onUpdate",null);param_default("date",null);param_default("showsTime",false);param_default("timeFormat","24");param_default("electric",true);param_default("step",2);param_default("position",null);param_default("cache",false);param_default("showOthers",false);param_default("multiple",null);var tmp=["inputField","displayArea","button"];for(var i in tmp){if(typeof params[tmp[i]]=="string"){params[tmp[i]]=document.getElementById(params[tmp[i]]);}}if(!(params.flat||params.multiple||params.inputField||params.displayArea||params.button)){alert("Calendar.setup:\n Nothing to setup (no fields found). Please check your code");return false;}function onSelect(cal){var p=cal.params;var update=(cal.dateClicked||p.electric);if(update&&p.inputField){p.inputField.value=cal.date.print(p.ifFormat);if(typeof p.inputField.onchange=="function")p.inputField.onchange();}if(update&&p.displayArea)p.displayArea.innerHTML=cal.date.print(p.daFormat);if(update&&typeof p.onUpdate=="function")p.onUpdate(cal);if(update&&p.flat){if(typeof p.flatCallback=="function")p.flatCallback(cal);}if(update&&p.singleClick&&cal.dateClicked)cal.callCloseHandler();};if(params.flat!=null){if(typeof params.flat=="string")params.flat=document.getElementById(params.flat);if(!params.flat){alert("Calendar.setup:\n Flat specified but can't find parent.");return false;}var cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect);cal.showsOtherMonths=params.showOthers;cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.params=params;cal.weekNumbers=params.weekNumbers;cal.setRange(params.range[0],params.range[1]);cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;if(params.ifFormat){cal.setDateFormat(params.ifFormat);}if(params.inputField&&typeof params.inputField.value=="string"){cal.parseDate(params.inputField.value);}cal.create(params.flat);cal.show();return false;}var triggerEl=params.button||params.displayArea||params.inputField;triggerEl["on"+params.eventName]=function(){var dateEl=params.inputField||params.displayArea;var dateFmt=params.inputField?params.ifFormat:params.daFormat;var mustCreate=false;var cal=window.calendar;if(dateEl)params.date=Date.parseDate(dateEl.value||dateEl.innerHTML,dateFmt);if(!(cal&¶ms.cache)){window.calendar=cal=new Calendar(params.firstDay,params.date,params.onSelect||onSelect,params.onClose||function(cal){cal.hide();});cal.showsTime=params.showsTime;cal.time24=(params.timeFormat=="24");cal.weekNumbers=params.weekNumbers;mustCreate=true;}else{if(params.date)cal.setDate(params.date);cal.hide();}if(params.multiple){cal.multiple={};for(var i=params.multiple.length;--i>=0;){var d=params.multiple[i];var ds=d.print("%Y%m%d");cal.multiple[ds]=d;}}cal.showsOtherMonths=params.showOthers;cal.yearStep=params.step;cal.setRange(params.range[0],params.range[1]);cal.params=params;cal.setDateStatusHandler(params.dateStatusFunc);cal.getDateText=params.dateText;cal.setDateFormat(dateFmt);if(mustCreate)cal.create();cal.refresh();if(!params.position)cal.showAtElement(params.button||params.displayArea||params.inputField,params.align);else cal.showAt(params.position[0],params.position[1]);return false;};return cal;};
\ No newline at end of file
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar.js
new file mode 100755
index 0000000..9088e0e
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar.js
@@ -0,0 +1,1806 @@
+/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com. Visit us at www.dynarch.com.
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+
+// $Id: calendar.js,v 1.51 2005/03/07 16:44:31 mishoo Exp $
+
+/** The Calendar object constructor. */
+Calendar = function (firstDayOfWeek, dateStr, onSelected, onClose) {
+ // member variables
+ this.activeDiv = null;
+ this.currentDateEl = null;
+ this.getDateStatus = null;
+ this.getDateToolTip = null;
+ this.getDateText = null;
+ this.timeout = null;
+ this.onSelected = onSelected || null;
+ this.onClose = onClose || null;
+ this.dragging = false;
+ this.hidden = false;
+ this.minYear = 1970;
+ this.maxYear = 2050;
+ this.dateFormat = Calendar._TT["DEF_DATE_FORMAT"];
+ this.ttDateFormat = Calendar._TT["TT_DATE_FORMAT"];
+ this.isPopup = true;
+ this.weekNumbers = true;
+ this.firstDayOfWeek = typeof firstDayOfWeek == "number" ? firstDayOfWeek : Calendar._FD; // 0 for Sunday, 1 for Monday, etc.
+ this.showsOtherMonths = false;
+ this.dateStr = dateStr;
+ this.ar_days = null;
+ this.showsTime = false;
+ this.time24 = true;
+ this.yearStep = 2;
+ this.hiliteToday = true;
+ this.multiple = null;
+ // HTML elements
+ this.table = null;
+ this.element = null;
+ this.tbody = null;
+ this.firstdayname = null;
+ // Combo boxes
+ this.monthsCombo = null;
+ this.yearsCombo = null;
+ this.hilitedMonth = null;
+ this.activeMonth = null;
+ this.hilitedYear = null;
+ this.activeYear = null;
+ // Information
+ this.dateClicked = false;
+
+ // one-time initializations
+ if (typeof Calendar._SDN == "undefined") {
+ // table of short day names
+ if (typeof Calendar._SDN_len == "undefined")
+ Calendar._SDN_len = 3;
+ var ar = new Array();
+ for (var i = 8; i > 0;) {
+ ar[--i] = Calendar._DN[i].substr(0, Calendar._SDN_len);
+ }
+ Calendar._SDN = ar;
+ // table of short month names
+ if (typeof Calendar._SMN_len == "undefined")
+ Calendar._SMN_len = 3;
+ ar = new Array();
+ for (var i = 12; i > 0;) {
+ ar[--i] = Calendar._MN[i].substr(0, Calendar._SMN_len);
+ }
+ Calendar._SMN = ar;
+ }
+};
+
+// ** constants
+
+/// "static", needed for event handlers.
+Calendar._C = null;
+
+/// detect a special case of "web browser"
+Calendar.is_ie = ( /msie/i.test(navigator.userAgent) &&
+ !/opera/i.test(navigator.userAgent) );
+
+Calendar.is_ie5 = ( Calendar.is_ie && /msie 5\.0/i.test(navigator.userAgent) );
+
+/// detect Opera browser
+Calendar.is_opera = /opera/i.test(navigator.userAgent);
+
+/// detect KHTML-based browsers
+Calendar.is_khtml = /Konqueror|Safari|KHTML/i.test(navigator.userAgent);
+
+// BEGIN: UTILITY FUNCTIONS; beware that these might be moved into a separate
+// library, at some point.
+
+Calendar.getAbsolutePos = function(el) {
+ var SL = 0, ST = 0;
+ var is_div = /^div$/i.test(el.tagName);
+ if (is_div && el.scrollLeft)
+ SL = el.scrollLeft;
+ if (is_div && el.scrollTop)
+ ST = el.scrollTop;
+ var r = { x: el.offsetLeft - SL, y: el.offsetTop - ST };
+ if (el.offsetParent) {
+ var tmp = this.getAbsolutePos(el.offsetParent);
+ r.x += tmp.x;
+ r.y += tmp.y;
+ }
+ return r;
+};
+
+Calendar.isRelated = function (el, evt) {
+ var related = evt.relatedTarget;
+ if (!related) {
+ var type = evt.type;
+ if (type == "mouseover") {
+ related = evt.fromElement;
+ } else if (type == "mouseout") {
+ related = evt.toElement;
+ }
+ }
+ while (related) {
+ if (related == el) {
+ return true;
+ }
+ related = related.parentNode;
+ }
+ return false;
+};
+
+Calendar.removeClass = function(el, className) {
+ if (!(el && el.className)) {
+ return;
+ }
+ var cls = el.className.split(" ");
+ var ar = new Array();
+ for (var i = cls.length; i > 0;) {
+ if (cls[--i] != className) {
+ ar[ar.length] = cls[i];
+ }
+ }
+ el.className = ar.join(" ");
+};
+
+Calendar.addClass = function(el, className) {
+ Calendar.removeClass(el, className);
+ el.className += " " + className;
+};
+
+// FIXME: the following 2 functions totally suck, are useless and should be replaced immediately.
+Calendar.getElement = function(ev) {
+ var f = Calendar.is_ie ? window.event.srcElement : ev.currentTarget;
+ while (f.nodeType != 1 || /^div$/i.test(f.tagName))
+ f = f.parentNode;
+ return f;
+};
+
+Calendar.getTargetElement = function(ev) {
+ var f = Calendar.is_ie ? window.event.srcElement : ev.target;
+ while (f.nodeType != 1)
+ f = f.parentNode;
+ return f;
+};
+
+Calendar.stopEvent = function(ev) {
+ ev || (ev = window.event);
+ if (Calendar.is_ie) {
+ ev.cancelBubble = true;
+ ev.returnValue = false;
+ } else {
+ ev.preventDefault();
+ ev.stopPropagation();
+ }
+ return false;
+};
+
+Calendar.addEvent = function(el, evname, func) {
+ if (el.attachEvent) { // IE
+ el.attachEvent("on" + evname, func);
+ } else if (el.addEventListener) { // Gecko / W3C
+ el.addEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = func;
+ }
+};
+
+Calendar.removeEvent = function(el, evname, func) {
+ if (el.detachEvent) { // IE
+ el.detachEvent("on" + evname, func);
+ } else if (el.removeEventListener) { // Gecko / W3C
+ el.removeEventListener(evname, func, true);
+ } else {
+ el["on" + evname] = null;
+ }
+};
+
+Calendar.createElement = function(type, parent) {
+ var el = null;
+ if (document.createElementNS) {
+ // use the XHTML namespace; IE won't normally get here unless
+ // _they_ "fix" the DOM2 implementation.
+ el = document.createElementNS("http://www.w3.org/1999/xhtml", type);
+ } else {
+ el = document.createElement(type);
+ }
+ if (typeof parent != "undefined") {
+ parent.appendChild(el);
+ }
+ return el;
+};
+
+// END: UTILITY FUNCTIONS
+
+// BEGIN: CALENDAR STATIC FUNCTIONS
+
+/** Internal -- adds a set of events to make some element behave like a button. */
+Calendar._add_evs = function(el) {
+ with (Calendar) {
+ addEvent(el, "mouseover", dayMouseOver);
+ addEvent(el, "mousedown", dayMouseDown);
+ addEvent(el, "mouseout", dayMouseOut);
+ if (is_ie) {
+ addEvent(el, "dblclick", dayMouseDblClick);
+ el.setAttribute("unselectable", true);
+ }
+ }
+};
+
+Calendar.findMonth = function(el) {
+ if (typeof el.month != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.month != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.findYear = function(el) {
+ if (typeof el.year != "undefined") {
+ return el;
+ } else if (typeof el.parentNode.year != "undefined") {
+ return el.parentNode;
+ }
+ return null;
+};
+
+Calendar.showMonthsCombo = function () {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var mc = cal.monthsCombo;
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ if (cal.activeMonth) {
+ Calendar.removeClass(cal.activeMonth, "active");
+ }
+ var mon = cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];
+ Calendar.addClass(mon, "active");
+ cal.activeMonth = mon;
+ var s = mc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else {
+ var mcw = mc.offsetWidth;
+ if (typeof mcw == "undefined")
+ // Konqueror brain-dead techniques
+ mcw = 50;
+ s.left = (cd.offsetLeft + cd.offsetWidth - mcw) + "px";
+ }
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+};
+
+Calendar.showYearsCombo = function (fwd) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ var cal = cal;
+ var cd = cal.activeDiv;
+ var yc = cal.yearsCombo;
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ if (cal.activeYear) {
+ Calendar.removeClass(cal.activeYear, "active");
+ }
+ cal.activeYear = null;
+ var Y = cal.date.getFullYear() + (fwd ? 1 : -1);
+ var yr = yc.firstChild;
+ var show = false;
+ for (var i = 12; i > 0; --i) {
+ if (Y >= cal.minYear && Y <= cal.maxYear) {
+ yr.innerHTML = Y;
+ yr.year = Y;
+ yr.style.display = "block";
+ show = true;
+ } else {
+ yr.style.display = "none";
+ }
+ yr = yr.nextSibling;
+ Y += fwd ? cal.yearStep : -cal.yearStep;
+ }
+ if (show) {
+ var s = yc.style;
+ s.display = "block";
+ if (cd.navtype < 0)
+ s.left = cd.offsetLeft + "px";
+ else {
+ var ycw = yc.offsetWidth;
+ if (typeof ycw == "undefined")
+ // Konqueror brain-dead techniques
+ ycw = 50;
+ s.left = (cd.offsetLeft + cd.offsetWidth - ycw) + "px";
+ }
+ s.top = (cd.offsetTop + cd.offsetHeight) + "px";
+ }
+};
+
+// event handlers
+
+Calendar.tableMouseUp = function(ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ if (cal.timeout) {
+ clearTimeout(cal.timeout);
+ }
+ var el = cal.activeDiv;
+ if (!el) {
+ return false;
+ }
+ var target = Calendar.getTargetElement(ev);
+ ev || (ev = window.event);
+ Calendar.removeClass(el, "active");
+ if (target == el || target.parentNode == el) {
+ Calendar.cellClick(el, ev);
+ }
+ var mon = Calendar.findMonth(target);
+ var date = null;
+ if (mon) {
+ date = new Date(cal.date);
+ if (mon.month != date.getMonth()) {
+ date.setMonth(mon.month);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ } else {
+ var year = Calendar.findYear(target);
+ if (year) {
+ date = new Date(cal.date);
+ if (year.year != date.getFullYear()) {
+ date.setFullYear(year.year);
+ cal.setDate(date);
+ cal.dateClicked = false;
+ cal.callHandler();
+ }
+ }
+ }
+ with (Calendar) {
+ removeEvent(document, "mouseup", tableMouseUp);
+ removeEvent(document, "mouseover", tableMouseOver);
+ removeEvent(document, "mousemove", tableMouseOver);
+ cal._hideCombos();
+ _C = null;
+ return stopEvent(ev);
+ }
+};
+
+Calendar.tableMouseOver = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return;
+ }
+ var el = cal.activeDiv;
+ var target = Calendar.getTargetElement(ev);
+ if (target == el || target.parentNode == el) {
+ Calendar.addClass(el, "hilite active");
+ Calendar.addClass(el.parentNode, "rowhilite");
+ } else {
+ if (typeof el.navtype == "undefined" || (el.navtype != 50 && (el.navtype == 0 || Math.abs(el.navtype) > 2)))
+ Calendar.removeClass(el, "active");
+ Calendar.removeClass(el, "hilite");
+ Calendar.removeClass(el.parentNode, "rowhilite");
+ }
+ ev || (ev = window.event);
+ if (el.navtype == 50 && target != el) {
+ var pos = Calendar.getAbsolutePos(el);
+ var w = el.offsetWidth;
+ var x = ev.clientX;
+ var dx;
+ var decrease = true;
+ if (x > pos.x + w) {
+ dx = x - pos.x - w;
+ decrease = false;
+ } else
+ dx = pos.x - x;
+
+ if (dx < 0) dx = 0;
+ var range = el._range;
+ var current = el._current;
+ var count = Math.floor(dx / 10) % range.length;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ while (count-- > 0)
+ if (decrease) {
+ if (--i < 0)
+ i = range.length - 1;
+ } else if ( ++i >= range.length )
+ i = 0;
+ var newval = range[i];
+ el.innerHTML = newval;
+
+ cal.onUpdateTime();
+ }
+ var mon = Calendar.findMonth(target);
+ if (mon) {
+ if (mon.month != cal.date.getMonth()) {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ Calendar.addClass(mon, "hilite");
+ cal.hilitedMonth = mon;
+ } else if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ } else {
+ if (cal.hilitedMonth) {
+ Calendar.removeClass(cal.hilitedMonth, "hilite");
+ }
+ var year = Calendar.findYear(target);
+ if (year) {
+ if (year.year != cal.date.getFullYear()) {
+ if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ Calendar.addClass(year, "hilite");
+ cal.hilitedYear = year;
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ } else if (cal.hilitedYear) {
+ Calendar.removeClass(cal.hilitedYear, "hilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.tableMouseDown = function (ev) {
+ if (Calendar.getTargetElement(ev) == Calendar.getElement(ev)) {
+ return Calendar.stopEvent(ev);
+ }
+};
+
+Calendar.calDragIt = function (ev) {
+ var cal = Calendar._C;
+ if (!(cal && cal.dragging)) {
+ return false;
+ }
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posX = ev.pageX;
+ posY = ev.pageY;
+ }
+ cal.hideShowCovered();
+ var st = cal.element.style;
+ st.left = (posX - cal.xOffs) + "px";
+ st.top = (posY - cal.yOffs) + "px";
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.calDragEnd = function (ev) {
+ var cal = Calendar._C;
+ if (!cal) {
+ return false;
+ }
+ cal.dragging = false;
+ with (Calendar) {
+ removeEvent(document, "mousemove", calDragIt);
+ removeEvent(document, "mouseup", calDragEnd);
+ tableMouseUp(ev);
+ }
+ cal.hideShowCovered();
+};
+
+Calendar.dayMouseDown = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (el.disabled) {
+ return false;
+ }
+ var cal = el.calendar;
+ cal.activeDiv = el;
+ Calendar._C = cal;
+ if (el.navtype != 300) with (Calendar) {
+ if (el.navtype == 50) {
+ el._current = el.innerHTML;
+ addEvent(document, "mousemove", tableMouseOver);
+ } else
+ addEvent(document, Calendar.is_ie5 ? "mousemove" : "mouseover", tableMouseOver);
+ addClass(el, "hilite active");
+ addEvent(document, "mouseup", tableMouseUp);
+ } else if (cal.isPopup) {
+ cal._dragStart(ev);
+ }
+ if (el.navtype == -1 || el.navtype == 1) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout("Calendar.showMonthsCombo()", 250);
+ } else if (el.navtype == -2 || el.navtype == 2) {
+ if (cal.timeout) clearTimeout(cal.timeout);
+ cal.timeout = setTimeout((el.navtype > 0) ? "Calendar.showYearsCombo(true)" : "Calendar.showYearsCombo(false)", 250);
+ } else {
+ cal.timeout = null;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseDblClick = function(ev) {
+ Calendar.cellClick(Calendar.getElement(ev), ev || window.event);
+ if (Calendar.is_ie) {
+ document.selection.empty();
+ }
+};
+
+Calendar.dayMouseOver = function(ev) {
+ var el = Calendar.getElement(ev);
+ if (Calendar.isRelated(el, ev) || Calendar._C || el.disabled) {
+ return false;
+ }
+ if (el.ttip) {
+ if (el.ttip.substr(0, 1) == "_") {
+ el.ttip = el.caldate.print(el.calendar.ttDateFormat) + el.ttip.substr(1);
+ }
+ el.calendar.tooltips.innerHTML = el.ttip;
+ }
+ if (el.navtype != 300) {
+ Calendar.addClass(el, "hilite");
+ if (el.caldate) {
+ Calendar.addClass(el.parentNode, "rowhilite");
+ }
+ }
+ return Calendar.stopEvent(ev);
+};
+
+Calendar.dayMouseOut = function(ev) {
+ with (Calendar) {
+ var el = getElement(ev);
+ if (isRelated(el, ev) || _C || el.disabled)
+ return false;
+ removeClass(el, "hilite");
+ if (el.caldate)
+ removeClass(el.parentNode, "rowhilite");
+ if (el.calendar)
+ el.calendar.tooltips.innerHTML = _TT["SEL_DATE"];
+ return stopEvent(ev);
+ }
+};
+
+/**
+ * A generic "click" handler :) handles all types of buttons defined in this
+ * calendar.
+ */
+Calendar.cellClick = function(el, ev) {
+ var cal = el.calendar;
+ var closing = false;
+ var newdate = false;
+ var date = null;
+ if (typeof el.navtype == "undefined") {
+ if (cal.currentDateEl) {
+ Calendar.removeClass(cal.currentDateEl, "selected");
+ Calendar.addClass(el, "selected");
+ closing = (cal.currentDateEl == el);
+ if (!closing) {
+ cal.currentDateEl = el;
+ }
+ }
+ cal.date.setDateOnly(el.caldate);
+ date = cal.date;
+ var other_month = !(cal.dateClicked = !el.otherMonth);
+ if (!other_month && !cal.currentDateEl)
+ cal._toggleMultipleDate(new Date(date));
+ else
+ newdate = !el.disabled;
+ // a date was clicked
+ if (other_month)
+ cal._init(cal.firstDayOfWeek, date);
+ } else {
+ if (el.navtype == 200) {
+ Calendar.removeClass(el, "hilite");
+ cal.callCloseHandler();
+ return;
+ }
+ date = new Date(cal.date);
+ if (el.navtype == 0)
+ date.setDateOnly(new Date()); // TODAY
+ // unless "today" was clicked, we assume no date was clicked so
+ // the selected handler will know not to close the calenar when
+ // in single-click mode.
+ // cal.dateClicked = (el.navtype == 0);
+ cal.dateClicked = false;
+ var year = date.getFullYear();
+ var mon = date.getMonth();
+ function setMonth(m) {
+ var day = date.getDate();
+ var max = date.getMonthDays(m);
+ if (day > max) {
+ date.setDate(max);
+ }
+ date.setMonth(m);
+ };
+ switch (el.navtype) {
+ case 400:
+ Calendar.removeClass(el, "hilite");
+ var text = Calendar._TT["ABOUT"];
+ if (typeof text != "undefined") {
+ text += cal.showsTime ? Calendar._TT["ABOUT_TIME"] : "";
+ } else {
+ // FIXME: this should be removed as soon as lang files get updated!
+ text = "Help and about box text is not translated into this language.\n" +
+ "If you know this language and you feel generous please update\n" +
+ "the corresponding file in \"lang\" subdir to match calendar-en.js\n" +
+ "and send it back to to get it into the distribution ;-)\n\n" +
+ "Thank you!\n" +
+ "http://dynarch.com/mishoo/calendar.epl\n";
+ }
+ alert(text);
+ return;
+ case -2:
+ if (year > cal.minYear) {
+ date.setFullYear(year - 1);
+ }
+ break;
+ case -1:
+ if (mon > 0) {
+ setMonth(mon - 1);
+ } else if (year-- > cal.minYear) {
+ date.setFullYear(year);
+ setMonth(11);
+ }
+ break;
+ case 1:
+ if (mon < 11) {
+ setMonth(mon + 1);
+ } else if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ setMonth(0);
+ }
+ break;
+ case 2:
+ if (year < cal.maxYear) {
+ date.setFullYear(year + 1);
+ }
+ break;
+ case 100:
+ cal.setFirstDayOfWeek(el.fdow);
+ return;
+ case 50:
+ var range = el._range;
+ var current = el.innerHTML;
+ for (var i = range.length; --i >= 0;)
+ if (range[i] == current)
+ break;
+ if (ev && ev.shiftKey) {
+ if (--i < 0)
+ i = range.length - 1;
+ } else if ( ++i >= range.length )
+ i = 0;
+ var newval = range[i];
+ el.innerHTML = newval;
+ cal.onUpdateTime();
+ return;
+ case 0:
+ // TODAY will bring us here
+ if ((typeof cal.getDateStatus == "function") &&
+ cal.getDateStatus(date, date.getFullYear(), date.getMonth(), date.getDate())) {
+ return false;
+ }
+ break;
+ }
+ if (!date.equalsTo(cal.date)) {
+ cal.setDate(date);
+ newdate = true;
+ } else if (el.navtype == 0)
+ newdate = closing = true;
+ }
+ if (newdate) {
+ ev && cal.callHandler();
+ }
+ if (closing) {
+ Calendar.removeClass(el, "hilite");
+ ev && cal.callCloseHandler();
+ }
+};
+
+// END: CALENDAR STATIC FUNCTIONS
+
+// BEGIN: CALENDAR OBJECT FUNCTIONS
+
+/**
+ * This function creates the calendar inside the given parent. If _par is
+ * null than it creates a popup calendar inside the BODY element. If _par is
+ * an element, be it BODY, then it creates a non-popup calendar (still
+ * hidden). Some properties need to be set before calling this function.
+ */
+Calendar.prototype.create = function (_par) {
+ var parent = null;
+ if (! _par) {
+ // default parent is the document body, in which case we create
+ // a popup calendar.
+ parent = document.getElementsByTagName("body")[0];
+ this.isPopup = true;
+ } else {
+ parent = _par;
+ this.isPopup = false;
+ }
+ this.date = this.dateStr ? new Date(this.dateStr) : new Date();
+
+ var table = Calendar.createElement("table");
+ this.table = table;
+ table.cellSpacing = 0;
+ table.cellPadding = 0;
+ table.calendar = this;
+ Calendar.addEvent(table, "mousedown", Calendar.tableMouseDown);
+
+ var div = Calendar.createElement("div");
+ this.element = div;
+ div.className = "calendar";
+ if (this.isPopup) {
+ div.style.position = "absolute";
+ div.style.display = "none";
+ }
+ div.appendChild(table);
+
+ var thead = Calendar.createElement("thead", table);
+ var cell = null;
+ var row = null;
+
+ var cal = this;
+ var hh = function (text, cs, navtype) {
+ cell = Calendar.createElement("td", row);
+ cell.colSpan = cs;
+ cell.className = "button";
+ if (navtype != 0 && Math.abs(navtype) <= 2)
+ cell.className += " nav";
+ Calendar._add_evs(cell);
+ cell.calendar = cal;
+ cell.navtype = navtype;
+ cell.innerHTML = "
" + text + "
";
+ return cell;
+ };
+
+ row = Calendar.createElement("tr", thead);
+ var title_length = 6;
+ (this.isPopup) && --title_length;
+ (this.weekNumbers) && ++title_length;
+
+ hh("?", 1, 400).ttip = Calendar._TT["INFO"];
+ this.title = hh("", title_length, 300);
+ this.title.className = "title";
+ if (this.isPopup) {
+ this.title.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ this.title.style.cursor = "move";
+ hh("×", 1, 200).ttip = Calendar._TT["CLOSE"];
+ }
+
+ row = Calendar.createElement("tr", thead);
+ row.className = "headrow";
+
+ this._nav_py = hh("«", 1, -2);
+ this._nav_py.ttip = Calendar._TT["PREV_YEAR"];
+
+ this._nav_pm = hh("‹", 1, -1);
+ this._nav_pm.ttip = Calendar._TT["PREV_MONTH"];
+
+ this._nav_now = hh(Calendar._TT["TODAY"], this.weekNumbers ? 4 : 3, 0);
+ this._nav_now.ttip = Calendar._TT["GO_TODAY"];
+
+ this._nav_nm = hh("›", 1, 1);
+ this._nav_nm.ttip = Calendar._TT["NEXT_MONTH"];
+
+ this._nav_ny = hh("»", 1, 2);
+ this._nav_ny.ttip = Calendar._TT["NEXT_YEAR"];
+
+ // day names
+ row = Calendar.createElement("tr", thead);
+ row.className = "daynames";
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ cell.className = "name wn";
+ cell.innerHTML = Calendar._TT["WK"];
+ }
+ for (var i = 7; i > 0; --i) {
+ cell = Calendar.createElement("td", row);
+ if (!i) {
+ cell.navtype = 100;
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+ this.firstdayname = (this.weekNumbers) ? row.firstChild.nextSibling : row.firstChild;
+ this._displayWeekdays();
+
+ var tbody = Calendar.createElement("tbody", table);
+ this.tbody = tbody;
+
+ for (i = 6; i > 0; --i) {
+ row = Calendar.createElement("tr", tbody);
+ if (this.weekNumbers) {
+ cell = Calendar.createElement("td", row);
+ }
+ for (var j = 7; j > 0; --j) {
+ cell = Calendar.createElement("td", row);
+ cell.calendar = this;
+ Calendar._add_evs(cell);
+ }
+ }
+
+ if (this.showsTime) {
+ row = Calendar.createElement("tr", tbody);
+ row.className = "time";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ cell.innerHTML = Calendar._TT["TIME"] || " ";
+
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = this.weekNumbers ? 4 : 3;
+
+ (function(){
+ function makeTimePart(className, init, range_start, range_end) {
+ var part = Calendar.createElement("span", cell);
+ part.className = className;
+ part.innerHTML = init;
+ part.calendar = cal;
+ part.ttip = Calendar._TT["TIME_PART"];
+ part.navtype = 50;
+ part._range = [];
+ if (typeof range_start != "number")
+ part._range = range_start;
+ else {
+ for (var i = range_start; i <= range_end; ++i) {
+ var txt;
+ if (i < 10 && range_end >= 10) txt = '0' + i;
+ else txt = '' + i;
+ part._range[part._range.length] = txt;
+ }
+ }
+ Calendar._add_evs(part);
+ return part;
+ };
+ var hrs = cal.date.getHours();
+ var mins = cal.date.getMinutes();
+ var t12 = !cal.time24;
+ var pm = (hrs > 12);
+ if (t12 && pm) hrs -= 12;
+ var H = makeTimePart("hour", hrs, t12 ? 1 : 0, t12 ? 12 : 23);
+ var span = Calendar.createElement("span", cell);
+ span.innerHTML = ":";
+ span.className = "colon";
+ var M = makeTimePart("minute", mins, 0, 59);
+ var AP = null;
+ cell = Calendar.createElement("td", row);
+ cell.className = "time";
+ cell.colSpan = 2;
+ if (t12)
+ AP = makeTimePart("ampm", pm ? "pm" : "am", ["am", "pm"]);
+ else
+ cell.innerHTML = " ";
+
+ cal.onSetTime = function() {
+ var pm, hrs = this.date.getHours(),
+ mins = this.date.getMinutes();
+ if (t12) {
+ pm = (hrs >= 12);
+ if (pm) hrs -= 12;
+ if (hrs == 0) hrs = 12;
+ AP.innerHTML = pm ? "pm" : "am";
+ }
+ H.innerHTML = (hrs < 10) ? ("0" + hrs) : hrs;
+ M.innerHTML = (mins < 10) ? ("0" + mins) : mins;
+ };
+
+ cal.onUpdateTime = function() {
+ var date = this.date;
+ var h = parseInt(H.innerHTML, 10);
+ if (t12) {
+ if (/pm/i.test(AP.innerHTML) && h < 12)
+ h += 12;
+ else if (/am/i.test(AP.innerHTML) && h == 12)
+ h = 0;
+ }
+ var d = date.getDate();
+ var m = date.getMonth();
+ var y = date.getFullYear();
+ date.setHours(h);
+ date.setMinutes(parseInt(M.innerHTML, 10));
+ date.setFullYear(y);
+ date.setMonth(m);
+ date.setDate(d);
+ this.dateClicked = false;
+ this.callHandler();
+ };
+ })();
+ } else {
+ this.onSetTime = this.onUpdateTime = function() {};
+ }
+
+ var tfoot = Calendar.createElement("tfoot", table);
+
+ row = Calendar.createElement("tr", tfoot);
+ row.className = "footrow";
+
+ cell = hh(Calendar._TT["SEL_DATE"], this.weekNumbers ? 8 : 7, 300);
+ cell.className = "ttip";
+ if (this.isPopup) {
+ cell.ttip = Calendar._TT["DRAG_TO_MOVE"];
+ cell.style.cursor = "move";
+ }
+ this.tooltips = cell;
+
+ div = Calendar.createElement("div", this.element);
+ this.monthsCombo = div;
+ div.className = "combo";
+ for (i = 0; i < Calendar._MN.length; ++i) {
+ var mn = Calendar.createElement("div");
+ mn.className = Calendar.is_ie ? "label-IEfix" : "label";
+ mn.month = i;
+ mn.innerHTML = Calendar._SMN[i];
+ div.appendChild(mn);
+ }
+
+ div = Calendar.createElement("div", this.element);
+ this.yearsCombo = div;
+ div.className = "combo";
+ for (i = 12; i > 0; --i) {
+ var yr = Calendar.createElement("div");
+ yr.className = Calendar.is_ie ? "label-IEfix" : "label";
+ div.appendChild(yr);
+ }
+
+ this._init(this.firstDayOfWeek, this.date);
+ parent.appendChild(this.element);
+};
+
+/** keyboard navigation, only for popup calendars */
+Calendar._keyEvent = function(ev) {
+ var cal = window._dynarch_popupCalendar;
+ if (!cal || cal.multiple)
+ return false;
+ (Calendar.is_ie) && (ev = window.event);
+ var act = (Calendar.is_ie || ev.type == "keypress"),
+ K = ev.keyCode;
+ if (ev.ctrlKey) {
+ switch (K) {
+ case 37: // KEY left
+ act && Calendar.cellClick(cal._nav_pm);
+ break;
+ case 38: // KEY up
+ act && Calendar.cellClick(cal._nav_py);
+ break;
+ case 39: // KEY right
+ act && Calendar.cellClick(cal._nav_nm);
+ break;
+ case 40: // KEY down
+ act && Calendar.cellClick(cal._nav_ny);
+ break;
+ default:
+ return false;
+ }
+ } else switch (K) {
+ case 32: // KEY space (now)
+ Calendar.cellClick(cal._nav_now);
+ break;
+ case 27: // KEY esc
+ act && cal.callCloseHandler();
+ break;
+ case 37: // KEY left
+ case 38: // KEY up
+ case 39: // KEY right
+ case 40: // KEY down
+ if (act) {
+ var prev, x, y, ne, el, step;
+ prev = K == 37 || K == 38;
+ step = (K == 37 || K == 39) ? 1 : 7;
+ function setVars() {
+ el = cal.currentDateEl;
+ var p = el.pos;
+ x = p & 15;
+ y = p >> 4;
+ ne = cal.ar_days[y][x];
+ };setVars();
+ function prevMonth() {
+ var date = new Date(cal.date);
+ date.setDate(date.getDate() - step);
+ cal.setDate(date);
+ };
+ function nextMonth() {
+ var date = new Date(cal.date);
+ date.setDate(date.getDate() + step);
+ cal.setDate(date);
+ };
+ while (1) {
+ switch (K) {
+ case 37: // KEY left
+ if (--x >= 0)
+ ne = cal.ar_days[y][x];
+ else {
+ x = 6;
+ K = 38;
+ continue;
+ }
+ break;
+ case 38: // KEY up
+ if (--y >= 0)
+ ne = cal.ar_days[y][x];
+ else {
+ prevMonth();
+ setVars();
+ }
+ break;
+ case 39: // KEY right
+ if (++x < 7)
+ ne = cal.ar_days[y][x];
+ else {
+ x = 0;
+ K = 40;
+ continue;
+ }
+ break;
+ case 40: // KEY down
+ if (++y < cal.ar_days.length)
+ ne = cal.ar_days[y][x];
+ else {
+ nextMonth();
+ setVars();
+ }
+ break;
+ }
+ break;
+ }
+ if (ne) {
+ if (!ne.disabled)
+ Calendar.cellClick(ne);
+ else if (prev)
+ prevMonth();
+ else
+ nextMonth();
+ }
+ }
+ break;
+ case 13: // KEY enter
+ if (act)
+ Calendar.cellClick(cal.currentDateEl, ev);
+ break;
+ default:
+ return false;
+ }
+ return Calendar.stopEvent(ev);
+};
+
+/**
+ * (RE)Initializes the calendar to the given date and firstDayOfWeek
+ */
+Calendar.prototype._init = function (firstDayOfWeek, date) {
+ var today = new Date(),
+ TY = today.getFullYear(),
+ TM = today.getMonth(),
+ TD = today.getDate();
+ this.table.style.visibility = "hidden";
+ var year = date.getFullYear();
+ if (year < this.minYear) {
+ year = this.minYear;
+ date.setFullYear(year);
+ } else if (year > this.maxYear) {
+ year = this.maxYear;
+ date.setFullYear(year);
+ }
+ this.firstDayOfWeek = firstDayOfWeek;
+ this.date = new Date(date);
+ var month = date.getMonth();
+ var mday = date.getDate();
+ var no_days = date.getMonthDays();
+
+ // calendar voodoo for computing the first day that would actually be
+ // displayed in the calendar, even if it's from the previous month.
+ // WARNING: this is magic. ;-)
+ date.setDate(1);
+ var day1 = (date.getDay() - this.firstDayOfWeek) % 7;
+ if (day1 < 0)
+ day1 += 7;
+ date.setDate(-day1);
+ date.setDate(date.getDate() + 1);
+
+ var row = this.tbody.firstChild;
+ var MN = Calendar._SMN[month];
+ var ar_days = this.ar_days = new Array();
+ var weekend = Calendar._TT["WEEKEND"];
+ var dates = this.multiple ? (this.datesCells = {}) : null;
+ for (var i = 0; i < 6; ++i, row = row.nextSibling) {
+ var cell = row.firstChild;
+ if (this.weekNumbers) {
+ cell.className = "day wn";
+ cell.innerHTML = date.getWeekNumber();
+ cell = cell.nextSibling;
+ }
+ row.className = "daysrow";
+ var hasdays = false, iday, dpos = ar_days[i] = [];
+ for (var j = 0; j < 7; ++j, cell = cell.nextSibling, date.setDate(iday + 1)) {
+ iday = date.getDate();
+ var wday = date.getDay();
+ cell.className = "day";
+ cell.pos = i << 4 | j;
+ dpos[j] = cell;
+ var current_month = (date.getMonth() == month);
+ if (!current_month) {
+ if (this.showsOtherMonths) {
+ cell.className += " othermonth";
+ cell.otherMonth = true;
+ } else {
+ cell.className = "emptycell";
+ cell.innerHTML = " ";
+ cell.disabled = true;
+ continue;
+ }
+ } else {
+ cell.otherMonth = false;
+ hasdays = true;
+ }
+ cell.disabled = false;
+ cell.innerHTML = this.getDateText ? this.getDateText(date, iday) : iday;
+ if (dates)
+ dates[date.print("%Y%m%d")] = cell;
+ if (this.getDateStatus) {
+ var status = this.getDateStatus(date, year, month, iday);
+ if (this.getDateToolTip) {
+ var toolTip = this.getDateToolTip(date, year, month, iday);
+ if (toolTip)
+ cell.title = toolTip;
+ }
+ if (status === true) {
+ cell.className += " disabled";
+ cell.disabled = true;
+ } else {
+ if (/disabled/i.test(status))
+ cell.disabled = true;
+ cell.className += " " + status;
+ }
+ }
+ if (!cell.disabled) {
+ cell.caldate = new Date(date);
+ cell.ttip = "_";
+ if (!this.multiple && current_month
+ && iday == mday && this.hiliteToday) {
+ cell.className += " selected";
+ this.currentDateEl = cell;
+ }
+ if (date.getFullYear() == TY &&
+ date.getMonth() == TM &&
+ iday == TD) {
+ cell.className += " today";
+ cell.ttip += Calendar._TT["PART_TODAY"];
+ }
+ if (weekend.indexOf(wday.toString()) != -1)
+ cell.className += cell.otherMonth ? " oweekend" : " weekend";
+ }
+ }
+ if (!(hasdays || this.showsOtherMonths))
+ row.className = "emptyrow";
+ }
+ this.title.innerHTML = Calendar._MN[month] + ", " + year;
+ this.onSetTime();
+ this.table.style.visibility = "visible";
+ this._initMultipleDates();
+ // PROFILE
+ // this.tooltips.innerHTML = "Generated in " + ((new Date()) - today) + " ms";
+};
+
+Calendar.prototype._initMultipleDates = function() {
+ if (this.multiple) {
+ for (var i in this.multiple) {
+ var cell = this.datesCells[i];
+ var d = this.multiple[i];
+ if (!d)
+ continue;
+ if (cell)
+ cell.className += " selected";
+ }
+ }
+};
+
+Calendar.prototype._toggleMultipleDate = function(date) {
+ if (this.multiple) {
+ var ds = date.print("%Y%m%d");
+ var cell = this.datesCells[ds];
+ if (cell) {
+ var d = this.multiple[ds];
+ if (!d) {
+ Calendar.addClass(cell, "selected");
+ this.multiple[ds] = date;
+ } else {
+ Calendar.removeClass(cell, "selected");
+ delete this.multiple[ds];
+ }
+ }
+ }
+};
+
+Calendar.prototype.setDateToolTipHandler = function (unaryFunction) {
+ this.getDateToolTip = unaryFunction;
+};
+
+/**
+ * Calls _init function above for going to a certain date (but only if the
+ * date is different than the currently selected one).
+ */
+Calendar.prototype.setDate = function (date) {
+ if (!date.equalsTo(this.date)) {
+ this._init(this.firstDayOfWeek, date);
+ }
+};
+
+/**
+ * Refreshes the calendar. Useful if the "disabledHandler" function is
+ * dynamic, meaning that the list of disabled date can change at runtime.
+ * Just * call this function if you think that the list of disabled dates
+ * should * change.
+ */
+Calendar.prototype.refresh = function () {
+ this._init(this.firstDayOfWeek, this.date);
+};
+
+/** Modifies the "firstDayOfWeek" parameter (pass 0 for Synday, 1 for Monday, etc.). */
+Calendar.prototype.setFirstDayOfWeek = function (firstDayOfWeek) {
+ this._init(firstDayOfWeek, this.date);
+ this._displayWeekdays();
+};
+
+/**
+ * Allows customization of what dates are enabled. The "unaryFunction"
+ * parameter must be a function object that receives the date (as a JS Date
+ * object) and returns a boolean value. If the returned value is true then
+ * the passed date will be marked as disabled.
+ */
+Calendar.prototype.setDateStatusHandler = Calendar.prototype.setDisabledHandler = function (unaryFunction) {
+ this.getDateStatus = unaryFunction;
+};
+
+/** Customization of allowed year range for the calendar. */
+Calendar.prototype.setRange = function (a, z) {
+ this.minYear = a;
+ this.maxYear = z;
+};
+
+/** Calls the first user handler (selectedHandler). */
+Calendar.prototype.callHandler = function () {
+ if (this.onSelected) {
+ this.onSelected(this, this.date.print(this.dateFormat));
+ }
+};
+
+/** Calls the second user handler (closeHandler). */
+Calendar.prototype.callCloseHandler = function () {
+ if (this.onClose) {
+ this.onClose(this);
+ }
+ this.hideShowCovered();
+};
+
+/** Removes the calendar object from the DOM tree and destroys it. */
+Calendar.prototype.destroy = function () {
+ var el = this.element.parentNode;
+ el.removeChild(this.element);
+ Calendar._C = null;
+ window._dynarch_popupCalendar = null;
+};
+
+/**
+ * Moves the calendar element to a different section in the DOM tree (changes
+ * its parent).
+ */
+Calendar.prototype.reparent = function (new_parent) {
+ var el = this.element;
+ el.parentNode.removeChild(el);
+ new_parent.appendChild(el);
+};
+
+// This gets called when the user presses a mouse button anywhere in the
+// document, if the calendar is shown. If the click was outside the open
+// calendar this function closes it.
+Calendar._checkCalendar = function(ev) {
+ var calendar = window._dynarch_popupCalendar;
+ if (!calendar) {
+ return false;
+ }
+ var el = Calendar.is_ie ? Calendar.getElement(ev) : Calendar.getTargetElement(ev);
+ for (; el != null && el != calendar.element; el = el.parentNode);
+ if (el == null) {
+ // calls closeHandler which should hide the calendar.
+ window._dynarch_popupCalendar.callCloseHandler();
+ return Calendar.stopEvent(ev);
+ }
+};
+
+/** Shows the calendar. */
+Calendar.prototype.show = function () {
+ var rows = this.table.getElementsByTagName("tr");
+ for (var i = rows.length; i > 0;) {
+ var row = rows[--i];
+ Calendar.removeClass(row, "rowhilite");
+ var cells = row.getElementsByTagName("td");
+ for (var j = cells.length; j > 0;) {
+ var cell = cells[--j];
+ Calendar.removeClass(cell, "hilite");
+ Calendar.removeClass(cell, "active");
+ }
+ }
+ this.element.style.display = "block";
+ this.hidden = false;
+ if (this.isPopup) {
+ window._dynarch_popupCalendar = this;
+ Calendar.addEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.addEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.addEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.hideShowCovered();
+};
+
+/**
+ * Hides the calendar. Also removes any "hilite" from the class of any TD
+ * element.
+ */
+Calendar.prototype.hide = function () {
+ if (this.isPopup) {
+ Calendar.removeEvent(document, "keydown", Calendar._keyEvent);
+ Calendar.removeEvent(document, "keypress", Calendar._keyEvent);
+ Calendar.removeEvent(document, "mousedown", Calendar._checkCalendar);
+ }
+ this.element.style.display = "none";
+ this.hidden = true;
+ this.hideShowCovered();
+};
+
+/**
+ * Shows the calendar at a given absolute position (beware that, depending on
+ * the calendar element style -- position property -- this might be relative
+ * to the parent's containing rectangle).
+ */
+Calendar.prototype.showAt = function (x, y) {
+ var s = this.element.style;
+ s.left = x + "px";
+ s.top = y + "px";
+ this.show();
+};
+
+/** Shows the calendar near a given element. */
+Calendar.prototype.showAtElement = function (el, opts) {
+ var self = this;
+ var p = Calendar.getAbsolutePos(el);
+ if (!opts || typeof opts != "string") {
+ this.showAt(p.x, p.y + el.offsetHeight);
+ return true;
+ }
+ function fixPosition(box) {
+ if (box.x < 0)
+ box.x = 0;
+ if (box.y < 0)
+ box.y = 0;
+ var cp = document.createElement("div");
+ var s = cp.style;
+ s.position = "absolute";
+ s.right = s.bottom = s.width = s.height = "0px";
+ document.body.appendChild(cp);
+ var br = Calendar.getAbsolutePos(cp);
+ document.body.removeChild(cp);
+ if (Calendar.is_ie) {
+ br.y += document.body.scrollTop;
+ br.x += document.body.scrollLeft;
+ } else {
+ br.y += window.scrollY;
+ br.x += window.scrollX;
+ }
+ var tmp = box.x + box.width - br.x;
+ if (tmp > 0) box.x -= tmp;
+ tmp = box.y + box.height - br.y;
+ if (tmp > 0) box.y -= tmp;
+ };
+ this.element.style.display = "block";
+ Calendar.continuation_for_the_fucking_khtml_browser = function() {
+ var w = self.element.offsetWidth;
+ var h = self.element.offsetHeight;
+ self.element.style.display = "none";
+ var valign = opts.substr(0, 1);
+ var halign = "l";
+ if (opts.length > 1) {
+ halign = opts.substr(1, 1);
+ }
+ // vertical alignment
+ switch (valign) {
+ case "T": p.y -= h; break;
+ case "B": p.y += el.offsetHeight; break;
+ case "C": p.y += (el.offsetHeight - h) / 2; break;
+ case "t": p.y += el.offsetHeight - h; break;
+ case "b": break; // already there
+ }
+ // horizontal alignment
+ switch (halign) {
+ case "L": p.x -= w; break;
+ case "R": p.x += el.offsetWidth; break;
+ case "C": p.x += (el.offsetWidth - w) / 2; break;
+ case "l": p.x += el.offsetWidth - w; break;
+ case "r": break; // already there
+ }
+ p.width = w;
+ p.height = h + 40;
+ self.monthsCombo.style.display = "none";
+ fixPosition(p);
+ self.showAt(p.x, p.y);
+ };
+ if (Calendar.is_khtml)
+ setTimeout("Calendar.continuation_for_the_fucking_khtml_browser()", 10);
+ else
+ Calendar.continuation_for_the_fucking_khtml_browser();
+};
+
+/** Customizes the date format. */
+Calendar.prototype.setDateFormat = function (str) {
+ this.dateFormat = str;
+};
+
+/** Customizes the tooltip date format. */
+Calendar.prototype.setTtDateFormat = function (str) {
+ this.ttDateFormat = str;
+};
+
+/**
+ * Tries to identify the date represented in a string. If successful it also
+ * calls this.setDate which moves the calendar to the given date.
+ */
+Calendar.prototype.parseDate = function(str, fmt) {
+ if (!fmt)
+ fmt = this.dateFormat;
+ this.setDate(Date.parseDate(str, fmt));
+};
+
+Calendar.prototype.hideShowCovered = function () {
+ if (!Calendar.is_ie && !Calendar.is_opera)
+ return;
+ function getVisib(obj){
+ var value = obj.style.visibility;
+ if (!value) {
+ if (document.defaultView && typeof (document.defaultView.getComputedStyle) == "function") { // Gecko, W3C
+ if (!Calendar.is_khtml)
+ value = document.defaultView.
+ getComputedStyle(obj, "").getPropertyValue("visibility");
+ else
+ value = '';
+ } else if (obj.currentStyle) { // IE
+ value = obj.currentStyle.visibility;
+ } else
+ value = '';
+ }
+ return value;
+ };
+
+ var tags = new Array("applet", "iframe", "select");
+ var el = this.element;
+
+ var p = Calendar.getAbsolutePos(el);
+ var EX1 = p.x;
+ var EX2 = el.offsetWidth + EX1;
+ var EY1 = p.y;
+ var EY2 = el.offsetHeight + EY1;
+
+ for (var k = tags.length; k > 0; ) {
+ var ar = document.getElementsByTagName(tags[--k]);
+ var cc = null;
+
+ for (var i = ar.length; i > 0;) {
+ cc = ar[--i];
+
+ p = Calendar.getAbsolutePos(cc);
+ var CX1 = p.x;
+ var CX2 = cc.offsetWidth + CX1;
+ var CY1 = p.y;
+ var CY2 = cc.offsetHeight + CY1;
+
+ if (this.hidden || (CX1 > EX2) || (CX2 < EX1) || (CY1 > EY2) || (CY2 < EY1)) {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = cc.__msh_save_visibility;
+ } else {
+ if (!cc.__msh_save_visibility) {
+ cc.__msh_save_visibility = getVisib(cc);
+ }
+ cc.style.visibility = "hidden";
+ }
+ }
+ }
+};
+
+/** Internal function; it displays the bar with the names of the weekday. */
+Calendar.prototype._displayWeekdays = function () {
+ var fdow = this.firstDayOfWeek;
+ var cell = this.firstdayname;
+ var weekend = Calendar._TT["WEEKEND"];
+ for (var i = 0; i < 7; ++i) {
+ cell.className = "day name";
+ var realday = (i + fdow) % 7;
+ if (i) {
+ cell.ttip = Calendar._TT["DAY_FIRST"].replace("%s", Calendar._DN[realday]);
+ cell.navtype = 100;
+ cell.calendar = this;
+ cell.fdow = realday;
+ Calendar._add_evs(cell);
+ }
+ if (weekend.indexOf(realday.toString()) != -1) {
+ Calendar.addClass(cell, "weekend");
+ }
+ cell.innerHTML = Calendar._SDN[(i + fdow) % 7];
+ cell = cell.nextSibling;
+ }
+};
+
+/** Internal function. Hides all combo boxes that might be displayed. */
+Calendar.prototype._hideCombos = function () {
+ this.monthsCombo.style.display = "none";
+ this.yearsCombo.style.display = "none";
+};
+
+/** Internal function. Starts dragging the element. */
+Calendar.prototype._dragStart = function (ev) {
+ if (this.dragging) {
+ return;
+ }
+ this.dragging = true;
+ var posX;
+ var posY;
+ if (Calendar.is_ie) {
+ posY = window.event.clientY + document.body.scrollTop;
+ posX = window.event.clientX + document.body.scrollLeft;
+ } else {
+ posY = ev.clientY + window.scrollY;
+ posX = ev.clientX + window.scrollX;
+ }
+ var st = this.element.style;
+ this.xOffs = posX - parseInt(st.left);
+ this.yOffs = posY - parseInt(st.top);
+ with (Calendar) {
+ addEvent(document, "mousemove", calDragIt);
+ addEvent(document, "mouseup", calDragEnd);
+ }
+};
+
+// BEGIN: DATE OBJECT PATCHES
+
+/** Adds the number of days array to the Date object. */
+Date._MD = new Array(31,28,31,30,31,30,31,31,30,31,30,31);
+
+/** Constants used for time computations */
+Date.SECOND = 1000 /* milliseconds */;
+Date.MINUTE = 60 * Date.SECOND;
+Date.HOUR = 60 * Date.MINUTE;
+Date.DAY = 24 * Date.HOUR;
+Date.WEEK = 7 * Date.DAY;
+
+Date.parseDate = function(str, fmt) {
+ var today = new Date();
+ var y = 0;
+ var m = -1;
+ var d = 0;
+ var a = str.split(/\W+/);
+ var b = fmt.match(/%./g);
+ var i = 0, j = 0;
+ var hr = 0;
+ var min = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (!a[i])
+ continue;
+ switch (b[i]) {
+ case "%d":
+ case "%e":
+ d = parseInt(a[i], 10);
+ break;
+
+ case "%m":
+ m = parseInt(a[i], 10) - 1;
+ break;
+
+ case "%Y":
+ case "%y":
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ break;
+
+ case "%b":
+ case "%B":
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { m = j; break; }
+ }
+ break;
+
+ case "%H":
+ case "%I":
+ case "%k":
+ case "%l":
+ hr = parseInt(a[i], 10);
+ break;
+
+ case "%P":
+ case "%p":
+ if (/pm/i.test(a[i]) && hr < 12)
+ hr += 12;
+ else if (/am/i.test(a[i]) && hr >= 12)
+ hr -= 12;
+ break;
+
+ case "%M":
+ min = parseInt(a[i], 10);
+ break;
+ }
+ }
+ if (isNaN(y)) y = today.getFullYear();
+ if (isNaN(m)) m = today.getMonth();
+ if (isNaN(d)) d = today.getDate();
+ if (isNaN(hr)) hr = today.getHours();
+ if (isNaN(min)) min = today.getMinutes();
+ if (y != 0 && m != -1 && d != 0)
+ return new Date(y, m, d, hr, min, 0);
+ y = 0; m = -1; d = 0;
+ for (i = 0; i < a.length; ++i) {
+ if (a[i].search(/[a-zA-Z]+/) != -1) {
+ var t = -1;
+ for (j = 0; j < 12; ++j) {
+ if (Calendar._MN[j].substr(0, a[i].length).toLowerCase() == a[i].toLowerCase()) { t = j; break; }
+ }
+ if (t != -1) {
+ if (m != -1) {
+ d = m+1;
+ }
+ m = t;
+ }
+ } else if (parseInt(a[i], 10) <= 12 && m == -1) {
+ m = a[i]-1;
+ } else if (parseInt(a[i], 10) > 31 && y == 0) {
+ y = parseInt(a[i], 10);
+ (y < 100) && (y += (y > 29) ? 1900 : 2000);
+ } else if (d == 0) {
+ d = a[i];
+ }
+ }
+ if (y == 0)
+ y = today.getFullYear();
+ if (m != -1 && d != 0)
+ return new Date(y, m, d, hr, min, 0);
+ return today;
+};
+
+/** Returns the number of days in the current month */
+Date.prototype.getMonthDays = function(month) {
+ var year = this.getFullYear();
+ if (typeof month == "undefined") {
+ month = this.getMonth();
+ }
+ if (((0 == (year%4)) && ( (0 != (year%100)) || (0 == (year%400)))) && month == 1) {
+ return 29;
+ } else {
+ return Date._MD[month];
+ }
+};
+
+/** Returns the number of day in the year. */
+Date.prototype.getDayOfYear = function() {
+ var now = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var then = new Date(this.getFullYear(), 0, 0, 0, 0, 0);
+ var time = now - then;
+ return Math.floor(time / Date.DAY);
+};
+
+/** Returns the number of the week in year, as defined in ISO 8601. */
+Date.prototype.getWeekNumber = function() {
+ var d = new Date(this.getFullYear(), this.getMonth(), this.getDate(), 0, 0, 0);
+ var DoW = d.getDay();
+ d.setDate(d.getDate() - (DoW + 6) % 7 + 3); // Nearest Thu
+ var ms = d.valueOf(); // GMT
+ d.setMonth(0);
+ d.setDate(4); // Thu in Week 1
+ return Math.round((ms - d.valueOf()) / (7 * 864e5)) + 1;
+};
+
+/** Checks date and time equality */
+Date.prototype.equalsTo = function(date) {
+ return ((this.getFullYear() == date.getFullYear()) &&
+ (this.getMonth() == date.getMonth()) &&
+ (this.getDate() == date.getDate()) &&
+ (this.getHours() == date.getHours()) &&
+ (this.getMinutes() == date.getMinutes()));
+};
+
+/** Set only the year, month, date parts (keep existing time) */
+Date.prototype.setDateOnly = function(date) {
+ var tmp = new Date(date);
+ this.setDate(1);
+ this.setFullYear(tmp.getFullYear());
+ this.setMonth(tmp.getMonth());
+ this.setDate(tmp.getDate());
+};
+
+/** Prints the date in a string according to the given format. */
+Date.prototype.print = function (str) {
+ var m = this.getMonth();
+ var d = this.getDate();
+ var y = this.getFullYear();
+ var wn = this.getWeekNumber();
+ var w = this.getDay();
+ var s = {};
+ var hr = this.getHours();
+ var pm = (hr >= 12);
+ var ir = (pm) ? (hr - 12) : hr;
+ var dy = this.getDayOfYear();
+ if (ir == 0)
+ ir = 12;
+ var min = this.getMinutes();
+ var sec = this.getSeconds();
+ s["%a"] = Calendar._SDN[w]; // abbreviated weekday name [FIXME: I18N]
+ s["%A"] = Calendar._DN[w]; // full weekday name
+ s["%b"] = Calendar._SMN[m]; // abbreviated month name [FIXME: I18N]
+ s["%B"] = Calendar._MN[m]; // full month name
+ // FIXME: %c : preferred date and time representation for the current locale
+ s["%C"] = 1 + Math.floor(y / 100); // the century number
+ s["%d"] = (d < 10) ? ("0" + d) : d; // the day of the month (range 01 to 31)
+ s["%e"] = d; // the day of the month (range 1 to 31)
+ // FIXME: %D : american date style: %m/%d/%y
+ // FIXME: %E, %F, %G, %g, %h (man strftime)
+ s["%H"] = (hr < 10) ? ("0" + hr) : hr; // hour, range 00 to 23 (24h format)
+ s["%I"] = (ir < 10) ? ("0" + ir) : ir; // hour, range 01 to 12 (12h format)
+ s["%j"] = (dy < 100) ? ((dy < 10) ? ("00" + dy) : ("0" + dy)) : dy; // day of the year (range 001 to 366)
+ s["%k"] = hr; // hour, range 0 to 23 (24h format)
+ s["%l"] = ir; // hour, range 1 to 12 (12h format)
+ s["%m"] = (m < 9) ? ("0" + (1+m)) : (1+m); // month, range 01 to 12
+ s["%M"] = (min < 10) ? ("0" + min) : min; // minute, range 00 to 59
+ s["%n"] = "\n"; // a newline character
+ s["%p"] = pm ? "PM" : "AM";
+ s["%P"] = pm ? "pm" : "am";
+ // FIXME: %r : the time in am/pm notation %I:%M:%S %p
+ // FIXME: %R : the time in 24-hour notation %H:%M
+ s["%s"] = Math.floor(this.getTime() / 1000);
+ s["%S"] = (sec < 10) ? ("0" + sec) : sec; // seconds, range 00 to 59
+ s["%t"] = "\t"; // a tab character
+ // FIXME: %T : the time in 24-hour notation (%H:%M:%S)
+ s["%U"] = s["%W"] = s["%V"] = (wn < 10) ? ("0" + wn) : wn;
+ s["%u"] = w + 1; // the day of the week (range 1 to 7, 1 = MON)
+ s["%w"] = w; // the day of the week (range 0 to 6, 0 = SUN)
+ // FIXME: %x : preferred date representation for the current locale without the time
+ // FIXME: %X : preferred time representation for the current locale without the date
+ s["%y"] = ('' + y).substr(2, 2); // year without the century (range 00 to 99)
+ s["%Y"] = y; // year with the century
+ s["%%"] = "%"; // a literal '%' character
+
+ var re = /%./g;
+ if (!Calendar.is_ie5 && !Calendar.is_khtml)
+ return str.replace(re, function (par) { return s[par] || par; });
+
+ var a = str.match(re);
+ for (var i = 0; i < a.length; i++) {
+ var tmp = s[a[i]];
+ if (tmp) {
+ re = new RegExp(a[i], 'g');
+ str = str.replace(re, tmp);
+ }
+ }
+
+ return str;
+};
+
+Date.prototype.__msh_oldSetFullYear = Date.prototype.setFullYear;
+Date.prototype.setFullYear = function(y) {
+ var d = new Date(this);
+ d.__msh_oldSetFullYear(y);
+ if (d.getMonth() != this.getMonth())
+ this.setDate(28);
+ this.__msh_oldSetFullYear(y);
+};
+
+// END: DATE OBJECT PATCHES
+
+
+// global object that remembers the calendar
+window._dynarch_popupCalendar = null;
diff --git a/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar_stripped.js b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar_stripped.js
new file mode 100755
index 0000000..4fe03f1
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/dynarch_calendar/javascripts/calendar_stripped.js
@@ -0,0 +1,14 @@
+/* Copyright Mihai Bazon, 2002-2005 | www.bazon.net/mishoo
+ * -----------------------------------------------------------
+ *
+ * The DHTML Calendar, version 1.0 "It is happening again"
+ *
+ * Details and latest version at:
+ * www.dynarch.com/projects/calendar
+ *
+ * This script is developed by Dynarch.com. Visit us at www.dynarch.com.
+ *
+ * This script is distributed under the GNU Lesser General Public License.
+ * Read the entire license text here: http://www.gnu.org/licenses/lgpl.html
+ */
+ Calendar=function(firstDayOfWeek,dateStr,onSelected,onClose){this.activeDiv=null;this.currentDateEl=null;this.getDateStatus=null;this.getDateToolTip=null;this.getDateText=null;this.timeout=null;this.onSelected=onSelected||null;this.onClose=onClose||null;this.dragging=false;this.hidden=false;this.minYear=1970;this.maxYear=2050;this.dateFormat=Calendar._TT["DEF_DATE_FORMAT"];this.ttDateFormat=Calendar._TT["TT_DATE_FORMAT"];this.isPopup=true;this.weekNumbers=true;this.firstDayOfWeek=typeof firstDayOfWeek=="number"?firstDayOfWeek:Calendar._FD;this.showsOtherMonths=false;this.dateStr=dateStr;this.ar_days=null;this.showsTime=false;this.time24=true;this.yearStep=2;this.hiliteToday=true;this.multiple=null;this.table=null;this.element=null;this.tbody=null;this.firstdayname=null;this.monthsCombo=null;this.yearsCombo=null;this.hilitedMonth=null;this.activeMonth=null;this.hilitedYear=null;this.activeYear=null;this.dateClicked=false;if(typeof Calendar._SDN=="undefined"){if(typeof Calendar._SDN_len=="undefined")Calendar._SDN_len=3;var ar=new Array();for(var i=8;i>0;){ar[--i]=Calendar._DN[i].substr(0,Calendar._SDN_len);}Calendar._SDN=ar;if(typeof Calendar._SMN_len=="undefined")Calendar._SMN_len=3;ar=new Array();for(var i=12;i>0;){ar[--i]=Calendar._MN[i].substr(0,Calendar._SMN_len);}Calendar._SMN=ar;}};Calendar._C=null;Calendar.is_ie=(/msie/i.test(navigator.userAgent)&&!/opera/i.test(navigator.userAgent));Calendar.is_ie5=(Calendar.is_ie&&/msie 5\.0/i.test(navigator.userAgent));Calendar.is_opera=/opera/i.test(navigator.userAgent);Calendar.is_khtml=/Konqueror|Safari|KHTML/i.test(navigator.userAgent);Calendar.getAbsolutePos=function(el){var SL=0,ST=0;var is_div=/^div$/i.test(el.tagName);if(is_div&&el.scrollLeft)SL=el.scrollLeft;if(is_div&&el.scrollTop)ST=el.scrollTop;var r={x:el.offsetLeft-SL,y:el.offsetTop-ST};if(el.offsetParent){var tmp=this.getAbsolutePos(el.offsetParent);r.x+=tmp.x;r.y+=tmp.y;}return r;};Calendar.isRelated=function(el,evt){var related=evt.relatedTarget;if(!related){var type=evt.type;if(type=="mouseover"){related=evt.fromElement;}else if(type=="mouseout"){related=evt.toElement;}}while(related){if(related==el){return true;}related=related.parentNode;}return false;};Calendar.removeClass=function(el,className){if(!(el&&el.className)){return;}var cls=el.className.split(" ");var ar=new Array();for(var i=cls.length;i>0;){if(cls[--i]!=className){ar[ar.length]=cls[i];}}el.className=ar.join(" ");};Calendar.addClass=function(el,className){Calendar.removeClass(el,className);el.className+=" "+className;};Calendar.getElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.currentTarget;while(f.nodeType!=1||/^div$/i.test(f.tagName))f=f.parentNode;return f;};Calendar.getTargetElement=function(ev){var f=Calendar.is_ie?window.event.srcElement:ev.target;while(f.nodeType!=1)f=f.parentNode;return f;};Calendar.stopEvent=function(ev){ev||(ev=window.event);if(Calendar.is_ie){ev.cancelBubble=true;ev.returnValue=false;}else{ev.preventDefault();ev.stopPropagation();}return false;};Calendar.addEvent=function(el,evname,func){if(el.attachEvent){el.attachEvent("on"+evname,func);}else if(el.addEventListener){el.addEventListener(evname,func,true);}else{el["on"+evname]=func;}};Calendar.removeEvent=function(el,evname,func){if(el.detachEvent){el.detachEvent("on"+evname,func);}else if(el.removeEventListener){el.removeEventListener(evname,func,true);}else{el["on"+evname]=null;}};Calendar.createElement=function(type,parent){var el=null;if(document.createElementNS){el=document.createElementNS("http://www.w3.org/1999/xhtml",type);}else{el=document.createElement(type);}if(typeof parent!="undefined"){parent.appendChild(el);}return el;};Calendar._add_evs=function(el){with(Calendar){addEvent(el,"mouseover",dayMouseOver);addEvent(el,"mousedown",dayMouseDown);addEvent(el,"mouseout",dayMouseOut);if(is_ie){addEvent(el,"dblclick",dayMouseDblClick);el.setAttribute("unselectable",true);}}};Calendar.findMonth=function(el){if(typeof el.month!="undefined"){return el;}else if(typeof el.parentNode.month!="undefined"){return el.parentNode;}return null;};Calendar.findYear=function(el){if(typeof el.year!="undefined"){return el;}else if(typeof el.parentNode.year!="undefined"){return el.parentNode;}return null;};Calendar.showMonthsCombo=function(){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var mc=cal.monthsCombo;if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}if(cal.activeMonth){Calendar.removeClass(cal.activeMonth,"active");}var mon=cal.monthsCombo.getElementsByTagName("div")[cal.date.getMonth()];Calendar.addClass(mon,"active");cal.activeMonth=mon;var s=mc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var mcw=mc.offsetWidth;if(typeof mcw=="undefined")mcw=50;s.left=(cd.offsetLeft+cd.offsetWidth-mcw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";};Calendar.showYearsCombo=function(fwd){var cal=Calendar._C;if(!cal){return false;}var cal=cal;var cd=cal.activeDiv;var yc=cal.yearsCombo;if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}if(cal.activeYear){Calendar.removeClass(cal.activeYear,"active");}cal.activeYear=null;var Y=cal.date.getFullYear()+(fwd?1:-1);var yr=yc.firstChild;var show=false;for(var i=12;i>0;--i){if(Y>=cal.minYear&&Y<=cal.maxYear){yr.innerHTML=Y;yr.year=Y;yr.style.display="block";show=true;}else{yr.style.display="none";}yr=yr.nextSibling;Y+=fwd?cal.yearStep:-cal.yearStep;}if(show){var s=yc.style;s.display="block";if(cd.navtype<0)s.left=cd.offsetLeft+"px";else{var ycw=yc.offsetWidth;if(typeof ycw=="undefined")ycw=50;s.left=(cd.offsetLeft+cd.offsetWidth-ycw)+"px";}s.top=(cd.offsetTop+cd.offsetHeight)+"px";}};Calendar.tableMouseUp=function(ev){var cal=Calendar._C;if(!cal){return false;}if(cal.timeout){clearTimeout(cal.timeout);}var el=cal.activeDiv;if(!el){return false;}var target=Calendar.getTargetElement(ev);ev||(ev=window.event);Calendar.removeClass(el,"active");if(target==el||target.parentNode==el){Calendar.cellClick(el,ev);}var mon=Calendar.findMonth(target);var date=null;if(mon){date=new Date(cal.date);if(mon.month!=date.getMonth()){date.setMonth(mon.month);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}else{var year=Calendar.findYear(target);if(year){date=new Date(cal.date);if(year.year!=date.getFullYear()){date.setFullYear(year.year);cal.setDate(date);cal.dateClicked=false;cal.callHandler();}}}with(Calendar){removeEvent(document,"mouseup",tableMouseUp);removeEvent(document,"mouseover",tableMouseOver);removeEvent(document,"mousemove",tableMouseOver);cal._hideCombos();_C=null;return stopEvent(ev);}};Calendar.tableMouseOver=function(ev){var cal=Calendar._C;if(!cal){return;}var el=cal.activeDiv;var target=Calendar.getTargetElement(ev);if(target==el||target.parentNode==el){Calendar.addClass(el,"hilite active");Calendar.addClass(el.parentNode,"rowhilite");}else{if(typeof el.navtype=="undefined"||(el.navtype!=50&&(el.navtype==0||Math.abs(el.navtype)>2)))Calendar.removeClass(el,"active");Calendar.removeClass(el,"hilite");Calendar.removeClass(el.parentNode,"rowhilite");}ev||(ev=window.event);if(el.navtype==50&&target!=el){var pos=Calendar.getAbsolutePos(el);var w=el.offsetWidth;var x=ev.clientX;var dx;var decrease=true;if(x>pos.x+w){dx=x-pos.x-w;decrease=false;}else dx=pos.x-x;if(dx<0)dx=0;var range=el._range;var current=el._current;var count=Math.floor(dx/10)%range.length;for(var i=range.length;--i>=0;)if(range[i]==current)break;while(count-->0)if(decrease){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();}var mon=Calendar.findMonth(target);if(mon){if(mon.month!=cal.date.getMonth()){if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}Calendar.addClass(mon,"hilite");cal.hilitedMonth=mon;}else if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}}else{if(cal.hilitedMonth){Calendar.removeClass(cal.hilitedMonth,"hilite");}var year=Calendar.findYear(target);if(year){if(year.year!=cal.date.getFullYear()){if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}Calendar.addClass(year,"hilite");cal.hilitedYear=year;}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}else if(cal.hilitedYear){Calendar.removeClass(cal.hilitedYear,"hilite");}}return Calendar.stopEvent(ev);};Calendar.tableMouseDown=function(ev){if(Calendar.getTargetElement(ev)==Calendar.getElement(ev)){return Calendar.stopEvent(ev);}};Calendar.calDragIt=function(ev){var cal=Calendar._C;if(!(cal&&cal.dragging)){return false;}var posX;var posY;if(Calendar.is_ie){posY=window.event.clientY+document.body.scrollTop;posX=window.event.clientX+document.body.scrollLeft;}else{posX=ev.pageX;posY=ev.pageY;}cal.hideShowCovered();var st=cal.element.style;st.left=(posX-cal.xOffs)+"px";st.top=(posY-cal.yOffs)+"px";return Calendar.stopEvent(ev);};Calendar.calDragEnd=function(ev){var cal=Calendar._C;if(!cal){return false;}cal.dragging=false;with(Calendar){removeEvent(document,"mousemove",calDragIt);removeEvent(document,"mouseup",calDragEnd);tableMouseUp(ev);}cal.hideShowCovered();};Calendar.dayMouseDown=function(ev){var el=Calendar.getElement(ev);if(el.disabled){return false;}var cal=el.calendar;cal.activeDiv=el;Calendar._C=cal;if(el.navtype!=300)with(Calendar){if(el.navtype==50){el._current=el.innerHTML;addEvent(document,"mousemove",tableMouseOver);}else addEvent(document,Calendar.is_ie5?"mousemove":"mouseover",tableMouseOver);addClass(el,"hilite active");addEvent(document,"mouseup",tableMouseUp);}else if(cal.isPopup){cal._dragStart(ev);}if(el.navtype==-1||el.navtype==1){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout("Calendar.showMonthsCombo()",250);}else if(el.navtype==-2||el.navtype==2){if(cal.timeout)clearTimeout(cal.timeout);cal.timeout=setTimeout((el.navtype>0)?"Calendar.showYearsCombo(true)":"Calendar.showYearsCombo(false)",250);}else{cal.timeout=null;}return Calendar.stopEvent(ev);};Calendar.dayMouseDblClick=function(ev){Calendar.cellClick(Calendar.getElement(ev),ev||window.event);if(Calendar.is_ie){document.selection.empty();}};Calendar.dayMouseOver=function(ev){var el=Calendar.getElement(ev);if(Calendar.isRelated(el,ev)||Calendar._C||el.disabled){return false;}if(el.ttip){if(el.ttip.substr(0,1)=="_"){el.ttip=el.caldate.print(el.calendar.ttDateFormat)+el.ttip.substr(1);}el.calendar.tooltips.innerHTML=el.ttip;}if(el.navtype!=300){Calendar.addClass(el,"hilite");if(el.caldate){Calendar.addClass(el.parentNode,"rowhilite");}}return Calendar.stopEvent(ev);};Calendar.dayMouseOut=function(ev){with(Calendar){var el=getElement(ev);if(isRelated(el,ev)||_C||el.disabled)return false;removeClass(el,"hilite");if(el.caldate)removeClass(el.parentNode,"rowhilite");if(el.calendar)el.calendar.tooltips.innerHTML=_TT["SEL_DATE"];return stopEvent(ev);}};Calendar.cellClick=function(el,ev){var cal=el.calendar;var closing=false;var newdate=false;var date=null;if(typeof el.navtype=="undefined"){if(cal.currentDateEl){Calendar.removeClass(cal.currentDateEl,"selected");Calendar.addClass(el,"selected");closing=(cal.currentDateEl==el);if(!closing){cal.currentDateEl=el;}}cal.date.setDateOnly(el.caldate);date=cal.date;var other_month=!(cal.dateClicked=!el.otherMonth);if(!other_month&&!cal.currentDateEl)cal._toggleMultipleDate(new Date(date));else newdate=!el.disabled;if(other_month)cal._init(cal.firstDayOfWeek,date);}else{if(el.navtype==200){Calendar.removeClass(el,"hilite");cal.callCloseHandler();return;}date=new Date(cal.date);if(el.navtype==0)date.setDateOnly(new Date());cal.dateClicked=false;var year=date.getFullYear();var mon=date.getMonth();function setMonth(m){var day=date.getDate();var max=date.getMonthDays(m);if(day>max){date.setDate(max);}date.setMonth(m);};switch(el.navtype){case 400:Calendar.removeClass(el,"hilite");var text=Calendar._TT["ABOUT"];if(typeof text!="undefined"){text+=cal.showsTime?Calendar._TT["ABOUT_TIME"]:"";}else{text="Help and about box text is not translated into this language.\n"+"If you know this language and you feel generous please update\n"+"the corresponding file in \"lang\" subdir to match calendar-en.js\n"+"and send it back to to get it into the distribution ;-)\n\n"+"Thank you!\n"+"http://dynarch.com/mishoo/calendar.epl\n";}alert(text);return;case-2:if(year>cal.minYear){date.setFullYear(year-1);}break;case-1:if(mon>0){setMonth(mon-1);}else if(year-->cal.minYear){date.setFullYear(year);setMonth(11);}break;case 1:if(mon<11){setMonth(mon+1);}else if(year=0;)if(range[i]==current)break;if(ev&&ev.shiftKey){if(--i<0)i=range.length-1;}else if(++i>=range.length)i=0;var newval=range[i];el.innerHTML=newval;cal.onUpdateTime();return;case 0:if((typeof cal.getDateStatus=="function")&&cal.getDateStatus(date,date.getFullYear(),date.getMonth(),date.getDate())){return false;}break;}if(!date.equalsTo(cal.date)){cal.setDate(date);newdate=true;}else if(el.navtype==0)newdate=closing=true;}if(newdate){ev&&cal.callHandler();}if(closing){Calendar.removeClass(el,"hilite");ev&&cal.callCloseHandler();}};Calendar.prototype.create=function(_par){var parent=null;if(!_par){parent=document.getElementsByTagName("body")[0];this.isPopup=true;}else{parent=_par;this.isPopup=false;}this.date=this.dateStr?new Date(this.dateStr):new Date();var table=Calendar.createElement("table");this.table=table;table.cellSpacing=0;table.cellPadding=0;table.calendar=this;Calendar.addEvent(table,"mousedown",Calendar.tableMouseDown);var div=Calendar.createElement("div");this.element=div;div.className="calendar";if(this.isPopup){div.style.position="absolute";div.style.display="none";}div.appendChild(table);var thead=Calendar.createElement("thead",table);var cell=null;var row=null;var cal=this;var hh=function(text,cs,navtype){cell=Calendar.createElement("td",row);cell.colSpan=cs;cell.className="button";if(navtype!=0&&Math.abs(navtype)<=2)cell.className+=" nav";Calendar._add_evs(cell);cell.calendar=cal;cell.navtype=navtype;cell.innerHTML="
+=end
+
+module BundledResource::Jstree
+ def bundle
+ require_stylesheet "/bundles/jstree/stylesheets/resizable"
+
+ require_javascript "prototype"
+ require_javascript "effects"
+ require_javascript "/bundles/jstree/javascripts/behaviour"
+ require_javascript "/bundles/jstree/javascripts/jstree"
+ end
+end
diff --git a/vendor/plugins/bundled_resource/bundles/jstree/javascripts/behaviour.js b/vendor/plugins/bundled_resource/bundles/jstree/javascripts/behaviour.js
new file mode 100644
index 0000000..bc5504f
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/jstree/javascripts/behaviour.js
@@ -0,0 +1,254 @@
+/*
+ Behaviour v1.1 by Ben Nolan, June 2005. Based largely on the work
+ of Simon Willison (see comments by Simon below).
+
+ Description:
+
+ Uses css selectors to apply javascript behaviours to enable
+ unobtrusive javascript in html documents.
+
+ Usage:
+
+ var myrules = {
+ 'b.someclass' : function(element){
+ element.onclick = function(){
+ alert(this.innerHTML);
+ }
+ },
+ '#someid u' : function(element){
+ element.onmouseover = function(){
+ this.innerHTML = "BLAH!";
+ }
+ }
+ };
+
+ Behaviour.register(myrules);
+
+ // Call Behaviour.apply() to re-apply the rules (if you
+ // update the dom, etc).
+
+ License:
+
+ My stuff is BSD licensed. Not sure about Simon's.
+
+ More information:
+
+ http://ripcord.co.nz/behaviour/
+
+*/
+
+var Behaviour = {
+ list : new Array,
+
+ register : function(sheet){
+ Behaviour.list.push(sheet);
+ },
+
+ start : function(){
+ Behaviour.addLoadEvent(function(){
+ Behaviour.apply();
+ });
+ },
+
+ apply : function(){
+ for (h=0;sheet=Behaviour.list[h];h++){
+ for (selector in sheet){
+ list = document.getElementsBySelector(selector);
+
+ if (!list){
+ continue;
+ }
+
+ for (i=0;element=list[i];i++){
+ sheet[selector](element);
+ }
+ }
+ }
+ },
+
+ addLoadEvent : function(func){
+ var oldonload = window.onload;
+
+ if (typeof window.onload != 'function') {
+ window.onload = func;
+ } else {
+ window.onload = function() {
+ oldonload();
+ func();
+ }
+ }
+ }
+}
+
+Behaviour.start();
+
+/*
+ The following code is Copyright (C) Simon Willison 2004.
+
+ document.getElementsBySelector(selector)
+ - returns an array of element objects from the current document
+ matching the CSS selector. Selectors can contain element names,
+ class names and ids and can be nested. For example:
+
+ elements = document.getElementsBySelect('div#main p a.external')
+
+ Will return an array of all 'a' elements with 'external' in their
+ class attribute that are contained inside 'p' elements that are
+ contained inside the 'div' element which has id="main"
+
+ New in version 0.4: Support for CSS2 and CSS3 attribute selectors:
+ See http://www.w3.org/TR/css3-selectors/#attribute-selectors
+
+ Version 0.4 - Simon Willison, March 25th 2003
+ -- Works in Phoenix 0.5, Mozilla 1.3, Opera 7, Internet Explorer 6, Internet Explorer 5 on Windows
+ -- Opera 7 fails
+*/
+
+function getAllChildren(e) {
+ // Returns all children of element. Workaround required for IE5/Windows. Ugh.
+ return e.all ? e.all : e.getElementsByTagName('*');
+}
+
+document.getElementsBySelector = function(selector) {
+ // Attempt to fail gracefully in lesser browsers
+ if (!document.getElementsByTagName) {
+ return new Array();
+ }
+ // Split selector in to tokens
+ var tokens = selector.split(' ');
+ var currentContext = new Array(document);
+ for (var i = 0; i < tokens.length; i++) {
+ token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
+ if (token.indexOf('#') > -1) {
+ // Token is an ID selector
+ var bits = token.split('#');
+ var tagName = bits[0];
+ var id = bits[1];
+ var element = document.getElementById(id);
+ if (tagName && element.nodeName.toLowerCase() != tagName) {
+ // tag with that ID not found, return false
+ return new Array();
+ }
+ // Set currentContext to contain just this element
+ currentContext = new Array(element);
+ continue; // Skip to next token
+ }
+ if (token.indexOf('.') > -1) {
+ // Token contains a class selector
+ var bits = token.split('.');
+ var tagName = bits[0];
+ var className = bits[1];
+ if (!tagName) {
+ tagName = '*';
+ }
+ // Get elements matching tag, filter them for class selector
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements;
+ if (tagName == '*') {
+ elements = getAllChildren(currentContext[h]);
+ } else {
+ elements = currentContext[h].getElementsByTagName(tagName);
+ }
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ for (var k = 0; k < found.length; k++) {
+ if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
+ currentContext[currentContextIndex++] = found[k];
+ }
+ }
+ continue; // Skip to next token
+ }
+ // Code to deal with attribute selectors
+ if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
+ var tagName = RegExp.$1;
+ var attrName = RegExp.$2;
+ var attrOperator = RegExp.$3;
+ var attrValue = RegExp.$4;
+ if (!tagName) {
+ tagName = '*';
+ }
+ // Grab all of the tagName elements within current context
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements;
+ if (tagName == '*') {
+ elements = getAllChildren(currentContext[h]);
+ } else {
+ elements = currentContext[h].getElementsByTagName(tagName);
+ }
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ var checkFunction; // This function will be used to filter the elements
+ switch (attrOperator) {
+ case '=': // Equality
+ checkFunction = function(e) { return (e.getAttribute(attrName) == attrValue); };
+ break;
+ case '~': // Match one of space seperated words
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('\\b'+attrValue+'\\b'))); };
+ break;
+ case '|': // Match start with value followed by optional hyphen
+ checkFunction = function(e) { return (e.getAttribute(attrName).match(new RegExp('^'+attrValue+'-?'))); };
+ break;
+ case '^': // Match starts with value
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) == 0); };
+ break;
+ case '$': // Match ends with value - fails with "Warning" in Opera 7
+ checkFunction = function(e) { return (e.getAttribute(attrName).lastIndexOf(attrValue) == e.getAttribute(attrName).length - attrValue.length); };
+ break;
+ case '*': // Match ends with value
+ checkFunction = function(e) { return (e.getAttribute(attrName).indexOf(attrValue) > -1); };
+ break;
+ default :
+ // Just test for existence of attribute
+ checkFunction = function(e) { return e.getAttribute(attrName); };
+ }
+ currentContext = new Array;
+ var currentContextIndex = 0;
+ for (var k = 0; k < found.length; k++) {
+ if (checkFunction(found[k])) {
+ currentContext[currentContextIndex++] = found[k];
+ }
+ }
+ // alert('Attribute Selector: '+tagName+' '+attrName+' '+attrOperator+' '+attrValue);
+ continue; // Skip to next token
+ }
+
+ if (!currentContext[0]){
+ return;
+ }
+
+ // If we get here, token is JUST an element (not a class or ID selector)
+ tagName = token;
+ var found = new Array;
+ var foundCount = 0;
+ for (var h = 0; h < currentContext.length; h++) {
+ var elements = currentContext[h].getElementsByTagName(tagName);
+ for (var j = 0; j < elements.length; j++) {
+ found[foundCount++] = elements[j];
+ }
+ }
+ currentContext = found;
+ }
+ return currentContext;
+}
+
+/* That revolting regular expression explained
+/^(\w+)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/
+ \---/ \---/\-------------/ \-------/
+ | | | |
+ | | | The value
+ | | ~,|,^,$,* or =
+ | Attribute
+ Tag
+*/
diff --git a/vendor/plugins/bundled_resource/bundles/jstree/javascripts/jstree.js b/vendor/plugins/bundled_resource/bundles/jstree/javascripts/jstree.js
new file mode 100644
index 0000000..333bd38
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/jstree/javascripts/jstree.js
@@ -0,0 +1,124 @@
+JSTree = Class.create();
+Object.extend(JSTree.prototype, {
+ initialize: function(tree) {
+ this.element = $(tree);
+ this.options = Object.extend({
+ duration: 0.25,
+ collapseAll: false,
+ collapsedClass: 'collapsed',
+ expandedClass: 'expanded',
+ imagePath: '.',
+ imageCollapse: 'minus.gif',
+ imageExpand: 'plus.gif'
+ }, arguments[1] || {});
+
+ this._setup();
+ },
+
+ isCollapsed: function(el) {
+ el = $(el);
+ return el.className == this.options.collapsedClass;
+ },
+
+ collapse: function(el, duration) {
+ el = $(el);
+ if (duration == undefined) duration = this.options.duration;
+ var recurse = (arguments[2] == undefined) ? true : arguments[2];
+
+ el.className = this.options.collapsedClass;
+
+ var a = el.firstChild;
+ if ((a.nodeType == 1) && (a.className == 'jstreeControl')) {
+ a.style.backgroundImage = this._imageUrl(this.options.imageExpand);
+ }
+
+ if (recurse) {
+ var children = el.getElementsByTagName('li');
+ for (var i = 0; i < children.length; i++) {
+ var c = children[i];
+ this.collapse(c, duration, false);
+ }
+
+ var sublists = el.getElementsByTagName('ul')
+ for (var j = 0; j < sublists.length; j++) {
+ var list = sublists[j];
+ Effect.BlindUp(list, { 'duration': duration });
+ }
+
+ }
+ },
+
+ expand: function(el, duration) {
+ el = $(el);
+ if (duration == undefined) duration = this.options.duration;
+
+ el.className = this.options.expandedClass;
+
+ var a = el.firstChild;
+ if ((a.nodeType == 1) && (a.className == 'jstreeControl')) {
+ a.style.backgroundImage = this._imageUrl(this.options.imageCollapse);
+ }
+
+ var children = el.childNodes;
+ for (i = 0; i < children.length; i++) {
+ var child = children[i];
+ if ((child.nodeType == 1) && (child.tagName == 'UL')) {
+ Effect.BlindDown(child, { 'duration': duration });
+ }
+ }
+ },
+
+ _imageUrl: function(image) {
+ var path = this.options.imagePath + '/' + image;
+ return 'url("' + path +'")';
+ },
+
+ _toggle: function() {
+ if (this.treeControl.isCollapsed(this)) {
+ this.treeControl.expand(this);
+ } else {
+ this.treeControl.collapse(this);
+ }
+ },
+
+ _setup: function() {
+ var tree = this.element;
+ tree.setAttribute('style', 'list-style: none; ' + tree.style);
+
+ lists = tree.getElementsByTagName('ul');
+ for (var i = 0; i < lists.length; i++) {
+ var sublist = lists[i];
+ sublist.setAttribute('style', 'list-style: none; ' + sublist.style);
+ }
+
+ var items = tree.getElementsByTagName('li');
+ for (var i = 0; i < items.length; i++) {
+ var item = items[i];
+ var collapsable = item.getElementsByTagName('ul').length > 0;
+ if (collapsable) {
+ var collapsed = this.isCollapsed(item);
+ var img = this._imageUrl(collapsed ? this.options.imageExpand : this.options.imageCollapse);
+ var style = 'padding-left: 15px; background: ' + img + ' left no-repeat;';
+ var a = document.createElement('a');
+a.innerHTML = ' ';
+ a.setAttribute('style', style);
+ a.setAttribute('class', 'jstreeControl');
+ a.onclick = this._toggle.bindAsEventListener(item);
+ item.insertBefore(a, item.firstChild);
+ item.treeControl = this;
+
+ if (collapsed || this.options.collapseAll) {
+ this.collapse(item, 0);
+ }
+ }
+ }
+ }
+});
+
+Behaviour.register({
+ 'ul.jstree': function(list) {
+ // new JSTree(list, { 'collapseAll': true, 'duration': 0 });
+ // new JSTree(list, { 'collapseAll': true, 'duration': 3 });
+ new JSTree(list, { 'collapseAll': true });
+ }
+});
diff --git a/vendor/plugins/bundled_resource/bundles/lightbox.rb b/vendor/plugins/bundled_resource/bundles/lightbox.rb
new file mode 100644
index 0000000..43a3019
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/lightbox.rb
@@ -0,0 +1,20 @@
+# == Usage ==
+# require_bundle :lightbox
+#
+# Surround a thumbnail image inside of an anchor tag like this:
+#
+#
+#
+#
+# And your thumbnails will turn in to full-sized images within the browser window when clicked on!
+#
+# == Full Lightbox Documenation ==
+# http://www.huddletogether.com/projects/lightbox/
+
+module BundledResource::Lightbox
+ def bundle
+ require_javascript "/bundles/lightbox/javascripts/lightbox"
+
+ require_stylesheet "/bundles/lightbox/stylesheets/lightbox"
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/bundled_resource/bundles/lightbox/images/loading.gif b/vendor/plugins/bundled_resource/bundles/lightbox/images/loading.gif
new file mode 100644
index 0000000..fbe57be
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/lightbox/images/loading.gif differ
diff --git a/vendor/plugins/bundled_resource/bundles/lightbox/images/overlay.png b/vendor/plugins/bundled_resource/bundles/lightbox/images/overlay.png
new file mode 100644
index 0000000..7cee298
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/lightbox/images/overlay.png differ
diff --git a/vendor/plugins/bundled_resource/bundles/lightbox/javascripts/lightbox.js b/vendor/plugins/bundled_resource/bundles/lightbox/javascripts/lightbox.js
new file mode 100644
index 0000000..b5589eb
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/lightbox/javascripts/lightbox.js
@@ -0,0 +1,318 @@
+/*
+ Lightbox JS: Fullsize Image Overlays
+ by Lokesh Dhakar - http://www.huddletogether.com
+
+ For more information on this script, visit:
+ http://huddletogether.com/projects/lightbox/
+
+ Licensed under the Creative Commons Attribution 2.5 License - http://creativecommons.org/licenses/by/2.5/
+ (basically, do anything you want, just leave my name and link)
+
+ Table of Contents
+ -----------------
+ Configuration
+
+ Functions
+ - getPageScroll()
+ - getPageSize()
+ - pause()
+ - showLightbox()
+ - hideLightbox()
+ - initLightbox()
+ - addLoadEvent()
+
+ Function Calls
+ - addLoadEvent(initLightbox)
+
+*/
+
+
+
+//
+// Configuration
+//
+
+// If you would like to use a loading image, point to it in the next line, otherwise leave as-is.
+var loadingImage = '/bundles/lightbox/images/loading.gif';
+
+
+
+
+
+//
+// getPageScroll()
+// Returns array with x,y page scroll values.
+// Core code from - quirksmode.org
+//
+function getPageScroll(){
+
+ var yScroll;
+
+ if (self.pageYOffset) {
+ yScroll = self.pageYOffset;
+ } else if (document.documentElement && document.documentElement.scrollTop){ // Explorer 6 Strict
+ yScroll = document.documentElement.scrollTop;
+ } else if (document.body) {// all other Explorers
+ yScroll = document.body.scrollTop;
+ }
+
+ arrayPageScroll = new Array('',yScroll)
+ return arrayPageScroll;
+}
+
+
+
+//
+// getPageSize()
+// Returns array with page width, height and window width, height
+// Core code from - quirksmode.org
+//
+function getPageSize(){
+
+ var xScroll, yScroll;
+
+ if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
+ xScroll = document.body.scrollWidth;
+ yScroll = document.body.scrollHeight;
+ } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
+ xScroll = document.body.offsetWidth;
+ yScroll = document.body.offsetHeight;
+ }
+
+ var windowWidth, windowHeight;
+ if (self.innerHeight) { // all except Explorer
+ windowWidth = self.innerWidth;
+ windowHeight = self.innerHeight;
+ } else if (document.documentElement && document.documentElement.clientHeight) { // Explorer 6 Strict Mode
+ windowWidth = document.documentElement.clientWidth;
+ windowHeight = document.documentElement.clientHeight;
+ } else if (document.body) { // other Explorers
+ windowWidth = document.body.clientWidth;
+ windowHeight = document.body.clientHeight;
+ }
+
+ // for small pages with total height less then height of the viewport
+ if(yScroll < windowHeight){
+ pageHeight = windowHeight;
+ } else {
+ pageHeight = yScroll;
+ }
+
+ // for small pages with total width less then width of the viewport
+ if(xScroll < windowWidth){
+ pageWidth = windowWidth;
+ } else {
+ pageWidth = xScroll;
+ }
+
+
+ arrayPageSize = new Array(pageWidth,pageHeight,windowWidth,windowHeight)
+ return arrayPageSize;
+}
+
+//
+// pause(numberMillis)
+// Pauses code execution for specified time. Uses busy code, not good.
+// Code from http://www.faqts.com/knowledge_base/view.phtml/aid/1602
+//
+function pause(numberMillis) {
+ var now = new Date();
+ var exitTime = now.getTime() + numberMillis;
+ while (true) {
+ now = new Date();
+ if (now.getTime() > exitTime)
+ return;
+ }
+}
+
+
+
+
+
+//
+// showLightbox()
+// Preloads images. Pleaces new image in lightbox then centers and displays.
+//
+function showLightbox(objLink)
+{
+ // prep objects
+ var objOverlay = document.getElementById('overlay');
+ var objLightbox = document.getElementById('lightbox');
+ var objImage = document.getElementById('lightboxImage');
+ var objLoadingImage = document.getElementById('loadingImage');
+
+ var arrayPageSize = getPageSize();
+ var arrayPageScroll = getPageScroll();
+
+ // center loadingImage if it exists
+ if (objLoadingImage) {
+ objLoadingImage.style.top = (arrayPageScroll[1] + ((arrayPageSize[3] - 35 - objLoadingImage.height) / 2) + 'px');
+ objLoadingImage.style.left = (((arrayPageSize[0] - 40 - objLoadingImage.width) / 2) + 'px');
+ }
+
+ // set height of Overlay to take up whole page and show
+ objOverlay.style.height = (arrayPageSize[1] + 'px');
+ objOverlay.style.display = 'block';
+
+ // preload image
+ imgPreload = new Image();
+
+ imgPreload.onload=function(){
+ objImage.src = objLink.href;
+
+ // center lightbox
+ objLightbox.style.top = (arrayPageScroll[1] + ((arrayPageSize[3] - 35 - imgPreload.height) / 2) + 'px');
+ objLightbox.style.left = (((arrayPageSize[0] - 40 - imgPreload.width) / 2) + 'px');
+
+ // A small pause between the image loading and displaying is required with IE,
+ // this prevents the previous image displaying for a short burst causing flicker.
+ if (navigator.appVersion.indexOf("MSIE")!=-1){
+ pause(250);
+ }
+
+ objLightbox.style.display = 'block';
+
+ return false;
+ }
+
+ imgPreload.src = objLink.href;
+
+}
+
+
+
+
+
+//
+// hideLightbox()
+//
+function hideLightbox()
+{
+ // get objects
+ objOverlay = document.getElementById('overlay');
+ objLightbox = document.getElementById('lightbox');
+
+ // hide lightbox and overlay
+ objOverlay.style.display = 'none';
+ objLightbox.style.display = 'none';
+}
+
+
+
+
+//
+// initLightbox()
+// Function runs on window load, going through link tags looking for rel="lightbox".
+// These links receive onclick events that enable the lightbox display for their targets.
+// The function also inserts html markup at the top of the page which will be used as a
+// container for the overlay pattern and the inline image.
+//
+function initLightbox()
+{
+
+ if (!document.getElementsByTagName){ return; }
+ var anchors = document.getElementsByTagName("a");
+
+ // loop through all anchor tags
+ for (var i=0; i
+ //
+ //
+ //
+
+ var objBody = document.getElementsByTagName("body").item(0);
+
+ // create overlay div and hardcode some functional styles (aesthetic styles are in CSS file)
+ var objOverlay = document.createElement("div");
+ objOverlay.setAttribute('id','overlay');
+ objOverlay.style.display = 'none';
+ objOverlay.style.position = 'absolute';
+ objOverlay.style.top = '0';
+ objOverlay.style.left = '0';
+ objOverlay.style.zIndex = '90';
+ objOverlay.style.width = '100%';
+ objBody.insertBefore(objOverlay, objBody.firstChild);
+
+ var arrayPageSize = getPageSize();
+ var arrayPageScroll = getPageScroll();
+
+ // preload and create loader image
+ var imgPreloader = new Image();
+
+ // if loader image found, create link to hide lightbox and create loadingimage
+ imgPreloader.onload=function(){
+
+ var objLoadingImageLink = document.createElement("a");
+ objLoadingImageLink.setAttribute('href','#');
+ objLoadingImageLink.onclick = function () {hideLightbox(); return false;}
+ objOverlay.appendChild(objLoadingImageLink);
+
+ var objLoadingImage = document.createElement("img");
+ objLoadingImage.src = loadingImage;
+ objLoadingImage.setAttribute('id','loadingImage');
+ objLoadingImage.style.position = 'absolute';
+ objLoadingImage.style.zIndex = '150';
+ objLoadingImageLink.appendChild(objLoadingImage);
+
+ imgPreloader.onload=function(){}; // clear onLoad, as IE will flip out w/animated gifs
+
+ return false;
+ }
+
+ imgPreloader.src = loadingImage;
+
+ // create lightbox div, same note about styles as above
+ var objLightbox = document.createElement("div");
+ objLightbox.setAttribute('id','lightbox');
+ objLightbox.style.display = 'none';
+ objLightbox.style.position = 'absolute';
+ objLightbox.style.zIndex = '100';
+ objBody.insertBefore(objLightbox, objOverlay.nextSibling);
+
+ // create link
+ var objLink = document.createElement("a");
+ objLink.setAttribute('href','#');
+ objLink.onclick = function () {hideLightbox(); return false;}
+ objLightbox.appendChild(objLink);
+
+ // create image
+ var objImage = document.createElement("img");
+ objImage.setAttribute('id','lightboxImage');
+ objLink.appendChild(objImage);
+}
+
+
+
+
+//
+// addLoadEvent()
+// Adds event to window.onload without overwriting currently assigned onload functions.
+// Function found at Simon Willison's weblog - http://simon.incutio.com/
+//
+function addLoadEvent(func)
+{
+ var oldonload = window.onload;
+ if (typeof window.onload != 'function'){
+ window.onload = func;
+ } else {
+ window.onload = function(){
+ oldonload();
+ func();
+ }
+ }
+
+}
+
+
+
+
+addLoadEvent(initLightbox); // run initLightbox onLoad
\ No newline at end of file
diff --git a/vendor/plugins/bundled_resource/bundles/lightbox/stylesheets/lightbox.css b/vendor/plugins/bundled_resource/bundles/lightbox/stylesheets/lightbox.css
new file mode 100644
index 0000000..99de44b
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/lightbox/stylesheets/lightbox.css
@@ -0,0 +1,15 @@
+#lightbox{
+ background-color:#eee;
+ padding: 10px;
+ border-bottom: 2px solid #666;
+ border-right: 2px solid #666;
+ }
+
+#overlay{ background-image: url(/bundles/lightbox/images/overlay.png); }
+
+* html #overlay{
+ background-color: #333;
+ back\ground-color: transparent;
+ background-image: url(/bundles/lightbox/images/blank.gif);
+ filter: progid:DXImageTransform.Microsoft.AlphaImageLoader(src="/bundles/lightbox/images/overlay.png", sizingMethod="scale");
+ }
\ No newline at end of file
diff --git a/vendor/plugins/bundled_resource/bundles/qforms.rb b/vendor/plugins/bundled_resource/bundles/qforms.rb
new file mode 100644
index 0000000..8dc488e
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms.rb
@@ -0,0 +1,29 @@
+# == Usage ==
+# require_bundle :qforms
+#
+# 1. Name your form, e.g.
+#
+#
+# 2. Create a qForms object from the named HTML form:
+#
+#
+# == Full qForms Documentation ==
+# http://pengoworks.com/qforms/docs/index.htm
+
+module BundledResource::Qforms
+ def bundle
+ require_javascript "/bundles/qforms/javascripts/qforms"
+ # require_javascript "/bundles/qforms/javascripts/qforms/field"
+ # require_javascript "/bundles/qforms/javascripts/qforms/functions"
+ # require_javascript "/bundles/qforms/javascripts/qforms/validation"
+ require_javascript "/bundles/qforms/javascripts/qforms_init"
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms.js
new file mode 100755
index 0000000..cab3723
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms.js
@@ -0,0 +1,1143 @@
+/******************************************************************************
+ qForm JavaScript API
+
+ Author: Dan G. Switzer, II
+ Date: December 10, 2000
+ Build: 139
+
+ Description:
+ This library provides a API to forms on your page. This simplifies retrieval
+ of field values by providing methods to retrieve the values from fields,
+ without having to do complicate coding.
+
+ To contribute money to further the development of the qForms API, see:
+ http://www.pengoworks.com/qForms/donations/
+
+ GNU License
+ ---------------------------------------------------------------------------
+ This library provides common methods for interacting with HTML forms
+ Copyright (C) 2001 Dan G. Switzer, II
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for mser details.
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+******************************************************************************/
+// find out which version of JavaScript the user has
+var _jsver = 11;
+for( var z=2; z < 6; z++ ) document.write("_jsver = 1" + z + ";");
+
+/******************************************************************************
+ qForm API Initialization
+******************************************************************************/
+// define _a object
+function _a(){
+ // qForm's Version info
+ this.version = "139";
+
+ // initialize the number of qForm instances
+ this.instances = 0;
+ // initialize an object to use for pointers
+ this.objects = new Object();
+ // the path where the external library components are found
+ this.librarypath = "";
+ // specifies whether the browser should autodetect the version of JavaScript being used
+ this.autodetect = true;
+ // this specifies the default modules to load when the wildcard ("*") is specified
+ this.modules = new Array("field", "functions|12", "validation");
+ // this is the name of the modules that have been loaded, libraries will not be loaded more then once
+ this.packages = new Object();
+ // this is a list of validators that has loaded
+ this.validators = new Array();
+ // this contains a list of the original contents of a container, when the setValue() method is used on a container, then the containers object is checked to see if the key exists
+ this.containers = new Object();
+ // this structure defines the version of JavaScript being used
+ this.jsver = new Object();
+ for( var z=1; z < 9; z++ ) this.jsver["1" + z] = "1." + z;
+
+ // this is background color style to use when a form field validation error has occurred
+ this.errorColor = "red";
+ // the style attribute to adjust when throwing an error
+ this.styleAttribute = "backgroundColor";
+ // this specifies whether or not to use error color coding (by default browser that support it use it)
+ this.useErrorColorCoding = (document.all || document.getElementById) ? true : false;
+ // this specifies whether all qForm objects should be validated upon a form submission, or just the form being submitted. By default only the form being submitted is validated.
+ this.validateAll = false;
+ // this specifies whether or not a form can be submitted if validation errors occurred. If set to false, the user gets an alert box, if set to true, the user receives a confirm box.
+ this.allowSubmitOnError = false;
+ // the place holder for the number of custom validators that have been initialized
+ this.customValidators = 0;
+ // specify whether the reset method should be run when the form object is initialized
+ this.resetOnInit = false;
+ // determine whether to show status bar messages
+ this.showStatusMsgs = true;
+
+ // set the regular expression attributes
+ this.reAttribs = "gi";
+ return true;
+}
+qFormAPI = new _a();
+
+// define _a setLibraryPath(); prototype
+function _a_setLibraryPath(path){
+ if( path.substring(path.length-1) != '/' ) path += '/';
+ this.librarypath = path;
+ return true;
+}
+_a.prototype.setLibraryPath = _a_setLibraryPath;
+
+// define _a include(); prototype
+function _a_include(src, path, ver){
+ var source = src;
+ if( !source ) return true;
+ if( !path ) var path = this.librarypath + "qforms/";
+ if( !ver ) var ver = "";
+
+ if( source.substring(source.length-3) != ".js" ) source += ".js";
+ var thisPackage = source.substring(0,source.length-3);
+
+ var strJS = "";
+
+ // if the package is already loaded, then kill method
+ if( this.packages[thisPackage] ) return true;
+
+ if( thisPackage == "*" ){
+ for( var i=0; i < this.modules.length; i++ ){
+ var source = this.modules[i];
+ var ver = "99";
+ if( source.indexOf("|") > -1 ){
+ ver = source.substring(source.indexOf("|") + 1);
+ source = source.substring(0, source.indexOf("|"));
+ }
+ if( _jsver > ver && this.autodetect ){
+ document.write(strJS + this.jsver[ver] + "\" src=\"" + path + source + "_js" + ver + ".js" + strEJS);
+ } else {
+ document.write(strJS + "\" src=\"" + path + source + ".js" + strEJS);
+ }
+ this.packages[source] = true;
+ }
+ } else {
+ if( !this.autodetect || _jsver < 12 || ver.length == 0 ){
+ document.write(strJS + "\" src=\"" + path + source + strEJS);
+ } else if( this.autodetect && (parseInt(_jsver, 10) >= parseInt(ver, 10)) ){
+ source = source.substring(0,source.length-3) + "_js" + ver + source.substring(source.length-3);
+ document.write(strJS + this.jsver[ver] + "\" src=\"" + path + source + strEJS);
+ } else {
+ document.write(strJS + "\" src=\"" + path + source + strEJS);
+ }
+ }
+
+ this.packages[thisPackage] = true;
+ return true;
+}
+_a.prototype.include = _a_include;
+
+function _a_unload(){
+ var isFramed = false;
+ // loop through all the forms and reset the status of the form to idle
+ for( obj in qFormAPI.objects ){
+ qFormAPI.objects[obj]._status = "idle";
+ if( !!qFormAPI.objects[obj]._frame ) isFramed = true;
+ }
+ // some psuedo garbage collection to destroy some of the pointers if in a framed environment
+ if( isFramed ){
+ // kill the objects if using frames
+ this.objects = new Object();
+ // kill the containers if using frames
+ this.containers = new Object();
+ }
+ return true;
+}
+_a.prototype.unload = _a_unload;
+
+// define _a validate(); prototype
+function _a_validate(qForm){
+ // if just validate a single form, then validate now and exit
+ if( !this.validateAll ) return qFormAPI.objects[qForm].validate();
+
+ var aryErrors = new Array();
+
+ // loop through all the forms
+ for( obj in qFormAPI.objects ){
+ // check the form for errors
+ qFormAPI.objects[obj].checkForErrors();
+ // add the errors from this form t adde queue
+ for( var i=0; i < qFormAPI.objects[obj]._queue.errors.length; i++ ){
+ aryErrors[aryErrors.length] = qFormAPI.objects[obj]._queue.errors[i];
+ }
+ }
+
+ // if there are no errors then return true
+ if( aryErrors.length == 0 ) return true;
+
+ var strError = "The following error(s) occurred:\n";
+ for( var i=0; i < aryErrors.length; i++ ) strError += " - " + aryErrors[i] + "\n";
+
+ var result = false;
+ // check to see if the user is allowed to submit the form even if an error occurred
+ if( this._allowSubmitOnError && this._showAlerts ) result = confirm(strError + "\nAre you sure you want to continue?");
+ // if the form can be submitted with errors and errors should not be alerted set a hidden field equal to the errors
+ else if( this._allowSubmitOnError && !this._showAlerts ) result = true;
+ // otherwise, just display the error
+ else alert(strError);
+
+ return result;
+}
+_a.prototype.validate = _a_validate;
+
+function _a_reset(hardReset){
+ // loop through all the forms and reset the properties
+ for( obj in qFormAPI.objects ) qFormAPI.objects[obj].reset(hardReset);
+ return true;
+}
+_a.prototype.reset = _a_reset;
+
+// define _a getFields(); prototype
+function _a_getFields(){
+ stcAllData = new Object();
+
+ // loop through all the forms
+ for( obj in qFormAPI.objects ){
+ // check the form for errors
+ var tmpStruct = qFormAPI.objects[obj].getFields();
+ // add the value from this form to the structure
+ for( field in tmpStruct ){
+ if( !stcAllData[field] ){
+ stcAllData[field] = tmpStruct[field];
+ } else {
+ stcAllData[field] += "," + tmpStruct[field];
+ }
+ }
+ }
+
+ // return all the form data
+ return stcAllData;
+}
+_a.prototype.getFields = _a_getFields;
+
+// define _a setFields(); prototype
+function _a_setFields(struct, rd, ra){
+ // loop through each form and populate the fields
+ for( obj in qFormAPI.objects ) qFormAPI.objects[obj].setFields(struct, rd, ra);
+}
+_a.prototype.setFields = _a_setFields;
+
+// define _a dump(); prototype
+function _a_dump(){
+ var str = "";
+ formData = this.getFields();
+ for( field in formData ) str += field + " = " + formData[field] + "\n";
+ alert(str);
+}
+_a.prototype.dump = _a_dump;
+
+
+/******************************************************************************
+ qForm Object
+******************************************************************************/
+// define qForm object
+function qForm(name, parent, frame){
+ if( name == null ) return true;
+ if( !name ) return alert("No form specified.");
+ // increase the instance counter
+ qFormAPI.instances++;
+ // make sure the unload event is called
+ if( qFormAPI.instances == 1 ) window.onunload = new Function(_functionToString(window.onunload, ";qFormAPI.unload();"));
+ this._name = name;
+ this._parent = (!!parent) ? parent : null;
+ this._frame = (!!frame) ? frame : null;
+ this._status = null;
+ this._queue = new Object();
+ this._queue.errorFields = ",";
+ this._queue.errors = new Array();
+ this._queue.validation = new Array();
+ this._showAlerts = true;
+ this._allowSubmitOnError = qFormAPI.allowSubmitOnError;
+ this._locked = false;
+ this._skipValidation = false;
+ qFormAPI.objects[this._name] = this;
+ // this is a string pointer to the qFormAPI object copy of this object
+ this._pointer = "qFormAPI.objects['" + this._name + "']";
+ this.init();
+ return true;
+}
+// initialize dummy qForm object so that NS will initialize the prototype object
+new qForm(null, null, null);
+
+// define qForm init(); prototype
+function _q_init(){
+ if( !this._name ) return false;
+
+ // if this is NS4 and the form is in a layer
+ if( this._parent && document.layers ) this._form = this._parent + ".document." + this._name;
+ // otherwise point to the form
+ else this._form = "document." + this._name;
+
+ // if the form is in a frame, then add path to the frame
+ if( this._frame ) this._form = this._frame + "." + this._form;
+
+ // create a pointer to the form object
+ this.obj = eval(this._form);
+
+ // if the object doesn't exist, thrown an error
+ if( !this.obj ) return alert("The form \"" + this._name + "\" does not exist. This error \nwill occur if the Form object was initialized before the form \nhas been created or if it simply doesn't exist. Please make \nsure to initialize the Form object after page loads to avoid \npotential problems.");
+
+ // set the onSubmit method equal to whatever the current onSubmit is
+ // this function is then run whenever the submitCheck determines it's ok to submit the form
+ this.onSubmit = new Function(_functionToString(this.obj.onsubmit, ""));
+ // replace the form's onSubmit event and just run the submitCheck() method
+ var strSubmitCheck = this._pointer + ".submitCheck();";
+ if( this._frame ) strSubmitCheck = "top." + strSubmitCheck;
+ this.obj.onsubmit = new Function("return " + strSubmitCheck);
+
+ // loop through form elements
+ this._fields = new Array();
+ this._pointers = new Object();
+ for( var j=0; j < this.obj.elements.length; j++ ) this.addField(this.obj.elements[j].name);
+ this._status = "initialized";
+
+ // reset the form
+ if( qFormAPI.resetOnInit ) this.reset();
+
+ return true;
+}
+qForm.prototype.init = _q_init;
+
+// define qForm addField prototype
+function _q_addField(field){
+ if( typeof field == "undefined" || field.length == 0 ) return false;
+ o = this.obj[field];
+ if( typeof o == "undefined" ) return false;
+ // if the field is an array
+ if( typeof o.type == "undefined" ) o = o[0];
+ if( (!!o.type) && (typeof this[field] == "undefined") && (field.length > 0) ){
+ this[field] = new Field(o, field, this._name);
+ this._fields[this._fields.length] = field;
+ this._pointers[field.toLowerCase()] = this[field];
+ }
+ return true;
+}
+qForm.prototype.addField = _q_addField;
+
+// define qForm removeField prototype
+function _q_removeField(field){
+ // this function requires a JS1.2 browser
+
+ // currently, events attached to a form field are not
+ // deleted. this means you'll need to manually remove
+ // the field from the DOM, or errors will occur
+ if( typeof this[field] == "undefined" ) return false;
+
+ var f = this._fields;
+ // find the field in the fields array and remove it
+ for( var i=0; i < f.length; i++ ){
+ if( f[i] == field ){
+ var fp = i;
+ break;
+ }
+ }
+
+ if( _jsver >= 12 ){
+ delete this[field];
+ f.splice(fp,1);
+ delete this._pointers[field.toLowerCase()];
+
+ var q = this._queue.validation;
+ // loop through validation queue, and remove references of
+ for( var j=0; j < q.length; j++ ){
+ if( q[j][0] == field ){
+ q.splice(j,1);
+ j--;
+ }
+ }
+ }
+ return true;
+}
+qForm.prototype.removeField = _q_removeField;
+
+
+// define qForm submitCheck prototype
+function _q_submitCheck(){
+ // make sure the form is submitted more then once
+ if( this._status == "submitting" || this._status == "validating" ) return false;
+ this._status = "submitting";
+
+ // validate the form
+ var result = qFormAPI.validate(this._name);
+ // if no errors occurred, run the onSubmit() method
+ if( result ){
+ // run the custom onSubmit method
+ var x = this.onSubmit();
+ // if a boolean value was passed back, then update the result value
+ if( typeof x == "boolean" ) result = x;
+ }
+
+ // if the form shouldn't be submitted, then reset the form's status
+ if( !result ){
+ // if any validation errors occur or the form is not to be submitted because the
+ // onSubmit() event return false, then set the reset the form's status
+ this._status = "idle";
+ // run any processing that should be done before submitting the form
+ } else {
+ // make sure to select all "container" objects so the values are included when submitted
+ _setContainerValues(this);
+ }
+ return result;
+}
+qForm.prototype.submitCheck = _q_submitCheck;
+
+// define qForm onSubmit(); prototype
+qForm.prototype.onSubmit = new Function("");
+
+
+// define qForm addMethod(); prototype
+function _q_addMethod(name, fn, type){
+ if( arguments.length < 2 ) return alert("To create a new method, you must specify \nboth a name and function to run: \n obj.addMethod(\"checkTime\", _isTime);");
+ var type = _param(arguments[2], "from").toLowerCase();
+
+ // set the object to attach the prototype method to
+ if( type == "field" ) type = "Field";
+ else type = "qForm";
+
+ // if adding a predefined function, then add it now
+ if( typeof fn == "function" ){
+ strFN = fn.toString();
+ strFN = strFN.substring(strFN.indexOf(" "), strFN.indexOf("("));
+ eval(type + ".prototype." + name + " = " + strFN);
+
+ // if creating a new function, then add it now
+ } else {
+ var fnTemp = new Function(fn);
+ eval(type + ".prototype." + name + " = fnTemp;");
+ }
+ return true;
+}
+qForm.prototype.addMethod = _q_addMethod;
+
+// define qForm addEvent(); prototype
+function _q_addEvent(event, cmd, append){
+ if( arguments.length < 2 ) return alert("Invalid arguments. Please use the format \naddEvent(event, command, [append]).");
+ var append = _param(arguments[2], true, "boolean");
+ _addEvent(this._pointer + ".obj", arguments[0], arguments[1], append);
+ return true;
+}
+qForm.prototype.addEvent = _q_addEvent;
+
+// define qForm required(); prototype
+function _q_required(fields, value){
+ var value = _param(arguments[1], true, "boolean");
+ aryField = _removeSpaces(fields).split(",");
+
+ for( var i=0; i < aryField.length; i++ ){
+ if( !this[aryField[i]] ) return alert("The form field \"" + aryField[i] + "\" does not exist.");
+ this[aryField[i]].required = value;
+ }
+ return true;
+}
+qForm.prototype.required = _q_required;
+
+// define qForm optional(); prototype
+function _q_optional(fields){
+ // turn the fields off
+ this.required(fields, false);
+ return true;
+}
+qForm.prototype.optional = _q_optional;
+
+
+// define qForm forceValidation(); prototype
+function _q_forceValidation(fields, value){
+ var value = _param(arguments[1], true, "boolean");
+ aryField = _removeSpaces(fields).split(",");
+
+ for( var i=0; i < aryField.length; i++ ){
+ if( !this[aryField[i]] ) return alert("The form field \"" + aryField[i] + "\" does not exist.");
+ this[aryField[i]].validate = value;
+ }
+ return true;
+}
+qForm.prototype.forceValidation = _q_forceValidation;
+
+
+// define qForm submit(); prototype
+function _q_submit(){
+ var x = false;
+ // do not submit the form more then once
+ if( this._status == "submitting" ) return false;
+ if( this.obj.onsubmit() ) x = this.obj.submit();
+ return (typeof x == "undefined") ? true : x;
+}
+qForm.prototype.submit = _q_submit;
+
+// define qForm disabled(); prototype
+function _q_disabled(status){
+ var objExists = (typeof this.obj.disabled == "boolean") ? true : false;
+ if( arguments.length == 0 ) var status = (this.obj.disabled) ? false : true;
+ // if the "disabled" var doesn't exist, then use the build in "locked" feature
+ if( !objExists ) this._locked = status;
+ // switch the status of the disabled property
+ else this.obj.disabled = status;
+ return true;
+}
+qForm.prototype.disabled = _q_disabled;
+
+// define qForm reset(); prototype
+function _q_reset(hardReset){
+ if( this._status == null ) return false;
+ // loop through form elements
+ for( var j=0; j < this._fields.length; j++ ){
+ // reset the value for this field
+ this[this._fields[j]].setValue(((!!hardReset) ? null : this[this._fields[j]].defaultValue), true, false);
+ // enforce any depencies of the current field
+ if( this[this._fields[j]]._queue.dependencies.length > 0 ) this[this._fields[j]].enforceDependency();
+ }
+ return true;
+}
+qForm.prototype.reset = _q_reset;
+
+// define qForm getFields(); prototype
+function _q_getFields(){
+ if( this._status == null ) return false;
+ struct = new Object();
+ // loop through form elements
+ for( var j=0; j < this._fields.length; j++ ) struct[this._fields[j]] = this[this._fields[j]].getValue();
+ return struct;
+}
+qForm.prototype.getFields = _q_getFields;
+
+// define qForm setFields(); prototype
+function _q_setFields(struct, rd, ra){
+ if( this._status == null ) return false;
+ // if you need to reset the default values of the fields
+ var resetDefault = _param(arguments[1], false, "boolean");
+ var resetAll = _param(arguments[2], true, "boolean");
+ // reset the form
+ if( resetAll ) this.reset();
+ // loop through form elements
+ for( key in struct ){
+ var obj = this._pointers[key.toLowerCase()];
+ if( obj ){
+ obj.setValue(struct[key], true, false);
+ if(resetDefault) obj.defaultValue = struct[key];
+ }
+ }
+ return true;
+}
+qForm.prototype.setFields = _q_setFields;
+
+// define qForm hasChanged(); prototype
+function _q_hasChanged(){
+ if( this._status == null ) return false;
+ var b = false;
+ // loop through form elements
+ for( var j=0; j < this._fields.length; j++ ){
+ if( this[this._fields[j]].getValue() != this[this._fields[j]].defaultValue ){
+ b = true;
+ break;
+ }
+ }
+ return b;
+}
+qForm.prototype.hasChanged = _q_hasChanged;
+
+// define qForm changedFields(); prototype
+function _q_changedFields(){
+ if( this._status == null ) return false;
+ struct = new Object();
+ // loop through form elements
+ for( var j=0; j < this._fields.length; j++ ){
+ if( this[this._fields[j]].getValue() != this[this._fields[j]].defaultValue ){
+ struct[this._fields[j]] = this[this._fields[j]].getValue();
+ }
+ }
+ return struct;
+}
+qForm.prototype.changedFields = _q_changedFields;
+
+// define qForm dump(); prototype
+function _q_dump(){
+ var str = "";
+ var f = this.getFields();
+ for( fld in f ) str += fld + " = " + f[fld] + "\n";
+ alert(str);
+}
+qForm.prototype.dump = _q_dump;
+
+/******************************************************************************
+ Field Object
+******************************************************************************/
+// define Field object
+function Field(form, field, formName, init){
+ if( arguments.length > 3 ) return true;
+ this._queue = new Object();
+ this._queue.dependencies = new Array();
+ this._queue.validation = new Array();
+ this.qForm = qFormAPI.objects[formName];
+ this.name = field;
+ this.path = this.qForm._form + "['" + field + "']";
+ this.pointer = this.qForm._pointer + "['" + field + "']";
+ this.obj = eval(this.path);
+ this.locked = false;
+ this.description = field.toLowerCase();
+ this.required = false;
+ this.validate = false;
+ this.container = false;
+ this.type = (!this.obj.type && !!this.obj[0]) ? this.obj[0].type : this.obj.type;
+ this.validatorAttached = false;
+
+ var value = this.getValue();
+ this.defaultValue = value;
+ this.lastValue = value;
+
+ // initialize the field object
+ this.init();
+
+ return true;
+}
+new Field(null, null, null, true);
+
+// define Field init(); prototype
+function _f_init(){
+ if( qFormAPI.useErrorColorCoding && this.obj.style ) this.styleValue = (!!this.obj.style[qFormAPI.styleAttribute]) ? this.obj.style[qFormAPI.styleAttribute].toLowerCase() : "";
+
+ if( document.layers && (this.type == "radio" || this.type == "checkbox") && !!this.obj[0] ){
+ this.addEvent("onclick", "return " + this.pointer + ".allowFocus();");
+ } else {
+ this.addEvent("onfocus", "return " + this.pointer + ".allowFocus();");
+ }
+}
+Field.prototype.init = _f_init;
+
+// define Field allowFocus(); prototype
+function _f_allowFocus(){
+ // if the background color equals the error color, then reset the style to the original background
+ if( qFormAPI.useErrorColorCoding && this.obj.style ){
+ if( this.qForm._queue.errorFields.indexOf(","+this.name+",") > -1 ) this.obj.style[qFormAPI.styleAttribute] = this.styleValue;
+ }
+ // store the current value in the lastValue property
+ this.lastValue = this.getValue();
+ // check to see if the field is locked
+ var result = this.checkIfLocked();
+
+ // if the field is locked, and we have a select box, we need to reset the value of the field
+ // and call the onblur method to remove focus
+ if( (this.type.indexOf("select") > -1) && !result ){
+ this.resetLast();
+ this.blur();
+ }
+
+ // if the field isn't locked, run the onFocus event
+ if( !result ) this.onFocus();
+ // return the result of the checkIfLocked() method
+ return result;
+}
+Field.prototype.allowFocus = _f_allowFocus;
+
+// define qForm onFocus(); prototype
+Field.prototype.onFocus = new Function("");
+
+// define Field addEvent(); prototype
+function _f_addEvent(event, cmd, append){
+ if( arguments.length < 2 ) return alert("Invalid arguments. Please use the format \naddEvent(event, command, [append]).");
+ var append = _param(arguments[2], true, "boolean");
+
+ // if the field is a multi-array element, then apply the event to all items in the array
+ if( (this.type == "radio" || this.type == "checkbox") && !!this.obj[0] ){
+ for( var i=0; i < this.obj.length; i++ ) _addEvent(this.path + "[" + i + "]", arguments[0], arguments[1], append);
+ } else {
+ _addEvent(this.path, arguments[0], arguments[1], append);
+ }
+ return true;
+}
+Field.prototype.addEvent = _f_addEvent;
+
+// define Field disabled(); prototype
+function _f_disabled(s){
+ var status = arguments[0];
+ var oField = (this.type == "radio") ? this.obj[0] : this.obj;
+ var objExists = (typeof oField.disabled == "boolean") ? true : false;
+ if( arguments.length == 0 ) var status = (oField.disabled) ? false : true;
+ // if the "disabled" var doesn't exist, then use the build in "locked" feature
+ if( !objExists ) this.locked = status;
+ // switch the status of the disabled property
+ else {
+ if( !!this.obj[0] && this.type.indexOf("select") == -1 ) for( var i=0; i < this.obj.length; i++ ) this.obj[i].disabled = status;
+ else this.obj.disabled = status;
+ }
+ return true;
+}
+Field.prototype.disabled = _f_disabled;
+
+// define Field checkIfLocked(); prototype
+function _f_checkIfLocked(showMsg){
+ var bShowMsg = _param(arguments[0], this.qForm._showAlerts);
+ // if the value isn't equal to the key, then don't relocate the user
+ if( this.isLocked() ){
+ this.blur();
+ if( bShowMsg ) alert("This field is disabled.");
+ return false;
+ }
+ return true;
+}
+Field.prototype.checkIfLocked = _f_checkIfLocked;
+
+// define Field isLocked(); prototype
+function _f_isLocked(){
+ var isLocked = this.locked;
+ if( this.qForm._locked ) isLocked = true; // if the entire form is locked
+ return isLocked;
+}
+Field.prototype.isLocked = _f_isLocked;
+
+// define Field isDisabled(); prototype
+function _f_isDisabled(){
+ // if the disabled object exists, then get its status
+ if( typeof this.obj.disabled == "boolean" ){
+ var isDisabled = this.obj.disabled;
+ if( this.qForm.obj.disabled ) isDisabled = true; // if the entire form is locked
+ return isDisabled;
+ // otherwise, return false (saying it's not disabled)
+ } else {
+ return false;
+ }
+}
+Field.prototype.isDisabled = _f_isDisabled;
+
+// define Field focus(); prototype
+function _f_focus(){
+ if( !!this.obj.focus ) this.obj.focus();
+}
+Field.prototype.focus = _f_focus;
+
+// define Field blur(); prototype
+function _f_blur(){
+ if( !!this.obj.blur ) this.obj.blur();
+}
+Field.prototype.blur = _f_blur;
+
+// define Field select(); prototype
+function _f_select(){
+ if( !!this.obj.select ) this.obj.select();
+}
+Field.prototype.select = _f_select;
+
+// define Field reset(); prototype
+function _f_reset(){
+ this.setValue(this.defaultValue, true, false);
+}
+Field.prototype.reset = _f_reset;
+
+// define Field getValue(); prototype
+function _f_getValue(){
+ var type = (this.type.substring(0,6) == "select") ? "select" : this.type;
+ var value = new Array();
+
+ if( type == "select" ){
+ if( this.type == "select-one" && !this.container ){
+ value[value.length] = (this.obj.selectedIndex == -1) ? "" : this.obj[this.obj.selectedIndex].value;
+ } else {
+ // loop through all element in the array for this field
+ for( var i=0; i < this.obj.length; i++ ){
+ // if the element is selected, get the selected values (unless it's a dummy container)
+ if( (this.obj[i].selected || this.container) && (!this.dummyContainer) ){
+ // append the selected value, if the value property doesn't exist, use the text
+ value[value.length] = this.obj[i].value;
+ }
+ }
+ }
+ } else if( (type == "checkbox") || (type == "radio") ){
+ // if more then one checkbox
+ if( !!this.obj[0] && !this.obj.value ){
+ // loop through all checkbox elements, and if a checkbox is checked, grab the value
+ for( var i=0; i < this.obj.length; i++ ) if( this.obj[i].checked ) value[value.length] = this.obj[i].value;
+ // otherwise, store the value of the field (if checkmarked) into the list
+ } else if( this.obj.checked ){
+ value[value.length] = this.obj.value;
+ }
+ } else {
+ value[value.length] = this.obj.value;
+ }
+ return value.join(",");
+}
+Field.prototype.getValue = _f_getValue;
+
+// define Field setValue(); prototype
+function _f_setValue(value, bReset, doEvents){
+ this.lastValue = this.getValue();
+ var reset = _param(arguments[1], true, "boolean");
+ var doEvents = _param(arguments[2], true, "boolean");
+ var type = (this.type.substring(0,6) == "select") ? "select" : this.type;
+ var v;
+
+ if( type == "select" ){
+ var bSelectOne = (this.type == "select-one") ? true : false;
+ var orig = value;
+ value = "," + value + ",";
+ bLookForFirst = true; // if select-one type, then only select the first value found
+ // if the select box is not a container
+ if( !this.container ){
+ // loop through all element in the array for this field
+ for( var i=0; i < this.obj.length; i++ ){
+ v = this.obj[i].value;
+ bSelectItem = (value.indexOf("," + v + ",") > -1) ? true : false;
+ if( bSelectItem && (bLookForFirst || !bSelectOne) ) this.obj[i].selected = true;
+ else if( reset || bSelectOne) this.obj[i].selected = false;
+ if( bSelectItem && bLookForFirst ) bLookForFirst = false;
+ }
+ // if a select-one box and nothing selected, then try to select the default value
+ if( bSelectOne && bLookForFirst ){
+ if( this.defaultValue == orig ) if( this.obj.length > 0 ) this.obj[0].selected = true;
+ else this.setValue(this.defaultValue);
+ }
+ // if the select box is a container, then search through the container's original contents
+ } else {
+ newValues = new Object();
+ for( var i=0; i < this.boundContainers.length; i++ ){
+ var sCName = this.qForm._name + "_" + this.boundContainers[i];
+ // check to see if the container exists, if it does check for the value
+ if( qFormAPI.containers[sCName] ){
+ // loop through all the container objects
+ for( key in qFormAPI.containers[sCName] ){
+ // if the key is in the container, then make sure to add the value
+ if( value.indexOf("," + key + ",") > -1 ){
+ newValues[key] = qFormAPI.containers[sCName][key];
+ }
+ }
+ }
+ }
+ // populate the container values
+ this.populate(newValues, reset)
+ }
+
+ } else if( (type == "checkbox") || (type == "radio") ){
+ // if more then one checkbox
+ if( !!this.obj[0] && !this.obj.value ){
+ // surround the value by commas for detection
+ value = "," + value + ",";
+ // loop through all checkbox elements, and if a checkbox is checked, grab the value
+ for( var i=0; i < this.obj.length; i++ ){
+ if( value.indexOf("," + this.obj[i].value + ",") > -1 ) this.obj[i].checked = true;
+ else if( reset ) this.obj[i].checked = false;
+ }
+ // otherwise, store the value of the field (if checkmarked) into the list
+ } else if( this.obj.value == value ){
+ this.obj.checked = true;
+ } else if( reset ){
+ this.obj.checked = false;
+ }
+
+ } else {
+ this.obj.value = (!value) ? "" : value;
+ }
+
+ // run the trigger events
+ if( doEvents ){
+ this.triggerEvent("onblur");
+ // run the onchange event if the value has changed
+ if( this.lastValue != value ) this.triggerEvent("onchange");
+ }
+ // run the onSetValue method
+ this.onSetValue();
+
+ return true;
+}
+Field.prototype.setValue = _f_setValue;
+
+// define Field onSetValue(); prototype
+Field.prototype.onSetValue = new Function("");
+
+// define Field triggerEvent(); prototype
+function _f_triggerEvent(event){
+ oEvent = eval("this.obj." + event);
+ if( (this.obj.type == "checkbox") || (this.obj.type == "radio") && !!this.obj[0] ){
+ for( var k=0; k < this.obj.length; k++ ){
+ oEvent = eval("this.obj[k]." + event);
+ if( typeof oEvent == "function" ) oEvent();
+ }
+ } else if( typeof oEvent == "function" ){
+ oEvent();
+ }
+}
+Field.prototype.triggerEvent = _f_triggerEvent;
+
+/******************************************************************************
+ Validation Object
+******************************************************************************/
+// define qForm addValidator(); prototype
+function _q_addValidator(name, fn){
+ if( arguments.length < 2 ) return alert("To create a new validation object, you must specify \nboth a name and function to run: \n obj.addValidator(\"isTime\", __isTime);");
+ if( typeof fn == "string" ){
+ var _func = new Function(fn);
+ _addValidator(name, _func);
+ } else {
+ _addValidator(name, fn);
+ }
+ return true;
+}
+qForm.prototype.addValidator = _q_addValidator;
+
+// define Field validateExp(); prototype
+function _f_validateExp(expression, error, cmd){
+ var expression = _param(arguments[0], "false");
+ var error = _param(arguments[1], "An error occurred on the field '\" + this.description + \"'.");
+ var cmd = _param(arguments[2]);
+
+ var strFn = "if( " + expression + " ){ this.error = \"" + error + "\";}";
+ if( cmd.length > 0 ) strFn += cmd;
+ strValidateExp = "_validateExp" + qFormAPI.customValidators;
+ _addValidator(strValidateExp, new Function(strFn));
+ eval(this.pointer + ".validate" + strValidateExp + "();");
+ qFormAPI.customValidators++;
+}
+Field.prototype.validateExp = _f_validateExp;
+
+function _addValidator(name, fn, alwaysRun){
+ var alwaysRun = _param(arguments[2], false, "boolean");
+
+ if( arguments.length < 2 ) return alert("To create a new validation object, you must specify \nboth a name and function to run: \n _addValidator(\"isTime\", __isTime);");
+ // strip "is" out of name if present
+ if( name.substring(0,2).toLowerCase() == "is" ) name = name.substring(2);
+
+ // if the validator has already been loaded, do not load it
+ for( var a=0; a < qFormAPI.validators.length; a++ ) if( qFormAPI.validators[a] == name ) return alert("The " + name + " validator has already been loaded.");
+
+ // add the validator to the array of validators
+ qFormAPI.validators[qFormAPI.validators.length] = name;
+
+ // if not registering a simple expression evaluator, then update the status bar
+ if( qFormAPI.showStatusMsgs && name.substring(0,12) != "_validateExp" ){
+ // update the status bar with the initialization request
+ window.status = "Initializing the validate" + name + "() and is" + name + "() validation scripts...";
+ // clear the status bar
+ setTimeout("window.status = ''", 100);
+ }
+
+ var strFN = fn.toString();
+ var strName = strFN.substring(strFN.indexOf(" "), strFN.indexOf("("));
+ var strArguments = strFN.substring( strFN.indexOf("(")+1, strFN.indexOf(")") );
+ // remove spaces from the arguments
+ while( strArguments.indexOf(" ") > -1 ) strArguments = strArguments.substring( 0, strArguments.indexOf(" ") ) + strArguments.substring( strArguments.indexOf(" ")+1 );
+
+ // add rountine to check to see if the validation method should be processed
+ // if displaying errors, but the field is locked then return false immediately
+ var strBody = "var display = (this.qForm._status == 'validating') ? false : true;\n";
+ strBody += "if( (display && this.isLocked()) || this.qForm._status.substring(0,5) == 'error') return false;\n this.value = this.getValue();";
+ if( !alwaysRun ) strBody += "if( !display && this.value.length == 0 && !this.required ) return false;\n";
+ strBody += "this.error = '';\n";
+
+ // get the body of the custom function
+ strBody += strFN.substring( strFN.indexOf("{")+1, strFN.lastIndexOf("}") );
+
+ // if alerting the user to the error
+ strBody += "if( this.error.length > 0 && !!errorMsg) this.error = errorMsg;\n";
+ strBody += "if( display && this.error.length > 0 ){\n";
+ strBody += "if( this.qForm._status.indexOf('_ShowError') > -1 ){\n";
+ strBody += "this.qForm._status = 'error';\n";
+ // if the user has specified an error message, then display the custom message
+ strBody += "alert(this.error);\n";
+ strBody += "setTimeout(this.pointer + \".focus();\", 1);\n";
+ strBody += "setTimeout(this.pointer + \".qForm._status = 'idle';\", 100);\n";
+ strBody += "} return false;\n";
+ strBody += "} else if ( display ){ return true; } return this.error;\n";
+
+ // start build a string to create the new function
+ var strNewFN = "new Function(";
+ var aryArguments = strArguments.split(",");
+ for( var i=0; i < aryArguments.length; i++ ){
+ if(aryArguments[i] != "") strNewFN += "\"" + aryArguments[i] + "\",";
+ }
+ var strRuleFN = strNewFN;
+
+ strNewFN += "\"errorMsg\",strBody);";
+
+ // create the Field prototype for validation
+ eval("Field.prototype.is" + name + " = " + strNewFN);
+
+ // create validation rule, the validation rule must loop through the arguments provided
+ // and create a string to stick in the validation queue. This string will be eval() later
+ // on to check for errors
+ var strRule = "var cmd = this.pointer + '.is" + name + "';\n";
+ strRule += "cmd += '( ';\n";
+ strRule += "for( i=0; i < arguments.length; i++ ){ \n";
+ strRule += "if( typeof arguments[i] == 'string' ) cmd += '\"' + arguments[i] + '\",';\n";
+ strRule += "else cmd += arguments[i] + ',';\n";
+ strRule += "}\n";
+ strRule += "cmd = cmd.substring(0, cmd.length-1);\n";
+ strRule += "cmd += ')';\n";
+ strRule += "this.qForm._queue.validation[this.qForm._queue.validation.length] = new Array(this.name, cmd);\n";
+ strRule += "this._queue.validation[this._queue.validation.length] = cmd;\n";
+ strRule += "if( !this.validatorAttached ){ this.addEvent('onblur', this.pointer + '.checkForErrors()');";
+ strRule += "this.validatorAttached = true;}\n";
+ strRule += "return true;\n";
+ strRuleFN += "\"errorMsg\",strRule);";
+ eval("Field.prototype.validate" + name + " = " + strRuleFN);
+
+ return true;
+}
+
+// define Field checkForErrors(); prototype
+function _f_checkForErrors(){
+ if( !this.validate || this.qForms._skipValidation ) return true;
+ // change the status of the form
+ this.qForm._status += "_ShowError";
+ // loop through the validation queue and validation each item, if the item has already been validated, don't validate again
+ for( var i=0; i < this._queue.validation.length; i++ ) if( !eval(this._queue.validation[i]) ) break;
+ // reset the status to idle
+ setTimeout(this.pointer + ".qForm._status = 'idle';", 100);
+ return true;
+}
+Field.prototype.checkForErrors = _f_checkForErrors;
+
+// define qForm validate(); prototype
+function _q_validate(){
+ // if validation library hasn't been loaded, then return true
+ if( !qFormAPI.packages.validation || this._skipValidation ) return true;
+
+ // check the form for errors
+ this.checkForErrors();
+
+ // if there are no errors then return true
+ if( this._queue.errors.length == 0 ) return true;
+
+ // run the custom onError event, if it returns false, cancel request
+ var result = this.onError();
+ if( result == false ) return true;
+
+ var strError = "The following error(s) occurred:\n";
+ for( var i=0; i < this._queue.errors.length; i++ ) strError += " - " + this._queue.errors[i] + "\n";
+
+ var result = false;
+ // check to see if the user is allowed to submit the form even if an error occurred
+ if( this._allowSubmitOnError && this._showAlerts ) result = confirm(strError + "\nAre you sure you want to continue?");
+ // if the form can be submitted with errors and errors should not be alerted set a hidden field equal to the errors
+ else if( this._allowSubmitOnError && !this._showAlerts ) result = true;
+ // otherwise, just display the error
+ else alert(strError);
+
+ return result;
+}
+qForm.prototype.validate = _q_validate;
+
+// define qForm checkForErrors(); prototype
+function _q_checkForErrors(){
+ var status = this._status; // copy the current form's status
+ this._status = "validating"; // set form's status to validating
+ this._queue.errors = new Array(); // clear the current error queue
+ aryQueue = new Array(); // create a local queue for the required fields
+ this._queue.errorFields = ",";
+
+
+ // loop through form elements
+ for( var j=0; j < this._fields.length; j++ ){
+ // if the current field is required, then check to make sure it's value isn't blank
+ if( this[this._fields[j]].required ) aryQueue[aryQueue.length] = new Array(this._fields[j], this._pointer + "['" + this._fields[j] + "'].isNotEmpty(\"The " + this[this._fields[j]].description + " field is required.\");");
+ // reset the CSS settings on the field
+ if( qFormAPI.useErrorColorCoding && this[this._fields[j]].obj.style ) this[this._fields[j]].obj.style[qFormAPI.styleAttribute] = this[this._fields[j]].styleValue;
+ }
+
+ // loop through the required fields queue, if the field throws an error, don't validate later
+ for( var i=0; i < aryQueue.length; i++ ) this[aryQueue[i][0]].throwError(eval(aryQueue[i][1]));
+
+ // loop through the validation queue and validation each item, if the item has already been validated, don't validate again
+ for( var i=0; i < this._queue.validation.length; i++ ) this[this._queue.validation[i][0]].throwError(eval(this._queue.validation[i][1]));
+
+ // run the custom validation routine
+ this.onValidate();
+
+ // set form's status back to it's last status
+ this._status = status;
+
+ return true;
+}
+qForm.prototype.checkForErrors = _q_checkForErrors;
+
+// define qForm onValidate(); prototype
+qForm.prototype.onValidate = new Function("");
+
+// define qForm onError(); prototype
+qForm.prototype.onError = new Function("");
+
+// define Field throwError() prototype
+function _f_throwError(error){
+ var q = this.qForm;
+ // if the error msg is a valid string and this field hasn't errored already, then queue msg
+ if( (typeof error == "string") && (error.length > 0) && (q._queue.errorFields.indexOf("," + this.name + ",") == -1) ){
+ q._queue.errors[q._queue.errors.length] = error;
+ q._queue.errorFields += this.name + ",";
+ // change the background color of failed validation fields to red
+ if( qFormAPI.useErrorColorCoding && this.obj.style ) this.obj.style[qFormAPI.styleAttribute] = qFormAPI.errorColor;
+ return true;
+ }
+ return false;
+}
+Field.prototype.throwError = _f_throwError;
+
+/******************************************************************************
+ Required Functions
+******************************************************************************/
+// define the addEvent() function
+function _addEvent(obj, event, cmd, append){
+ if( arguments.length < 3 ) return alert("Invalid arguments. Please use the format \n_addEvent(object, event, command, [append]).");
+ var append = _param(arguments[3], true, "boolean");
+ var event = arguments[0] + "." + arguments[1].toLowerCase();
+ var objEvent = eval(event);
+ var strEvent = (objEvent) ? objEvent.toString() : "";
+ // strip out the body of the function
+ strEvent = strEvent.substring(strEvent.indexOf("{")+1, strEvent.lastIndexOf("}"));
+ strEvent = (append) ? (strEvent + cmd) : (cmd + strEvent);
+ strEvent += "\n";
+ eval(event + " = new Function(strEvent)");
+ return true;
+}
+
+// define the _functionToString() function
+function _functionToString(fn, cmd, append){
+ if( arguments.length < 1 ) return alert("Invalid arguments. Please use the format \n_functionToString(function, [command], [append]).");
+ var append = _param(arguments[2], true, "boolean");
+ var strFunction = (!fn) ? "" : fn.toString();
+ // strip out the body of the function
+ strFunction = strFunction.substring(strFunction.indexOf("{")+1, strFunction.lastIndexOf("}"));
+ if( cmd ) strFunction = (append) ? (strFunction + cmd + "\n") : (cmd + strFunction + "\n");
+ return strFunction;
+}
+
+// define the _param(value, default, type) function
+function _param(v, d, t){
+ // if no default value is present, use an empty string
+ if( typeof d == "undefined" ) d = "";
+ // if no type value is present, use "string"
+ if( typeof t == "undefined" ) t = "string";
+ // if datatype should be a number and it's a string, convert it to a number
+ if( t == "number" && typeof v == "string" ) var v = parseFloat(arguments[0]);
+ // get the value to return, if the v param is not equal to the type, use default value
+ var value = (typeof v != "undefined" && typeof v == t.toLowerCase()) ? v : d;
+ return value;
+}
+
+// define the _removeSpaces(value) function
+function _removeSpaces(v){
+ // remove all spaces
+ while( v.indexOf(" ") > -1 ) v = v.substring( 0, v.indexOf(" ") ) + v.substring( v.indexOf(" ")+1 );
+ return v;
+}
+
+// defined the _setContainerValues(obj) function
+function _setContainerValues(obj){
+ // loop through form elements
+ for( var i=0; i < obj._fields.length; i++ ){
+ if( obj[obj._fields[i]].container && obj[obj._fields[i]].type.substring(0,6) == "select" ){
+ for( var x=0; x < obj[obj._fields[i]].obj.length; x++ ){
+ obj[obj._fields[i]].obj[x].selected = (!obj[obj._fields[i]].dummyContainer);
+ }
+ }
+ }
+}
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/bits.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/bits.js
new file mode 100755
index 0000000..7c8bdbf
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/bits.js
@@ -0,0 +1,54 @@
+/******************************************************************************
+ qForm JSAPI: Bits Extensions Library
+
+ Author: Dan G. Switzer, II
+ Build: 101
+******************************************************************************/
+// define Field getBits(); prototype
+function _Field_getBits(useValue){
+ var isCheckbox = (this.type == "checkbox") ? true : false;
+ var isSelect = (this.type == "select-multiple") ? true : false;
+
+ if( !isCheckbox && !isSelect && (this.obj.length > 0) ) return alert("This method is only available to checkboxes or select boxes with multiple options.");
+ var useValue = _param(arguments[0], false, "boolean");
+
+ var iBit = 0;
+ // loop through all checkbox elements, and if a checkbox is checked, grab the value
+ for( var i=0; i < this.obj.length; i++ ){
+ // if the option is checked, then add the 2 ^ i to the existing value
+ if( isCheckbox && this.obj[i].checked ){
+ // append the selected value
+ iBit += (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i);
+ } else if( isSelect && this.obj.options[i].selected ){
+ iBit += (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i);
+ }
+ }
+ return iBit;
+}
+Field.prototype.getBits = _Field_getBits;
+
+// define Field setBits(); prototype
+function _Field_setBits(value, useValue){
+ var isCheckbox = (this.type == "checkbox") ? true : false;
+ var isSelect = (this.type == "select-multiple") ? true : false;
+
+ if( !isCheckbox && !isSelect && (this.obj.length > 0) ) return alert("This method is only available to checkboxes or select boxes with multiple options.");
+ var value = _param(arguments[0], "0");
+ var useValue = _param(arguments[1], false, "boolean");
+
+ var value = parseInt(value, 10);
+ // loop through all checkbox elements, and if a checkbox is checked, grab the value
+ for( var i=0; i < this.obj.length; i++ ){
+ // if the bitand returns the same as the value being checked, then the current
+ // checkbox should be checked
+ var j = (useValue) ? parseInt(this.obj[i].value, 10) : Math.pow(2, i);
+ var result = ( (value & j) == j) ? true : false;
+ if( isCheckbox ) this.obj[i].checked = result;
+ else if( isSelect ) this.obj.options[i].selected = result;
+ }
+ // if the value provided is greater then the last bit value, return false to indicate an error
+ // otherwise return true to say everything is ok
+ return (value < Math.pow(2, i)) ? true : false;
+}
+Field.prototype.setBits = _Field_setBits;
+
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cfform.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cfform.js
new file mode 100755
index 0000000..4c79ee7
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cfform.js
@@ -0,0 +1,191 @@
+/******************************************************************************
+ qForm JSAPI: CFFORM Validation Library
+
+ Author: Dan G. Switzer, II
+ Build: 100
+******************************************************************************/
+qFormAPI.packages.cfform = true;
+
+_addValidator(
+ "isBoolean",
+ function (){
+ if(
+ (this.value.toUpperCase() != "TRUE")
+ || (this.value.toUpperCase() != "FALSE")
+ || (this.value.toUpperCase() != "YES")
+ || (this.value.toUpperCase() != "NO")
+ || (this.value != "0")
+ || (this.value != "1")
+ ){
+ this.error = "The " + this.description + " field does not contain a boolean value.";
+ }
+ }
+);
+
+/**
+ * A string GUID value is required. A GUID is a string
+ * of length 36 formatted as XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX, where X is a
+ * hexadecimal digit (0-9 or A-F).
+ */
+_addValidator(
+ "isGUID",
+ function (){
+ var v = _trim(this.value);
+ if( !(/[A-Fa-f0-9]{8,8}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{12,12}/.test(v)) ){
+ this.error = "The " + this.description + " field does not contain a valid GUID.";
+ }
+ }
+);
+
+/**
+ * A string UUID value is required. A UUID is a string
+ * of length 35 formatted as XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX, where X is a
+ * hexadecimal digit (0-9 or A-F).
+ */
+_addValidator(
+ "isUUID",
+ function (){
+ var v = _trim(this.value);
+ if( !(/[A-Fa-f0-9]{8,8}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{4,4}-[A-Fa-f0-9]{16,16}/.test(v)) ){
+ this.error = "The " + this.description + " field does not contain a valid GUID.";
+ }
+ }
+);
+
+
+/**
+ * validate that the value is formatted correctly for a http/https/ftp url
+ * This pattern will match http/https/ftp urls.
+ *
+ * Matches: http://www.mm.com/index.cfm
+ * HTTP://WWW.MM.COM
+ * http://www.mm.com/index.cfm?userid=1&name=mike+nimer
+ * http://www.mm.com/index.cfm/userid/1/name/mike+nimer - trick used by cf developers so search engines can parse their sites (search engines ignore query strings)
+ * ftp://www.mm.com/
+ * ftp://uname:pass@www.mm.com/
+ * mailto:email@address.com
+ * news:rec.gardening
+ * news:rec.gardening
+ * http://a/
+ * file://ftp.yoyodyne.com/pub/files/foobar.txt
+ * Non-Matches: www.yahoo.com
+ * http:www.mm.com
+ *
+ */
+_addValidator(
+ "isURL",
+ function (){
+ var v = _trim(this.value).toLowerCase();
+ if( !(/^((http|https|ftp|file)\:\/\/([a-zA-Z0-0]*:[a-zA-Z0-0]*(@))?[a-zA-Z0-9-\.]+(\.[a-zA-Z]{2,3})?(:[a-zA-Z0-9]*)?\/?([a-zA-Z0-9-\._\?\,\'\/\+&%\$#\=~])*)|((mailto)\:[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z0-9]{2,7})|((news)\:[a-zA-Z0-9\.]*)$/.test(v)) ){
+ this.error = "The " + this.description + " field does not contain a valid URL.";
+ }
+ }
+);
+
+
+/**
+ * A string UUID value is required. A UUID is a string
+ * of length 35 formatted as XXXXXXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX, where X is a
+ * hexadecimal digit (0-9 or A-F).
+ */
+_addValidator(
+ "isRegEx",
+ function (r){
+ var v = _trim(this.value);
+ if( !(r.test(v)) ){
+ this.error = "The " + this.description + " field contains invalid data.";
+ }
+ }
+);
+
+/**
+ * validate that the value is formatted as a telephone correctly
+ * This pattern matches any US Telephone Number.
+ * This regular expression excludes the first number, after the area code,from being 0 or 1;
+ * it also allows an extension to be added where it does not have to be prefixed by 'x'.
+ *
+ * Matches:
+ * 617.219.2000
+ * 219-2000
+ * (617)283-3599 x234
+ * 1(222)333-4444
+ * 1 (222) 333-4444
+ * 222-333-4444
+ * 1-222-333-4444
+ * Non-Matches:
+ * 44-1344-458606
+ * +44-1344-458606
+ * +34-91-397-6611
+ * 7-095-940-2000
+ * +7-095-940-2000
+ * +49-(0)-889-748-5516
+*/
+_addValidator(
+ "isUSPhone",
+ function (){
+ var v = _trim(this.value);
+ if( !(/^(((1))?[ ,\-,\.]?([\\(]?([1-9][0-9]{2})[\\)]?))?[ ,\-,\.]?([^0-1]){1}([0-9]){2}[ ,\-,\.]?([0-9]){4}(( )((x){0,1}([0-9]){1,5}){0,1})?$/.test(v)) ){
+ this.error = "The " + this.description + " field does not contain a valid phone number.";
+ }
+ }
+);
+
+
+_addValidator(
+ "isTime",
+ function (){
+ var aTime = this.value.split(":");
+ var isTime = true;
+
+ if( (this.value.length == 0) || (aTime.length != 2) ) isTime = false;
+
+ if( isTime ){
+ var sHour = aTime[0];
+ var iHour = parseInt(sHour, 10);
+ var sMinute = aTime[1];
+ var iMinute = parseInt(sMinute, 10);
+
+ if( (sHour != String(iHour)) || (sMinute != String(iMinute)) ) isTime = false;
+ else if( (iHour < 0) || (iHour > 23) ) isTime = false;
+ else if( (iMinute < 0) || (iMinute > 59) ) isTime = false;
+ }
+
+ if( !isTime ){
+ this.error = "The " + this.description + " field does not contain a valid time.";
+ }
+ }
+);
+
+_addValidator(
+ "isInt",
+ function (){
+ var v = this.value;
+ var isNumeric = (v == String(parseFloat(v, 10)));
+ if( !isNumeric || (parseInt(v, 10) != parseFloat(v, 10)) ){
+ this.error = "The " + this.description + " field must contain an integer value.";
+ }
+ }
+);
+
+_addValidator(
+ "isFloat",
+ function (){
+ var v = this.value;
+ var isNumeric = (v == String(parseFloat(v, 10)));
+ if( !isNumeric || (parseInt(v, 10) == parseFloat(v, 10)) ){
+ this.error = "The " + this.description + " field must contain an floating number.";
+ }
+ }
+);
+
+_addValidator(
+ "isNumber",
+ function (){
+ var v = this.value;
+ if( v != String(parseFloat(v, 10)) ){
+ this.error = "The " + this.description + " field must contain a valid number.";
+ }
+ }
+);
+
+
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cookies.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cookies.js
new file mode 100755
index 0000000..f61f98e
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/cookies.js
@@ -0,0 +1,89 @@
+/******************************************************************************
+ qForm JSAPI: Cookie Library
+
+ Author: Dan G. Switzer, II
+ Build: 105
+******************************************************************************/
+// initialize workspace variables
+var _c_dToday = new Date();
+var _c_iExpiresIn = 90;
+var _c_strName = self.location.pathname;
+
+/******************************************************************************
+ Required Functions
+******************************************************************************/
+// retrieve a cookie from the browser
+function _getCookie(name){
+ var iStart = document.cookie.indexOf(name + "=");
+ var iLength = iStart + name.length + 1;
+ if( (iStart == -1) || (!iStart && (name == document.cookie.substring(0)) ) ) return null;
+ var iEnd = document.cookie.indexOf(";", iLength);
+ if( iEnd == -1 ) iEnd = document.cookie.length;
+ return unescape(document.cookie.substring(iLength, iEnd));
+}
+
+// set a cookie to the browser
+function _setCookie(name, value, expires, path, domain, secure){
+ document.cookie = name + "=" + escape(value) +
+ ( (expires) ? ";expires=" + expires.toGMTString() : "") +
+ ( (path) ? ";path=" + path : "") +
+ ( (domain) ? ";domain=" + domain : "") +
+ ( (secure) ? ";secure" : "");
+}
+
+function _deleteCookie(name, path, domain){
+ if (Get_Cookie(name)) document.cookie = name + "=" +
+ ( (path) ? ";path=" + path : "") +
+ ( (domain) ? ";domain=" + domain : "") +
+ ";expires=Thu, 01-Jan-1970 00:00:01 GMT";
+}
+
+function _createCookiePackage(struct){
+ var cookie = "";
+ for( key in struct ){
+ if( cookie.length > 0 ) cookie += "&";
+ cookie += key + ":" + escape(struct[key]);
+ }
+ return cookie;
+}
+
+function _readCookiePackage(pkg){
+ struct = new Object();
+ // break the package into key/value pairs
+ var a = pkg.split("&");
+ // loop through the array and seperate the key/value pairs
+ for( var i=0; i < a.length; i++ ) a[i] = a[i].split(":");
+ // convert the values into a structure
+ for( var i=0; i < a.length; i++ ) struct[a[i][0]] = unescape(a[i][1]);
+ // return the structure
+ return struct;
+}
+
+/******************************************************************************
+ qForm Methods
+******************************************************************************/
+// define qForm loadFields(); prototype
+function _qForm_loadFields(){
+ var strPackage = _getCookie("qForm_" + this._name + "_" + _c_strName);
+ // there is no form saved
+ if( strPackage == null ) return false;
+
+ this.setFields(_readCookiePackage(strPackage), null, true);
+}
+qForm.prototype.loadFields = _qForm_loadFields;
+
+// define qForm saveFields(); prototype
+function _qForm_saveFields(){
+ var expires = new Date(_c_dToday.getTime() + (_c_iExpiresIn * 86400000));
+ var strPackage = _createCookiePackage(this.getFields());
+ _setCookie("qForm_" + this._name + "_" + _c_strName, strPackage, expires);
+}
+qForm.prototype.saveFields = _qForm_saveFields;
+
+// define qForm saveOnSubmit(); prototype
+function _qForm_saveOnSubmit(){
+ // grab the current onSubmit() method and append the saveFields() method to it
+ var fn = _functionToString(this.onSubmit, "this.saveFields();");
+ this.onSubmit = new Function(fn);
+}
+qForm.prototype.saveOnSubmit = _qForm_saveOnSubmit;
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/field.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/field.js
new file mode 100755
index 0000000..dbcd296
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/field.js
@@ -0,0 +1,281 @@
+/******************************************************************************
+ qForm JSAPI: Field Extensions Library
+
+ Author: Dan G. Switzer, II
+ Build: 109
+******************************************************************************/
+// define Field makeContainer(); prototype
+function _Field_makeContainer(bindTo){
+ lstContainers = (arguments.length == 0) ? this.name : this.name + "," + arguments[0];
+ this.container = true;
+ this.defaultValue = this.getValue();
+ this.lastValue = this.defaultValue;
+ this.dummyContainer = false;
+ this.boundContainers = _listToArray(lstContainers.toLowerCase());
+ var thisKey = this.qForm._name + "_" + this.name.toLowerCase();
+
+ // copy objects from the select box into the container object
+ qFormAPI.containers[thisKey] = new Object();
+ for( var i=0; i < this.obj.options.length; i++ ){
+ qFormAPI.containers[thisKey][this.obj.options[i].value] = this.obj.options[i].text;
+ }
+}
+Field.prototype.makeContainer = _Field_makeContainer;
+
+// define Field resetLast(); prototype
+function _Field_resetLast(){
+ this.setValue(this.lastValue, null, false);
+ return true;
+}
+Field.prototype.resetLast = _Field_resetLast;
+
+// define Field toUpperCase(); prototype
+function _Field_toUpperCase(){
+ this.setValue(this.getValue().toUpperCase(), null, false);
+ return true;
+}
+Field.prototype.toUpperCase = _Field_toUpperCase;
+
+// define Field toLowerCase(); prototype
+function _Field_toLowerCase(){
+ this.setValue(this.getValue().toLowerCase(), null, false);
+ return true;
+}
+Field.prototype.toLowerCase = _Field_toLowerCase;
+
+// define Field ltrim(); prototype
+function _Field_ltrim(){
+ this.setValue(_ltrim(this.getValue()), null, false);
+ return true;
+}
+Field.prototype.ltrim = _Field_ltrim;
+
+// define Field rtrim(); prototype
+function _Field_rtrim(){
+ this.setValue(_rtrim(this.getValue()), null, false);
+ return true;
+}
+Field.prototype.rtrim = _Field_rtrim;
+
+// define Field trim(); prototype
+function _Field_trim(){
+ this.setValue(_trim(this.getValue()), null, false);
+ return true;
+}
+Field.prototype.trim = _Field_trim;
+
+// define Field compare(); prototype
+function _Field_compare(field){
+ if( this.getValue() == this.qForm[field].getValue() ){
+ return true;
+ } else {
+ return false;
+ }
+ return true;
+}
+Field.prototype.compare = _Field_compare;
+
+// define Field mirrorTo(); prototype
+function _Field_mirrorTo(objName){
+ // test to see if the object is qForm object
+ isQForm = ( objName.indexOf(".") > -1 ) ? !eval("!objName.substring(0,objName.indexOf('.'))") : false;
+
+ // if it's a qForm object, then set the value of the field to the current field when updated
+ if( isQForm ){
+ var strCommand = objName + ".setValue(" + this.pointer + ".getValue()" + ", null, false);";
+ // otherwise, set the local variable
+ } else {
+ var strCommand = objName + " = " + this.pointer + ".getValue();";
+ }
+
+ // add an onblur event so that when the field is updated, the requested field
+ // is updated with the value
+ this.addEvent(_getEventType(this.type), strCommand, false);
+}
+Field.prototype.mirrorTo = _Field_mirrorTo;
+
+// define Field createDependencyTo(); prototype
+function _Field_createDependencyTo(field, condition){
+ var condition = (arguments.length > 1) ? "\"" + arguments[1] + "\"" : null;
+ var otherField = this.qForm._pointer + "['" + field + "']";
+ if( !eval(otherField) ) return alert("The " + field + " field does not exist. The dependency \nto " + this.name + " can not be created.");
+ // add an onblur event so that when the field is updated, the requested field
+ // is updated with the value
+ if( this.qForm[field]._queue.dependencies.length == 0 ) this.qForm[field].addEvent(_getEventType(this.qForm[field].type), otherField + ".enforceDependency();", false);
+ this.qForm[field]._queue.dependencies[this.qForm[field]._queue.dependencies.length] = otherField + ".isDependent('" + this.name + "', " + condition + ");";
+ return true;
+}
+Field.prototype.createDependencyTo = _Field_createDependencyTo;
+
+// *this is an internal method that should only be used by the API*
+// define Field isDependent(); prototype
+function _Field_isDependent(field, condition){
+ var condition = _param(arguments[1], null);
+ this.value = this.getValue();
+
+ // if the current field is empty or not equal to the specified value, then the
+ // dependent field is not required, otherwise the dependency is enforced
+ if( condition == null ){
+ var result = (this.isNotEmpty() || this.required);
+ } else {
+ // if there's a space in the condition, assume you're to evaluate the string
+ if( condition.indexOf("this.") > -1 || condition == "true" || condition == "false" ){
+ var result = eval(condition);
+ // otherwise, you're doing a simple value compare
+ } else {
+ var result = (this.value == condition);
+ }
+ }
+ // return both the field and the result
+ var o = null;
+ o = new Object();
+ o.field = field;
+ o.result = result;
+ return o;
+}
+Field.prototype.isDependent = _Field_isDependent;
+
+// *this is an internal method that should only be used by the API*
+// define Field enforceDependency(); prototype
+function _Field_enforceDependency(e){
+ var lstExcludeFields = _param(arguments[0], ",");
+ var lstFieldsChecked = ",";
+ var lstFieldsRequired = ",";
+ // loop through all the dependency and run each one
+ for( var i=0; i < this._queue.dependencies.length; i++ ){
+ var s = eval(this._queue.dependencies[i]);
+ // keep a unique list of field checked
+ if( lstFieldsChecked.indexOf("," + s.field + ",") == -1 ) lstFieldsChecked += s.field + ",";
+ // keep a unique list of fields that now should be required
+ if( s.result && lstFieldsRequired.indexOf("," + s.field + ",") == -1 ) lstFieldsRequired += s.field + ",";
+ }
+ // create an array of the field checked
+ aryFieldsChecked = lstFieldsChecked.split(",");
+ // loop through the array skipping the first and last elements
+ for( var j=1; j < aryFieldsChecked.length-1; j++ ){
+ // determine if the field is required
+ var result = (lstFieldsRequired.indexOf("," + aryFieldsChecked[j] + ",") > -1);
+ // update it's status
+ this.qForm[aryFieldsChecked[j]].required = result;
+ // now go check the dependencies for the field whose required status was changed
+ // if the dependency rules for the field have already been run, then don't run
+ // them again
+ if( lstExcludeFields.indexOf("," + aryFieldsChecked[j] + ",") == -1 ) setTimeout(this.qForm._pointer + "." + aryFieldsChecked[j] + ".enforceDependency('" + lstExcludeFields + this.name + ",')", 1);
+ }
+}
+Field.prototype.enforceDependency = _Field_enforceDependency;
+
+
+// define Field location(); prototype
+function _Field_location(target, key){
+ var target = _param(arguments[0], "self");
+ var key = _param(arguments[1]);
+ // if the current field is disabled or locked, then kill the method
+ if( this.isLocked() || this.isDisabled() ) return this.setValue(key, null, false);
+
+ var value = this.getValue();
+ this.setValue(key, null, false);
+ // if the value isn't equal to the key, then don't relocate the user
+ if( value != key ) eval(target + ".location = value");
+
+ return true;
+}
+Field.prototype.location = _Field_location;
+
+// define Field format(); prototype
+function _Field_format(mask, type){
+ var type = _param(arguments[1], "numeric").toLowerCase();
+ this.validate = true;
+ this.validateFormat(mask, type);
+}
+Field.prototype.format = _Field_format;
+
+
+// define Field populate(); prototype
+function _Field_populate(struct, reset, sort, prefix){
+ // if the current field is disabled or locked, then kill the method
+ if( this.isLocked() || this.isDisabled() ) return false;
+
+ var reset = _param(arguments[1], true, "boolean");
+ var sort = _param(arguments[2], false, "boolean");
+ var prefix = _param(arguments[3], null, "object");
+
+ if( this.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes.");
+
+ // clear the select box
+ if( reset ) this.obj.length = 0;
+
+ // if prefixing options
+ if( !!prefix ) for( key in prefix ) this.obj.options[this.obj.length] = new Option(prefix[key], key);
+
+ // populate the select box
+ for( key in struct ) this.obj.options[this.obj.length] = new Option(struct[key], key);
+
+ // if the user wishes to sort the options in the select box
+ if( sort ) _sortOptions(this.obj);
+ return true;
+}
+Field.prototype.populate = _Field_populate;
+
+// define Field transferTo(); prototype
+function _Field_transferTo(field, sort, type, selectItems, reset){
+ // if the current field is disabled or locked, then kill the method
+ if( this.isLocked() || this.isDisabled() ) return false;
+ var sort = _param(arguments[1], true, "boolean");
+ var type = _param(arguments[2], "selected");
+ var selectItems = _param(arguments[3], true, "boolean");
+ var reset = _param(arguments[4], false, "boolean");
+
+ _transferOptions(this.obj, this.qForm[field].obj, sort, type, selectItems, reset);
+ return true;
+}
+Field.prototype.transferTo = _Field_transferTo;
+
+// define Field transferFrom(); prototype
+function _Field_transferFrom(field, sort, type, selectItems, reset){
+ // if the current field is disabled or locked, then kill the method
+ if( this.isLocked() || this.isDisabled() ) return false;
+ var sort = _param(arguments[1], true, "boolean");
+ var type = _param(arguments[2], "selected");
+ var selectItems = _param(arguments[3], true, "boolean");
+ var reset = _param(arguments[4], false, "boolean");
+
+ _transferOptions(this.qForm[field].obj, this.obj, sort, type, selectItems, reset);
+ return true;
+}
+Field.prototype.transferFrom = _Field_transferFrom;
+
+// define Field moveUp(); prototype
+function _Field_moveUp(){
+ // if the current field is disabled or locked, then kill the method
+ if( this.isLocked() || this.isDisabled() || this.type.substring(0,6) != "select" ) return false;
+
+ var oOptions = this.obj.options;
+ // rearrange
+ for( var i=1; i < oOptions.length; i++ ){
+ // swap options
+ if( oOptions[i].selected ){
+ _swapOptions(oOptions[i], oOptions[i-1]);
+ }
+ }
+ return true;
+}
+Field.prototype.moveUp = _Field_moveUp;
+
+// define Field moveDown(); prototype
+function _Field_moveDown(){
+ // if the current field is disabled or locked, then kill the method
+ if( this.isLocked() || this.isDisabled() || this.type.substring(0,6) != "select" ) return false;
+
+ var oOptions = this.obj.options;
+ // rearrange
+ for( var i=oOptions.length-2; i > -1; i-- ){
+ // swap options
+ if( oOptions[i].selected ){
+ _swapOptions(oOptions[i+1], oOptions[i]);
+ }
+ }
+ return true;
+}
+Field.prototype.moveDown = _Field_moveDown;
+
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions.js
new file mode 100755
index 0000000..af931ea
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions.js
@@ -0,0 +1,200 @@
+/******************************************************************************
+ qForm JSAPI: Functions Library
+
+ Author: Dan G. Switzer, II
+ Build: 108
+******************************************************************************/
+function _trim(s){ return _rtrim(_ltrim(s)); }
+
+function _ltrim(s){
+ var w = " \n\t\f";
+ // remove all beginning white space
+ while( w.indexOf(s.charAt(0)) != -1 && s.length != 0 ) s = s.substring(1);
+ return s;
+}
+
+function _rtrim(s){
+ var w = " \n\t\f";
+ // remove all ending white space
+ while( w.indexOf(s.charAt(s.length-1)) != -1 && s.length != 0 ) s = s.substring(0, s.length-1);
+ return s;
+}
+
+function _listToArray(string,delim){
+ var delim = _param(arguments[1], ",");
+ tmp = string.split(delim);
+ for( var i=0; i < tmp.length; i++ ) tmp[i] = _trim(tmp[i]);
+ return tmp;
+}
+
+function _listSum(string,delim){
+ var delim = _param(arguments[1], ",");
+ tmp = _listToArray(string,delim);
+ iValue = 0;
+ for( var i=0; i < tmp.length; i++ ) iValue += parseInt(tmp[i], 10);
+ return iValue;
+}
+
+function _stripInvalidChars(string, type){
+ var string = _param(arguments[0]);
+ var type = _param(arguments[1], "numeric").toLowerCase();
+ var validchars = type;
+
+ stcTypes = new Object();
+ stcTypes.numeric = "1234567890";
+ stcTypes.alpha = "abcdefghijklmnopqrstuvwxyz";
+ stcTypes.alphnumeric = "abcdefghijklmnopqrstuvwxyz1234567890";
+
+ if( stcTypes[type] ) validchars = stcTypes[type];
+
+ var tmp = "";
+ var lc = string.toLowerCase();
+ // loop through the string an make sure each character is an alpha character
+ for( var i=0;i < string.length;i++ ){
+ if (validchars.indexOf(lc.charAt(i)) != -1) tmp += string.charAt(i);
+ }
+ return tmp;
+}
+
+function _isLength(string, len, type){
+ var string = _param(arguments[0]);
+ var len = parseInt(_param(arguments[1], 10, "number"), 10);
+ var type = _param(arguments[2], "numeric");
+
+ var tmp = _stripInvalidChars(string, type);
+ return (tmp.length == len) ? true : false;
+}
+
+function _getState(abbr){
+ var abbr = _param(arguments[0]).toLowerCase();
+ _s = new Object(); _s.al = "Alabama"; _s.ak = "Alaska"; _s.as = "American Samoa"; _s.az = "Arizona"; _s.ar = "Arkansas"; _s.ca = "California"; _s.co = "Colorado"; _s.ct = "Connecticut"; _s.de = "Delaware"; _s.dc = "District of Columbia"; _s.fm = "Federal States of Micronesia"; _s.fl = "Florida"; _s.ga = "Georgia"; _s.gu = "Guam"; _s.hi = "Hawaii"; _s.id = "Idaho"; _s.il = "Illinois"; _s["in"] = "Indiana"; _s.ia = "Iowa"; _s.ks = "Kansas"; _s.ky = "Kentucky"; _s.la = "Louisana"; _s.me = "Maine"; _s.mh = "Marshall Islands"; _s.md = "Maryland"; _s.ma = "Massachusetts"; _s.mi = "Michigan"; _s.mn = "Minnesota"; _s.ms = "Mississippi"; _s.mo = "Missouri"; _s.mt = "Montana"; _s.ne = "Nebraska"; _s.nv = "Nevada"; _s.nh = "New Hampshire"; _s.nj = "New Jersey"; _s.nm = "New Mexico"; _s.ny = "New York"; _s.nc = "North Carolina"; _s.nd = "North Dakota"; _s.mp = "Northern Mariana Islands"; _s.oh = "Ohio"; _s.ok = "Oklahoma"; _s.or = "Oregon"; _s.pw = "Palau"; _s.pa = "Pennsylvania"; _s.pr = "Puerto Rico"; _s.ri = "Rhode Island"; _s.sc = "South Carolina"; _s.sd = "South Dakota"; _s.tn = "Tennessee"; _s.tx = "Texas"; _s.ut = "Utah"; _s.vt = "Vermont"; _s.vi = "Virgin Islands"; _s.va = "Virginia"; _s.wa = "Washington"; _s.wv = "West Virginia"; _s.wi = "Wisconsin"; _s.wy = "Wyoming"; _s.aa = "Armed Forces Americas"; _s.ae = "Armed Forces Africa/Europe/Middle East"; _s.ap = "Armed Forces Pacific";
+ if( !_s[abbr] ){
+ return null;
+ } else {
+ return _s[abbr];
+ }
+}
+
+// define the default properties for the sort
+qFormAPI.sortOptions = new Object();
+qFormAPI.sortOptions.order = "asc";
+qFormAPI.sortOptions.byText = true;
+function _sortOptions(obj, order, byText){
+ var order = _param(arguments[1], qFormAPI.sortOptions.order);
+ if( order != "asc" && order != "desc" ) order = "asc";
+ var byText = _param(arguments[2], qFormAPI.sortOptions.byText, "boolean");
+ var orderAsc = (order == "asc") ? true : false;
+
+ // loop through all the options and sort them asc
+ for( var i=0; i < obj.options.length; i++ ){
+ for( var j=0; j < obj.options.length-1; j++ ){
+ // if an option is greater than the next option, swap them
+ if( orderAsc && (byText && obj.options[j].text > obj.options[j+1].text) || (!byText && obj.options[j].value > obj.options[j+1].value) ){
+ tmpTxt = obj.options[j].text;
+ tmpVal = obj.options[j].value;
+ tmpSel = obj.options[j].selected;
+ obj.options[j].text = obj.options[j+1].text ;
+ obj.options[j].value = obj.options[j+1].value;
+ obj.options[j].selected = obj.options[j+1].selected;
+ obj.options[j+1].text = tmpTxt ;
+ obj.options[j+1].value = tmpVal;
+ obj.options[j+1].selected = tmpSel;
+ } else if( !orderAsc && (byText && obj.options[j].text < obj.options[j+1].text) || (!byText && obj.options[j].value < obj.options[j+1].value) ){
+ tmpTxt = obj.options[j].text;
+ tmpVal = obj.options[j].value;
+ tmpSel = obj.options[j].selected;
+ obj.options[j].text = obj.options[j+1].text ;
+ obj.options[j].value = obj.options[j+1].value;
+ obj.options[j].selected = obj.options[j+1].selected;
+ obj.options[j+1].text = tmpTxt ;
+ obj.options[j+1].value = tmpVal;
+ obj.options[j+1].selected = tmpSel;
+ }
+ }
+ }
+ return true;
+}
+
+function _transferOptions(field1, field2, sort, type, selectItems, reset){
+ var sort = _param(arguments[2], true, "boolean");
+ var type = _param(arguments[3].toLowerCase(), "selected");
+ if( type != "all" && type != "selected" ) type = "selected";
+ var selectItems = _param(arguments[4], true, "boolean");
+ var reset = _param(arguments[5], false, "boolean");
+ var doAll = (type == "all") ? true : false;
+
+ if( field1.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field1.name + "\" is not a select box.");
+ if( field2.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field2.name + "\" is not a select box.");
+
+ // clear the select box
+ if( reset ) field2.length = 0;
+
+ for( var i=0; i < field1.length; i++ ){
+ // if the current option is selected, move it
+ if( doAll || field1.options[i].selected ){
+ field2.options[field2.length] = new Option(field1.options[i].text, field1.options[i].value, false, selectItems);
+ field1.options[i] = null;
+ i--; // since you just deleted a option, redo this array position next loop
+ }
+ }
+
+ // if sorting the fields
+ if( sort ) _sortOptions(field2);
+ return true;
+}
+
+
+function _getURLParams(){
+ struct = new Object();
+ var strURL = document.location.href;
+ var iPOS = strURL.indexOf("?");
+ // if there are some query string params, split them into an array
+ if( iPOS != -1 ){
+ var strQS = strURL.substring(iPOS + 1);
+ var aryQS = strQS.split("&");
+
+ // otherwise, return the empty structure
+ } else {
+ return struct;
+ }
+
+ // loop through the array
+ for( var i=0; i < aryQS.length; i++ ){
+ iPOS = aryQS[i].indexOf("=");
+ // if no equal sign is found, then the value is null
+ if( iPOS == -1 ){
+ struct[aryQS[i]] = null;
+ // otherwise grab the variable name and it's value and stick it in structure
+ } else {
+ var key = aryQS[i].substring(0, iPOS);
+ var value = unescape(aryQS[i].substring(iPOS+1));
+ // if the value doesn't exist, then create a new key
+ if( !struct[key] ) struct[key] = value;
+ // otherwise, append the value
+ else struct[key] += "," + value;
+ }
+ }
+
+ return struct;
+}
+
+function _createFields(struct, type){
+ var type = _param(arguments[1], "hidden");
+ if( this.status == null ) return false;
+ // loop through form elements
+ for( key in struct ){
+ document.write("");
+ }
+ return true;
+}
+
+function _getEventType(type){
+ // the default event type
+ var strEvent = "onblur";
+ // if this is a checkbox & radio button, then mirror value on click
+ if( type == "checkbox" || type == "radio" ) strEvent = "onclick";
+ // if this is a select box, then mirror value when the value changes
+ else if( type.substring(0,6) == "select" ) strEvent = "onchange";
+ return strEvent;
+}
+
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions_js12.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions_js12.js
new file mode 100755
index 0000000..f96487e
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/functions_js12.js
@@ -0,0 +1,189 @@
+/******************************************************************************
+ qForm JSAPI: Functions Library
+
+ Author: Dan G. Switzer, II
+ Build: 110
+******************************************************************************/
+function _trim(s){
+ return _rtrim(_ltrim(s));
+}
+
+function _ltrim(s){
+ // remove all beginning white space
+ return (s.length == 0) ? s : s.replace(new RegExp("^\\s+", qFormAPI.reAttribs), "");
+}
+
+function _rtrim(s){
+ // remove all ending white space
+ return (s.length == 0) ? s : s.replace(new RegExp("\\s+$", qFormAPI.reAttribs), "");
+}
+
+function _listToArray(string,delim){
+ var delim = _param(arguments[1], ",");
+ tmp = string.split(delim);
+ for( var i=0; i < tmp.length; i++ ) tmp[i] = _trim(tmp[i]);
+ return tmp;
+}
+
+function _listSum(string,delim){
+ var delim = _param(arguments[1], ",");
+ tmp = _listToArray(string,delim);
+ iValue = 0;
+ for( var i=0; i < tmp.length; i++ ) iValue += parseInt(_trim(tmp[i]), 10);
+ return iValue;
+}
+
+function _stripInvalidChars(_s, _t){
+ var s = _param(arguments[0]);
+ var t = _param(arguments[1], "numeric").toLowerCase();
+ var r;
+
+ if( t == "numeric" ) r = new RegExp("([^0-9.]*)(^-?\\d*\\.?\\d*)(\\D*)", qFormAPI.reAttribs);
+ else if( t == "alpha" ) r = new RegExp("[^A-Za-z]+", qFormAPI.reAttribs);
+ else if( t == "alphanumeric" ) r = new RegExp("\\W+", qFormAPI.reAttribs);
+ else r = new RegExp("[^" + t + "]+", qFormAPI.reAttribs);
+
+ if( t == "numeric" ) s = s.replace(r, "$2");
+ else s = s.replace(r, "");
+
+ return s;
+}
+
+function _isLength(string, len, type){
+ var string = _param(arguments[0]);
+ var len = parseInt(_param(arguments[1], 10, "number"), 10);
+ var type = _param(arguments[2], "numeric");
+
+ var tmp = _stripInvalidChars(string, type);
+ return (tmp.length == len) ? true : false;
+}
+
+function _getState(abbr){
+ var abbr = _param(arguments[0]).toLowerCase();
+ _s = new Object(); _s.al = "Alabama"; _s.ak = "Alaska"; _s.as = "American Samoa"; _s.az = "Arizona"; _s.ar = "Arkansas"; _s.ca = "California"; _s.co = "Colorado"; _s.ct = "Connecticut"; _s.de = "Delaware"; _s.dc = "District of Columbia"; _s.fm = "Federal States of Micronesia"; _s.fl = "Florida"; _s.ga = "Georgia"; _s.gu = "Guam"; _s.hi = "Hawaii"; _s.id = "Idaho"; _s.il = "Illinois"; _s["in"] = "Indiana"; _s.ia = "Iowa"; _s.ks = "Kansas"; _s.ky = "Kentucky"; _s.la = "Louisana"; _s.me = "Maine"; _s.mh = "Marshall Islands"; _s.md = "Maryland"; _s.ma = "Massachusetts"; _s.mi = "Michigan"; _s.mn = "Minnesota"; _s.ms = "Mississippi"; _s.mo = "Missouri"; _s.mt = "Montana"; _s.ne = "Nebraska"; _s.nv = "Nevada"; _s.nh = "New Hampshire"; _s.nj = "New Jersey"; _s.nm = "New Mexico"; _s.ny = "New York"; _s.nc = "North Carolina"; _s.nd = "North Dakota"; _s.mp = "Northern Mariana Islands"; _s.oh = "Ohio"; _s.ok = "Oklahoma"; _s.or = "Oregon"; _s.pw = "Palau"; _s.pa = "Pennsylvania"; _s.pr = "Puerto Rico"; _s.ri = "Rhode Island"; _s.sc = "South Carolina"; _s.sd = "South Dakota"; _s.tn = "Tennessee"; _s.tx = "Texas"; _s.ut = "Utah"; _s.vt = "Vermont"; _s.vi = "Virgin Islands"; _s.va = "Virginia"; _s.wa = "Washington"; _s.wv = "West Virginia"; _s.wi = "Wisconsin"; _s.wy = "Wyoming"; _s.aa = "Armed Forces Americas"; _s.ae = "Armed Forces Africa/Europe/Middle East"; _s.ap = "Armed Forces Pacific";
+ if( !_s[abbr] ){
+ return null;
+ } else {
+ return _s[abbr];
+ }
+}
+
+// define the default properties for the sort
+qFormAPI.sortOptions = new Object();
+qFormAPI.sortOptions.order = "asc";
+qFormAPI.sortOptions.byText = true;
+
+function _sortOptions(obj, order, byText){
+ var order = _param(arguments[1], qFormAPI.sortOptions.order);
+ if( order != "asc" && order != "desc" ) order = "asc";
+ var byText = _param(arguments[2], qFormAPI.sortOptions.byText, "boolean");
+ var orderAsc = (order == "asc") ? true : false;
+
+ // loop through all the options and sort them asc
+ for( var i=0; i < obj.options.length; i++ ){
+ for( var j=0; j < obj.options.length-1; j++ ){
+ // if an option is greater than the next option, swap them
+ if( orderAsc && (byText && obj.options[j].text > obj.options[j+1].text) || (!byText && obj.options[j].value > obj.options[j+1].value) ){
+ _swapOptions(obj.options[j], obj.options[j+1]);
+ } else if( !orderAsc && (byText && obj.options[j].text < obj.options[j+1].text) || (!byText && obj.options[j].value < obj.options[j+1].value) ){
+ _swapOptions(obj.options[j], obj.options[j+1]);
+ }
+ }
+ }
+ return true;
+}
+
+function _swapOptions(o1, o2){
+ var sText = o1.text;
+ var sValue = o1.value;
+ var sSelected = o1.selected;
+ o1.text = o2.text ;
+ o1.value = o2.value;
+ o1.selected = o2.selected;
+ o2.text = sText;
+ o2.value = sValue;
+ o2.selected = sSelected;
+}
+
+function _transferOptions(field1, field2, sort, type, selectItems, reset){
+ var sort = _param(arguments[2], true, "boolean");
+ var type = _param(arguments[3], "selected").toLowerCase();
+ if( type != "all" && type != "selected" ) type = "selected";
+ var selectItems = _param(arguments[4], true, "boolean");
+ var reset = _param(arguments[5], false, "boolean");
+ var doAll = (type == "all") ? true : false;
+
+ if( field1.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field1.name + "\" is not a select box.");
+ if( field2.type.substring(0,6) != "select" ) return alert("This method is only available to select boxes. \nThe field \"" + field2.name + "\" is not a select box.");
+
+ // clear the select box
+ if( reset ) field2.length = 0;
+
+ for( var i=0; i < field1.length; i++ ){
+ // if the current option is selected, move it
+ if( doAll || field1.options[i].selected ){
+ field2.options[field2.length] = new Option(field1.options[i].text, field1.options[i].value, false, selectItems);
+ field1.options[i] = null;
+ i--; // since you just deleted a option, redo this array position next loop
+ }
+ }
+
+ // if sorting the fields
+ if( sort ) _sortOptions(field2);
+ return true;
+}
+
+
+function _getURLParams(){
+ struct = new Object();
+ var strURL = document.location.href;
+ var iPOS = strURL.indexOf("?");
+ // if there are some query string params, split them into an array
+ if( iPOS != -1 ){
+ var strQS = strURL.substring(iPOS + 1);
+ var aryQS = strQS.split("&");
+ // otherwise, return the empty structure
+ } else {
+ return struct;
+ }
+
+ // loop through the array
+ for( var i=0; i < aryQS.length; i++ ){
+ iPOS = aryQS[i].indexOf("=");
+ // if no equal sign is found, then the value is null
+ if( iPOS == -1 ){
+ struct[aryQS[i]] = null;
+ // otherwise grab the variable name and it's value and stick it in structure
+ } else {
+ var key = aryQS[i].substring(0, iPOS);
+ var value = unescape(aryQS[i].substring(iPOS+1));
+ // if the value doesn't exist, then create a new key
+ if( !struct[key] ) struct[key] = value;
+ // otherwise, append the value
+ else struct[key] += "," + value;
+ }
+ }
+ return struct;
+}
+
+function _createFields(struct, type){
+ var type = _param(arguments[1], "hidden");
+ if( this.status == null ) return false;
+ // loop through form elements
+ for( key in struct ){
+ document.write("");
+ }
+ return true;
+}
+
+function _getEventType(type){
+ // the default event type
+ var strEvent = "onblur";
+ // if this is a checkbox & radio button, then mirror value on click
+ if( type == "checkbox" || type == "radio" ) strEvent = "onclick";
+ // if this is a select box, then mirror value when the value changes
+ else if( type.substring(0,6) == "select" ) strEvent = "onchange";
+ return strEvent;
+}
+
+
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation.js
new file mode 100755
index 0000000..963cf70
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation.js
@@ -0,0 +1,404 @@
+/******************************************************************************
+ qForm JSAPI: Validation Library
+
+ Author: Dan G. Switzer, II
+ Build: 113
+******************************************************************************/
+qFormAPI.packages.validation = true;
+
+// define Field isNotNull(); prototype
+function _Field_isNotNull(){
+ // check for blank field
+ if( this.value.length == 0 ){
+ this.error = "You must specify a(n) " + this.description + ".";
+ }
+}
+_addValidator("isNotNull", _Field_isNotNull, true);
+
+// define Field isNotEmpty(); prototype
+function _Field_isNotEmpty(){
+ // check for blank field
+ if( _ltrim(this.value).length == 0 ){
+ this.error = "You must specify a(n) " + this.description + ".";
+ }
+}
+_addValidator("isNotEmpty", _Field_isNotEmpty);
+
+// define Field isEmail(); prototype
+function _Field_isEmail(){
+ // check for @ . or blank field
+ if( this.value.indexOf(" ") != -1 ){
+ this.error = "Invalid " + this.description + " address. An e-mail address should not contain a space.";
+ } else if( this.value.indexOf("@") == -1 ){
+ this.error = "Invalid " + this.description + " address. An e-mail address must contain the @ symbol.";
+ } else if( this.value.indexOf("@") == 0 ){
+ this.error = "Invalid " + this.description + " address. The @ symbol can not be the first character of an e-mail address.";
+ } else if( this.value.substring(this.value.indexOf("@")+2).indexOf(".") == -1 ){
+ this.error = "Invalid " + this.description + " address. An e-mail address must contain at least one period after the @ symbol.";
+ } else if( this.value.lastIndexOf("@") == this.value.length-1 ){
+ this.error = "Invalid " + this.description + " address. The @ symbol can not be the last character of an e-mail address.";
+ } else if( this.value.lastIndexOf(".") == this.value.length-1 ){
+ this.error = "Invalid " + this.description + " address. A period can not be the last character of an e-mail address.";
+ }
+}
+_addValidator("isEmail", _Field_isEmail);
+
+// define Field isPassword(); prototype
+function _Field_isPassword(field, minlen, maxlen){
+ var minlen = _param(arguments[1], 1, "number"); // default minimum length of password
+ var maxlen = _param(arguments[2], 255, "number"); // default maximum length of password
+
+ if( field != null && !this.compare(field) ){
+ this.error = "The " + this.description + " and " + this.qForm[field].description + " values do not match.";
+ }
+
+ // if there aren't an errors yet
+ if( this.error.length == 0 ){
+ if( (this.value.length < minlen) || (this.value.length > maxlen) ){
+ this.error = "The " + this.description + " field must be between " + minlen.toString() + " and " + maxlen.toString() + " characters long.";
+ }
+ }
+}
+_addValidator("isPassword", _Field_isPassword);
+
+// define Field isSame(); prototype
+function _Field_isSame(field){
+ if( !this.compare(field) ){
+ this.error = "The " + this.description + " and " + this.qForm[field].description + " values do not match.";
+ }
+}
+_addValidator("isSame", _Field_isSame);
+
+// define Field isDifferent(); prototype
+function _Field_isDifferent(field){
+ if( this.compare(field) ){
+ this.error = "The " + this.description + " and " + this.qForm[field].description + " must be different.";
+ }
+}
+_addValidator("isDifferent", _Field_isDifferent);
+
+// define Field isRange(); prototype
+function _Field_isRange(low, high){
+ var low = _param(arguments[0], 0, "number");
+ var high = _param(arguments[1], 9999999, "number");
+ var iValue = parseInt(this.value, 10);
+ if( isNaN(iValue) ) iValue = 0;
+
+ // check to make sure the number is within the valid range
+ if( ( low > iValue ) || ( high < iValue ) ){
+ this.error = "The " + this.description + " field does not contain a\nvalue between " + low + " and " + high + ".";
+ }
+}
+_addValidator("isRange", _Field_isRange);
+
+// define Field isInteger(); prototype
+function _Field_isInteger(){
+ var i = parseInt(this.value, 10);
+ // make sure the user specified a numeric value
+ if( isNaN(i) || (String(i) != this.value) ){
+ this.error = "The value for " + this.description + " is not a numeric value. This field requires a numeric value.";
+ }
+}
+_addValidator("isInteger", _Field_isInteger);
+
+// define Field isNumeric(); prototype
+function _Field_isNumeric(){
+ var i = parseFloat(this.value, 10);
+ // make sure the user specified a numeric value
+ if( isNaN(i) || (String(i) != this.value) ){
+ this.error = "The value for " + this.description + " is not a numeric value. This field requires a numeric value.";
+ }
+}
+_addValidator("isNumeric", _Field_isNumeric);
+
+// define Field isAlpha(); prototype
+function _Field_isAlpha(){
+ if( !_isLength(this.value, this.value.length, "alpha") ){
+ this.error = "The value for " + this.description + " must contain only alpha characters.";
+ }
+}
+_addValidator("isAlpha", _Field_isAlpha);
+
+// define Field isAlphaNumeric(); prototype
+function _Field_isAlphaNumeric(){
+ if( !_isLength(this.value, this.value.length, "alphanumeric") ){
+ this.error = "The value for " + this.description + " must contain only alpha-numeric characters.";
+ }
+}
+_addValidator("isAlphaNumeric", _Field_isAlphaNumeric);
+
+// define Field isDate(); prototype
+function _Field_isDate(mask){
+ var strMask = _param(arguments[0], "mm/dd/yyyy");
+ var iMaskMonth = strMask.lastIndexOf("m") - strMask.indexOf("m") + 1;
+ var iMaskDay = strMask.lastIndexOf("d") - strMask.indexOf("d") + 1;
+ var iMaskYear = strMask.lastIndexOf("y") - strMask.indexOf("y") + 1;
+
+ var strDate = this.value;
+
+ // find the delimiter
+ var delim = "", lstMask = "mdy";
+ for( var i=0; i < strMask.length; i++ ){
+ if (lstMask.indexOf(strMask.substring(i, i+1)) == -1){
+ delim = strMask.substring(i, i+1);
+ break;
+ }
+ }
+ aMask = strMask.split(delim);
+ if( aMask.length == 3 ){
+ dt = this.value.split(delim);
+ if( dt.length != 3 ) this.error = "An invalid date was provided for " + this.description + " field.";
+ for( i=0; i < aMask.length; i++ ){
+ if( aMask[i].indexOf("m") > -1 ) var sMonth = dt[i];
+ else if( aMask[i].indexOf("d") > -1 ) var sDay = dt[i];
+ else if( aMask[i].indexOf("y") > -1 ) var sYear = dt[i];
+ }
+ } else if( mask.length == 1 ){
+ var sMonth = this.value.substring(strMask.indexOf("m")-1, strMask.lastIndexOf("m"));
+ var sDay = this.value.substring(strMask.indexOf("d")-1, strMask.lastIndexOf("d"));
+ var sYear = this.value.substring(strMask.indexOf("y")-1, strMask.lastIndexOf("y"));
+ } else {
+ this.error = "An invalid date mask was provided for " + this.description + " field.";
+ }
+
+ var iMonth = parseInt(sMonth, 10);
+ var iDay = parseInt(sDay, 10);
+ var iYear = parseInt(sYear, 10);
+
+ if( isNaN(iMonth) || sMonth.length > iMaskMonth ) iMonth = 0;
+ if( isNaN(iDay) || sDay.length > iMaskDay ) iDay = 0;
+ if( isNaN(sYear) || sYear.length != iMaskYear ) sYear = null;
+
+ lst30dayMonths = ",4,6,9,11,";
+
+ if( sYear == null ){
+ this.error = "An invalid year was provided for the " + this.description + " field. The year \n should be a " + iMaskYear + " digit number.";
+ } else if( (iMonth < 1) || (iMonth > 12 ) ){
+ this.error = "An invalid month was provided for " + this.description + " field.";
+ } else {
+ if( iYear < 100 ) var iYear = iYear + ((iYear > 20) ? 1900 : 2000);
+ var iYear = (sYear.length == 4) ? parseInt(sYear, 10) : parseInt("20" + sYear, 10);
+ if( lst30dayMonths.indexOf("," + iMonth + ",") > -1 ){
+ if( (iDay < 1) || (iDay > 30 ) ) this.error = "An invalid day was provided for the " + this.description + " field.";
+ } else if( iMonth == 2 ){
+ if( (iDay < 1) || (iDay > 28 && !( (iDay == 29) && (iYear%4 == 0 ) ) ) ) this.error = "An invalid day was provided for the " + this.description + " field.";
+ } else {
+ if( (iDay < 1) || (iDay > 31 ) ) this.error = "An invalid day was provided for the " + this.description + " field.";
+ }
+ }
+
+}
+_addValidator("isDate", _Field_isDate);
+
+
+// define Field isCreditCard(); prototype
+function _Field_isCreditCard(){
+ var strCC = _stripInvalidChars(this.value, "numeric").toString();
+ var isNumeric = (strCC.length > 0) ? true : false;
+
+ if( isNumeric ){
+ // now check mod10
+ var dd = (strCC.length % 2 == 1) ? false : true;
+ var cd = 0;
+ var td;
+
+ for( var i=0; i < strCC.length; i++ ){
+ td = parseInt(strCC.charAt(i), 10);
+ if( dd ){
+ td *= 2;
+ cd += (td % 10);
+ if ((td / 10) >= 1.0) cd++;
+ dd = false;
+ } else {
+ cd += td;
+ dd = true;
+ }
+ }
+ if( (cd % 10) != 0 ) this.error = "The credit card number entered in the " + this.description + " field is invalid.";
+ } else {
+ this.error = "The credit card number entered in the " + this.description + " field is invalid.";
+ }
+}
+_addValidator("isCreditCard", _Field_isCreditCard);
+
+// define Field isPhoneNumber(); prototype
+function _Field_isPhoneNumber(len){
+ var len = parseInt(_param(arguments[0], 10, "number"), 10);
+ var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description;
+
+ // check to make sure the phone is the correct length
+ if( !_isLength(this.value, len) ){
+ this.error = "The " + description + " field must include " + len + " digits.";
+ }
+}
+_addValidator("isPhoneNumber", _Field_isPhoneNumber);
+
+// define Field isLength(); prototype
+function _Field_isLength(len, type){
+ var len = parseInt(_param(arguments[0], 10, "number"), 10);
+ var type = _param(arguments[1], "numeric");
+
+ // check to make sure the phone is the correct length
+ if( !_isLength(this.value, len, type) ){
+ this.error = "The " + this.description + " field must include " + len + " " + type + " characters.";
+ }
+}
+_addValidator("isLength", _Field_isLength);
+
+// define Field isSSN(); prototype
+function _Field_isSSN(){
+ var description = (this.description == this.name.toLowerCase()) ? "social security" : this.description;
+
+ // check to make sure the phone is the correct length
+ if( !_isLength(this.value, 9) ){
+ this.error = "The " + description + " field must include 9 digits.";
+ }
+}
+_addValidator("isSSN", _Field_isSSN);
+
+
+// define Field isState(); prototype
+function _Field_isState(){
+ // check to make sure the phone is the correct length
+ if( _getState(this.value) == null ){
+ this.error = "The " + this.description + " field must contain a valid 2-digit state abbreviation.";
+ }
+}
+_addValidator("isState", _Field_isState);
+
+// define Field isZipCode(); prototype
+function _Field_isZipCode(){
+ var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description;
+
+ iZipLen = _stripInvalidChars(this.value).length;
+
+ // check to make sure the zip code is the correct length
+ if( iZipLen != 5 && iZipLen != 9 ){
+ this.error = "The " + description + " field must contain either 5 or 9 digits.";
+ }
+}
+_addValidator("isZipCode", _Field_isZipCode);
+
+// define Field isFormat(); prototype
+function _Field_isFormat(mask, type){
+ var mask = _param(arguments[0]);
+ var type = _param(arguments[1], "numeric").toLowerCase();
+ var strErrorMsg = "";
+
+ var strMaskLC = mask.toLowerCase();
+ // define quick masks
+ if( strMaskLC == "ssn" ){
+ mask = "xxx-xx-xxxx";
+ type = "numeric";
+ var description = (this.description == this.name.toLowerCase()) ? "social security number" : this.description;
+ strErrorMsg = "The " + description + " field must contain 9 digits and \nshould be in the format: " + mask;
+
+ } else if( (strMaskLC == "phone") || (strMaskLC == "phone1") ){
+ mask = "(xxx) xxx-xxxx";
+ type = "numeric";
+ var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description;
+ strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask;
+
+ } else if( strMaskLC == "phone2" ){
+ mask = "xxx-xxx-xxxx";
+ type = "numeric";
+ var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description;
+ strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask;
+
+ } else if( strMaskLC == "phone3" ){
+ mask = "xxx/xxx-xxxx";
+ type = "numeric";
+ var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description;
+ strErrorMsg = "The " + description + " field must contain 10 digits and \nshould be in the format: " + mask;
+
+ } else if( strMaskLC == "phone7" ){
+ mask = "xxx-xxxx";
+ type = "numeric";
+ var description = (this.description == this.name.toLowerCase()) ? "phone number" : this.description;
+ strErrorMsg = "The " + description + " field must contain 7 digits and \nshould be in the format: " + mask;
+
+ } else if( strMaskLC == "zip" ){
+ if( _stripInvalidChars(this.value).length < 6 ){
+ mask = "xxxxx";
+ } else {
+ mask = "xxxxx-xxxx";
+ }
+ type = "numeric";
+ var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description;
+ strErrorMsg = "The " + description + " field should contain either 5 or 9 digits \nand be in the format: xxxxx or xxxxx-xxxx";
+
+ } else if( strMaskLC == "zip5" ){
+ mask = "xxxxx";
+ type = "numeric";
+ var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description;
+ strErrorMsg = "The " + description + " field should contain 5 digits \nand be in the format: " + mask;
+
+ } else if( strMaskLC == "zip9" ){
+ mask = "xxxxx-xxxx";
+ type = "numeric";
+ var description = (this.description == this.name.toLowerCase()) ? "zip code" : this.description;
+ strErrorMsg = "The " + description + " field should contain 9 digits \nand be in the format: " + mask;
+ } else {
+ var description = this.description;
+ }
+
+ var string = _stripInvalidChars(this.value, type);
+ var masklen = _stripInvalidChars(mask, "x").length;
+
+ // check to make sure the string contains the correct number of characters
+ if( string.length != masklen && this.value.length > 0){
+ if( strErrorMsg.length == 0 ) strErrorMsg = "This field requires at least " + masklen + " valid characters. Please \nmake sure to enter the value in the format: \n " + mask + "\n(where 'x' is a valid character.)";
+ this.error = strErrorMsg;
+
+ // else re-format the string as defined by the mask
+ } else if( string.length == masklen ){
+ // find the position of all non "X" characters
+ var stcMask = new Object();
+ var lc = mask.toLowerCase();
+ // loop through the string an make sure each character is an valid character
+ for( var i=0; i < mask.length; i++ ){
+ if( lc.charAt(i) != "x" ) stcMask[i] = mask.charAt(i);
+ }
+
+ // put all the non-"X" characters back into the parsed string
+ var iLastChar = 0;
+ var newString = "";
+ var i = 0;
+ for( var pos in stcMask ){
+ pos = parseInt(pos, 10);
+ if( pos > iLastChar ){
+ newString += string.substring(iLastChar, pos-i) + stcMask[pos];
+ iLastChar = pos-i;
+ } else {
+ newString += stcMask[pos];
+ }
+ i++;
+ }
+ if( i == 0 ){
+ newString = string;
+ } else {
+ newString += string.substring(iLastChar);
+ }
+
+ // set the value of the field to the new string--make sure not to perform the onBlur event
+ this.setValue(newString, true, false);
+ }
+}
+_addValidator("isFormat", _Field_isFormat);
+
+// define Field isLengthGT(); prototype
+function _Field_isLengthGT(len){
+ if( this.obj.value.length <= len){
+ this.error = "The " + this.description + " field must be greater than " + len + " characters.";
+ }
+}
+_addValidator("isLengthGT", _Field_isLengthGT);
+
+// define Field isLengthLT(); prototype
+function _Field_isLengthLT(len){
+ if( this.obj.value.length >= len){
+ this.error = "The " + this.description + " field must be less than " + len + " characters.";
+ }
+}
+_addValidator("isLengthLT", _Field_isLengthLT);
+
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation_addon.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation_addon.js
new file mode 100755
index 0000000..3ac7f29
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/validation_addon.js
@@ -0,0 +1,34 @@
+/******************************************************************************
+ qForm JSAPI: Add-on Validation Library
+
+ Author: Dan G. Switzer, II
+ Build: 100
+******************************************************************************/
+qFormAPI.packages.validation = true;
+
+// [start] validation routine
+function _f_isAtLeastOne(_f){
+ var sFields = this.name + ((typeof _f == "string") ? "," + _removeSpaces(_f) : "");
+ var aFields = sFields.split(","), v = new Array(), d = new Array(), x = ",";
+
+ for( var i=0; i < aFields.length; i++ ){
+ if( !this.qForm[aFields[i]] ) return alert("The field name \"" + aFields[i] + "\" does not exist.");
+ // store the value in an array
+ v[v.length] = this.qForm[aFields[i]].getValue();
+ // if the field name is already in the list, don't add it
+ if( x.indexOf("," + aFields[i] + ",") == -1 ){
+ d[d.length] = this.qForm[aFields[i]].description;
+ x += aFields[i] + ",";
+ }
+ }
+
+ // if all of the form fields has empty lengths, then throw
+ // an error message to the page
+ if( v.join("").length == 0 ){
+ this.error = "At least one of the following fields is required:\n " + d.join(", ");
+ for( i=0; i < aFields.length; i++ ){
+ if( qFormAPI.useErrorColorCoding && this.qForm[aFields[i]].obj.style ) this.qForm[aFields[i]].obj.style[qFormAPI.styleAttribute] = qFormAPI.errorColor;
+ }
+ }
+}
+_addValidator("isAtLeastOne", _f_isAtLeastOne, true);
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/wddx.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/wddx.js
new file mode 100755
index 0000000..a3f0a12
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms/wddx.js
@@ -0,0 +1,102 @@
+/******************************************************************************
+ qForm JSAPI: WDDX Mod Library
+
+ Author: Dan G. Switzer, II
+ Build: 101
+******************************************************************************/
+
+/******************************************************************************
+ Required Functions
+******************************************************************************/
+function __serializeStruct(struct){
+ // open packet
+ var aWDDX = new Array("");
+ for( var key in struct ) aWDDX[aWDDX.length] = "" + __wddxValue(struct[key]) + "";
+ // close packet
+ aWDDX[aWDDX.length] = "";
+ return aWDDX.join("");
+}
+
+
+function __wddxValue(str){
+ var aValue = new Array();
+ for( var i=0; i < str.length; ++i) aValue[aValue.length] = _encoding.table[str.charAt(i)];
+ return aValue.join("");
+}
+
+function _wddx_Encoding(){
+ // Encoding table for strings (CDATA)
+ var et = new Array();
+
+ // numbers to characters table
+ var n2c = new Array();
+
+ for( var i=0; i < 256; ++i ){
+ // build a character from octal code
+ var d1 = Math.floor(i/64);
+ var d2 = Math.floor((i%64)/8);
+ var d3 = i%8;
+ var c = eval("\"\\" + d1.toString(10) + d2.toString(10) + d3.toString(10) + "\"");
+
+ // modify character-code conversion tables
+ n2c[i] = c;
+
+ // modify encoding table
+ if( i < 32 && i != 9 && i != 10 && i != 13 ){
+ // control characters that are not tabs, newlines, and carriage returns
+
+ // create a two-character hex code representation
+ var hex = i.toString(16);
+ if( hex.length == 1 ) hex = "0" + hex;
+
+ et[n2c[i]] = "";
+
+ } else if( i < 128 ){
+ // low characters that are not special control characters
+ et[n2c[i]] = n2c[i];
+ } else {
+ // high characters
+ et[n2c[i]] = "" + i.toString(16) + ";";
+ }
+ }
+
+ // special escapes for CDATA encoding
+ et["<"] = "<";
+ et[">"] = ">";
+ et["&"] = "&";
+
+ this.table = et;
+}
+_encoding = new _wddx_Encoding();
+
+
+/******************************************************************************
+ qForm Methods
+******************************************************************************/
+function _a_serialize(exclude){
+ // if you need to reset the default values of the fields
+ var lstExclude = (arguments.length > 0) ? "," + _removeSpaces(arguments[0]) + "," : "";
+ struct = new Object();
+ stcAllFields = qFormAPI.getFields();
+ // loop through form elements
+ for( key in stcAllFields ){
+ if( lstExclude.indexOf("," + key + ",") == -1 ) struct[key] = stcAllFields[key];
+ }
+ // create & return the serialized object
+ return __serializeStruct(struct);
+}
+_a.prototype.serialize = _a_serialize;
+
+// define qForm serialize(); prototype
+function _q_serialize(exclude){
+ // if you need to reset the default values of the fields
+ var lstExclude = (arguments.length > 0) ? "," + _removeSpaces(arguments[0]) + "," : "";
+ struct = new Object();
+ // loop through form elements
+ for( var j=0; j < this._fields.length; j++ ){
+ if( lstExclude.indexOf("," + this._fields[j] + ",") == -1 ) struct[this._fields[j]] = this[this._fields[j]].getValue();
+ }
+ // create & return the serialized object
+ return __serializeStruct(struct);
+}
+qForm.prototype.serialize = _q_serialize;
diff --git a/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms_init.js b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms_init.js
new file mode 100755
index 0000000..68ccea8
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/qforms/javascripts/qforms_init.js
@@ -0,0 +1,9 @@
+// set the path to the qForms directory
+qFormAPI.setLibraryPath("/bundles/qforms/javascripts/");
+// this loads all the default libraries
+qFormAPI.include("*");
+
+
+/********************************************************************
+ load custom validation scripts here
+********************************************************************/
diff --git a/vendor/plugins/bundled_resource/bundles/stateful_form.rb b/vendor/plugins/bundled_resource/bundles/stateful_form.rb
new file mode 100644
index 0000000..5e1a66b
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/stateful_form.rb
@@ -0,0 +1,74 @@
+# == Usage ==
+# require_bundle :stateful_form
+#
+#
+# == Full Stateful Form Documentation ==
+# ?
+
+module BundledResource::StatefulForm
+ def bundle
+ require_javascript "/bundles/stateful_form/javascripts/stateful_form"
+ end
+
+ module Helper
+ # Returns true or false depending on the visible state of the element.
+ def visible_state(element_id)
+ (params[:visible_state][element_id.to_sym] == '1') rescue visible_state_get_default(element_id)
+ end
+
+ # Returns 'block' or 'none' depending on the visible state of the element.
+ def css_visible_state(element_id)
+ visible_state(element_id) ? 'block' : 'none'
+ end
+
+ # Outputs a hidden_state_tag for each element that has a known state. The element's
+ # state is 'known' if,
+ # a) the params[:visible_state] hash includes the element's id, or
+ # b) the element's default state has been stored via a previous all to visible_state_set_default
+ #
+ # Call perpetuate_visible_states once within of a view's form tags to perpetuate the
+ # visible (or hidden) states of sections of the form.
+ def perpetuate_visible_states
+ buffer = ""
+ known_states = []
+ known_states |= params[:visible_state].keys if params[:visible_state]
+ known_states |= @visible_state_default.keys if @visible_state_default
+ for element_id in known_states
+ logger.warn "(Stateful Form) Perpetuating visible state #{element_id}: #{visible_state(element_id)}"
+ buffer << hidden_state_tag(element_id)
+ end
+ buffer
+ end
+
+ def visible_state_set_default(element_id, state = true)
+ @visible_state_default ||= Hash.new
+ @visible_state_default[element_id.to_sym] = state
+ end
+
+ def visible_state_get_default(element_id)
+ @visible_state_default ||= Hash.new
+ @visible_state_default[element_id.to_sym] || false
+ end
+
+ # Show an html input tag (type='hidden') to preserve state information
+ # about some other element's visible state. Calling hidden_state_tag
+ # multiple times on the same element_id will result in only ONE tag being
+ # written unless force_write is set to true.
+ # If +boolean_value+ is nil, the the current visible_state will be used.
+ def hidden_state_tag(element_id, boolean_value = nil, force_write = false)
+ @visible_state_perpetuated ||= []
+ if boolean_value.nil?
+ value = visible_state(element_id) ? 1 : 0
+ else
+ value = boolean_value ? 1 : 0
+ end
+ buffer = ""
+ if force_write or !@visible_state_perpetuated.include?(element_id)
+ buffer = hidden_field_tag("visible_state[#{element_id}]", value, :id => "visible_state_#{element_id}")
+ @visible_state_perpetuated << element_id
+ end
+ buffer
+ end
+ end
+end
+
diff --git a/vendor/plugins/bundled_resource/bundles/stateful_form/javascripts/stateful_form.js b/vendor/plugins/bundled_resource/bundles/stateful_form/javascripts/stateful_form.js
new file mode 100644
index 0000000..00cb433
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/stateful_form/javascripts/stateful_form.js
@@ -0,0 +1,18 @@
+function remember_visible_state_of(element) {
+ var visible_state = (Element.visible(element) ? 1 : 0);
+ // Allow optional second argument to override the state
+ if (arguments.length > 1) visible_state = (arguments[1] == true || arguments[1] == 1) ? 1 : 0;
+
+ // See if the element has a hidden field where we will store its state
+ element = $(element);
+ var hidden_field_id = 'visible_state_' + element.id
+ var hidden_field = $(hidden_field_id);
+
+ if (hidden_field)
+ hidden_field.value = visible_state;
+ else
+ new Insertion.Top(element,
+ "");
+}
diff --git a/vendor/plugins/bundled_resource/bundles/textarea.rb b/vendor/plugins/bundled_resource/bundles/textarea.rb
new file mode 100644
index 0000000..cc0550b
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/textarea.rb
@@ -0,0 +1,8 @@
+module BundledResource::Textarea
+ def bundle
+ require_stylesheet "/bundles/textarea/stylesheets/resizable"
+
+ require_javascript "/bundles/textarea/javascripts/resizable"
+ require_javascript "/bundles/textarea/javascripts/wordcount"
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/bundled_resource/bundles/textarea/images/areaLess.png b/vendor/plugins/bundled_resource/bundles/textarea/images/areaLess.png
new file mode 100755
index 0000000..bae913c
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/textarea/images/areaLess.png differ
diff --git a/vendor/plugins/bundled_resource/bundles/textarea/images/areaMore.png b/vendor/plugins/bundled_resource/bundles/textarea/images/areaMore.png
new file mode 100755
index 0000000..910e18f
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/textarea/images/areaMore.png differ
diff --git a/vendor/plugins/bundled_resource/bundles/textarea/images/fontDown.png b/vendor/plugins/bundled_resource/bundles/textarea/images/fontDown.png
new file mode 100755
index 0000000..48dfe73
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/textarea/images/fontDown.png differ
diff --git a/vendor/plugins/bundled_resource/bundles/textarea/images/fontUp.png b/vendor/plugins/bundled_resource/bundles/textarea/images/fontUp.png
new file mode 100755
index 0000000..13523bf
Binary files /dev/null and b/vendor/plugins/bundled_resource/bundles/textarea/images/fontUp.png differ
diff --git a/vendor/plugins/bundled_resource/bundles/textarea/javascripts/resizable.js b/vendor/plugins/bundled_resource/bundles/textarea/javascripts/resizable.js
new file mode 100755
index 0000000..ef49338
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/textarea/javascripts/resizable.js
@@ -0,0 +1,130 @@
+document.getElementsByClassName = function (needle)
+{
+ var my_array = document.getElementsByTagName("*");
+ var retvalue = new Array();
+ var i;
+ var j;
+
+ for (i = 0, j = 0; i < my_array.length; i++)
+ {
+ var c = " " + my_array[i].className + " ";
+ if (c.indexOf(" " + needle + " ") != -1)
+ retvalue[j++] = my_array[i];
+ }
+ return retvalue;
+}
+
+function addEvent(obj, evType, fn)
+{
+ if (obj.addEventListener)
+ {
+ obj.addEventListener(evType, fn, true);
+ return true;
+ }
+ else if (obj.attachEvent)
+ {
+ var r = obj.attachEvent("on"+evType, fn);
+ return r;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+addEvent(window, 'load', function()
+{
+ var resizables = document.getElementsByClassName('resizable');
+ var i, ta, wrapper, bigger, smaller, biggerFont, smallerFont, biggerSpan, smallerSpan, biggerFontSpan, smallerFontSpan;
+ for (i=0; i 30)
+ this.resizer.style.height = (parseInt(this.resizer.style.height) - 30) + "px";
+ return false;
+ }
+
+ biggerFont.onclick = function()
+ {
+ if (this.resizer.style.fontSize == '')
+ this.resizer.style.fontSize = '1em';
+
+ this.resizer.style.fontSize = (parseFloat(this.resizer.style.fontSize) + 0.1) + "em";
+
+ return false;
+ }
+
+ smallerFont.onclick = function()
+ {
+ if (this.resizer.style.fontSize == '')
+ this.resizer.style.fontSize = '1em';
+
+ if ((parseFloat(this.resizer.style.fontSize) - 0.1) > 0.5)
+ this.resizer.style.fontSize = (parseFloat(this.resizer.style.fontSize) - 0.1) + "em";
+
+ return false;
+ }
+
+ wrapper.appendChild(bigger);
+ wrapper.appendChild(smaller);
+ wrapper.appendChild(biggerFont);
+ wrapper.appendChild(smallerFont);
+ }
+});
diff --git a/vendor/plugins/bundled_resource/bundles/textarea/javascripts/wordcount.js b/vendor/plugins/bundled_resource/bundles/textarea/javascripts/wordcount.js
new file mode 100755
index 0000000..ef6c32f
--- /dev/null
+++ b/vendor/plugins/bundled_resource/bundles/textarea/javascripts/wordcount.js
@@ -0,0 +1,29 @@
+
+addEvent(window, 'load', function()
+{
+ var counts = document.getElementsByClassName('wordcount');
+ var i, countHolder;
+
+ for (i=0; i 0 and (ENV['RAILS_BUNDLES'].nil? or ENV['RAILS_BUNDLES'].upcase == 'YES')
+ # We need to load each ruby file in the bundles directory. Each file should
+ # contain a module within the BundledResource module, e.g.
+ # module BundledResource::Qforms
+ # def bundle
+ # require_javascript '...' # require whatever resources are necessary
+ # end
+ # end
+ #
+ # Note that the single 'bundle' method can have optional parameters if needed.
+
+ require 'require_resource'
+ require 'bundled_resource'
+
+ RAILS_DEFAULT_LOGGER.debug "Resource Bundles: #{bundle_files.size}"
+ bundle_files.each do |filename|
+ name = File.basename(filename)
+ name = name[0...-3] if name =~ /\.rb$/
+
+ RAILS_DEFAULT_LOGGER.debug "Requiring Bundle\n #{filename}"
+ require filename
+
+ # Rename the generic 'bundle' method in to something that doesn't conflict with
+ # the other module method names.
+ bundle_module = BundledResource.const_get(name.to_s.camelize)
+ bundle_module.module_eval "alias bundle_#{name} bundle"
+ bundle_module.send :undef_method, :bundle
+
+ # Then include the bundle module in to the base module, so that the methods will
+ # be available inside ActionView::Base
+ BundledResource.send(:include, bundle_module)
+
+ # Check for optional Controller module
+ if bundle_module.const_defined? 'Controller'
+ controller_addon = bundle_module.const_get('Controller')
+ RAILS_DEFAULT_LOGGER.debug "Including #{name} bundle's Controller module"
+ ActionController::Base.send(:include, controller_addon)
+ end
+
+ # Check for optional Helper module
+ if bundle_module.const_defined? 'Helper'
+ helper_addon = bundle_module.const_get('Helper')
+ RAILS_DEFAULT_LOGGER.debug "Including #{name} bundle's Helper module"
+ ActionView::Base.send(:include, helper_addon)
+ end
+ end
+
+ public_bundle_directory = File.expand_path(File.join(RAILS_ROOT, "public", "bundles"))
+
+ BundledResource.create_public_bundle_directory(public_bundle_directory)
+ BundledResource.copy_bundles(bundle_directory_root, public_bundle_directory)
+
+ ActionView::Base.send(:include, RequireResource)
+ ActionView::Base.send(:include, BundledResource)
+end
\ No newline at end of file
diff --git a/vendor/plugins/bundled_resource/lib/bundled_resource.rb b/vendor/plugins/bundled_resource/lib/bundled_resource.rb
new file mode 100644
index 0000000..eb96b32
--- /dev/null
+++ b/vendor/plugins/bundled_resource/lib/bundled_resource.rb
@@ -0,0 +1,49 @@
+require 'fileutils'
+
+BUNDLE_RESOURCE_README = <<-END
+Files in this directory are automatically generated from your Rails bundles.
+They are copied from the directories of each bundle into this directory
+each time Rails starts (server, console). Any edits you make will NOT
+persist across the next server restart; instead you should edit the files within
+the bundle directory itself (e.g. vendor/plugins/bundle_resource/bundles/*).
+END
+
+module BundledResource
+ def require_bundle(name, *args)
+ method = "bundle_#{name}"
+ send(method, *args)
+ end
+
+ def self.copy_bundles(bundle_directory_root, public_bundle_directory)
+ RAILS_DEFAULT_LOGGER.debug "Copying bundles from\n #{bundle_directory_root} to\n #{public_bundle_directory}"
+ Dir.new(bundle_directory_root).each do |entry|
+ full_path = File.join(bundle_directory_root, entry)
+ if File.directory?(full_path)
+ unless ['.', '..', '.svn'].include?(entry)
+ BundledResource.copy_files(full_path, public_bundle_directory)
+ end
+ end
+ end
+ RAILS_DEFAULT_LOGGER.debug "Done copying bundles."
+ end
+
+ def self.create_public_bundle_directory(public_bundle_dir)
+ if File.exists?(public_bundle_dir)
+ RAILS_DEFAULT_LOGGER.debug "Removing bundles directory\n #{public_bundle_dir}"
+ FileUtils.rm_rf(public_bundle_dir)
+ end
+
+ RAILS_DEFAULT_LOGGER.debug "Creating bundles directory\n #{public_bundle_dir}"
+ # create the public/engines directory, with a warning message in it.
+ FileUtils.mkdir(public_bundle_dir)
+ File.open(File.join(public_bundle_dir, "README"), "w") do |f|
+ f.puts BUNDLE_RESOURCE_README
+ end
+ end
+
+ # Replicates the subdirectories under the bundle's directory into
+ # the application's public directory.
+ def self.copy_files(source, destination)
+ FileUtils.cp_r(source, destination) rescue nil
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/bundled_resource/lib/require_resource.rb b/vendor/plugins/bundled_resource/lib/require_resource.rb
new file mode 100644
index 0000000..95948b8
--- /dev/null
+++ b/vendor/plugins/bundled_resource/lib/require_resource.rb
@@ -0,0 +1,124 @@
+# RequireResource v.1.4 by Duane Johnson
+#
+# Makes inclusion of javascript and stylesheet resources easier via automatic or explicit
+# calls. e.g. require_javascript 'popup' is an explicit call.
+#
+# The simplest way to make use of this functionality is to add
+# <%= resource_tags %>
+# to your layout (usually in the section). This will automatically add
+# bundle support to your application, as well as enable simple javascript and stylesheet
+# dependencies for your views.
+#
+# Note that this can easily be turned in to a helper on its own.
+module RequireResource
+ mattr_accessor :path_prefix
+
+ # Write out all javascripts & stylesheets, including default javascripts (unless :defaults => false)
+ def resource_tags(options = {})
+ options = {:auto => true, :defaults => true}.update(options)
+ require_defaults if options[:defaults]
+ stylesheet_auto_link_tags(:auto => options[:auto]) +
+ javascript_auto_include_tags(:auto => options[:auto])
+ end
+
+ # Write out the tags themselves based on the array of stylesheets to be included
+ def stylesheet_auto_link_tags(options = {})
+ options = {:auto => true}.update(options)
+ ensure_resource_is_initialized(:stylesheet)
+ autorequire(:stylesheet) if options[:auto]
+ @stylesheets.uniq.inject("") do |buffer, css|
+ buffer << stylesheet_link_tag(css) + "\n "
+ end
+ end
+
+ # Write out the
+ #
+ # engine_javascript "my_engine", "another_file", "one_more" =>
+ #
+ #
+ #
+ #
+ # Any options supplied as a Hash as the last argument will be processed as in
+ # javascript_include_tag.
+ #
+ def engine_javascript(engine_name, *sources)
+ javascript_include_tag(*convert_public_sources(engine_name, :javascript, sources))
+ end
+
+ # Returns a image tag based on the parameters passed to it
+ # Required option is option[:engine] in order to correctly idenfity the correct engine location
+ #
+ # engine_image 'rails-engines.png', :engine => 'my_engine', :alt => 'My Engine' =>
+ # loading from engine '#{engine.name}'")
+ rails_pre_engines_require_or_load(engine_file_name)
+ found = true
+ end
+ end
+ end
+ end
+
+ # finally, load any application-specific controller classes using the 'proper'
+ # rails load mechanism, EXCEPT when we're testing engines and could load this file
+ # from an engine
+ rails_pre_engines_require_or_load(file_name) unless Engines.disable_app_code_mixing && found
+ end
+
+ def rails_1_0_require_or_load(file_name)
+ file_name = $1 if file_name =~ /^(.*)\.rb$/
+
+ Engines.log.debug "Engines 1.0.0 require_or_load '#{file_name}'"
+
+ # if the file_name ends in "_controller" or "_controller.rb", strip all
+ # path information out of it except for module context, and load it. Ditto
+ # for helpers.
+ found = if file_name =~ /_controller(.rb)?$/
+ require_engine_files(file_name, 'controller')
+ elsif file_name =~ /_helper(.rb)?$/ # any other files we can do this with?
+ require_engine_files(file_name, 'helper')
+ end
+
+ # finally, load any application-specific controller classes using the 'proper'
+ # rails load mechanism, EXCEPT when we're testing engines and could load this file
+ # from an engine
+ Engines.log.debug("--> loading from application: '#{file_name}'")
+ rails_pre_engines_require_or_load(file_name) unless Engines.disable_app_code_mixing && found
+ Engines.log.debug("--> Done loading.")
+ end
+
+ # Load the given file (which should be a path to be matched from the root of each
+ # engine) from all active engines which have that file.
+ # NOTE! this method automagically strips file_name up to and including the first
+ # instance of '/app/controller'. This should correspond to the app/controller folder
+ # under RAILS_ROOT. However, if you have your Rails application residing under a
+ # path which includes /app/controller anyway, such as:
+ #
+ # /home/username/app/controller/my_web_application # == RAILS_ROOT
+ #
+ # then you might have trouble. Sorry, just please don't have your web application
+ # running under a path like that.
+ def require_engine_files(file_name, type='')
+ found = false
+ Engines.log.debug "requiring #{type} file '#{file_name}'"
+ processed_file_name = file_name.gsub(/[\w\W\/\.]*app\/#{type}s\//, '')
+ Engines.log.debug "--> rewrote to '#{processed_file_name}'"
+ Engines.each(:load_order) do |engine|
+ engine_file_name = File.join(engine.root, 'app', "#{type}s", processed_file_name)
+ engine_file_name += '.rb' unless ! load? || engine_file_name[-3..-1] == '.rb'
+ Engines.log.debug "--> checking '#{engine.name}' for #{engine_file_name}"
+ if File.exist?(engine_file_name) ||
+ (engine_file_name[-3..-1] != '.rb' && File.exist?(engine_file_name + '.rb'))
+ Engines.log.debug "--> found, loading from engine '#{engine.name}'"
+ rails_pre_engines_require_or_load(engine_file_name)
+ found = true
+ end
+ end
+ found
+ end
+end
+
+
+# We only need to deal with LoadingModules in Rails 1.0.0
+if Rails::VERSION::STRING =~ /^1.0/ && !Engines.config(:edge)
+ module ::Dependencies
+ class RootLoadingModule < LoadingModule
+ # hack to allow adding to the load paths within the Rails Dependencies mechanism.
+ # this allows Engine classes to be unloaded and loaded along with standard
+ # Rails application classes.
+ def add_path(path)
+ @load_paths << (path.kind_of?(ConstantLoadPath) ? path : ConstantLoadPath.new(path))
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/engines/lib/engines/migration_extensions.rb b/vendor/plugins/engines/lib/engines/migration_extensions.rb
new file mode 100644
index 0000000..0653261
--- /dev/null
+++ b/vendor/plugins/engines/lib/engines/migration_extensions.rb
@@ -0,0 +1,53 @@
+#require 'active_record/connection_adapters/abstract/schema_statements'
+
+module ::ActiveRecord::ConnectionAdapters::SchemaStatements
+ alias :old_initialize_schema_information :initialize_schema_information
+ def initialize_schema_information
+ # create the normal schema stuff
+ old_initialize_schema_information
+
+ # create the engines schema stuff.
+ begin
+ execute "CREATE TABLE #{engine_schema_info_table_name} (engine_name #{type_to_sql(:string)}, version #{type_to_sql(:integer)})"
+ rescue ActiveRecord::StatementInvalid
+ # Schema has been initialized
+ end
+ end
+
+ def engine_schema_info_table_name
+ ActiveRecord::Base.wrapped_table_name "engine_schema_info"
+ end
+end
+
+
+require 'breakpoint'
+module ::Engines
+ class EngineMigrator < ActiveRecord::Migrator
+
+ # We need to be able to set the 'current' engine being migrated.
+ cattr_accessor :current_engine
+
+ class << self
+
+ def schema_info_table_name
+ ActiveRecord::Base.wrapped_table_name "engine_schema_info"
+ end
+
+ def current_version
+ result = ActiveRecord::Base.connection.select_one("SELECT version FROM #{schema_info_table_name} WHERE engine_name = '#{current_engine.name}'")
+ if result
+ result["version"].to_i
+ else
+ # There probably isn't an entry for this engine in the migration info table.
+ # We need to create that entry, and set the version to 0
+ ActiveRecord::Base.connection.execute("INSERT INTO #{schema_info_table_name} (version, engine_name) VALUES (0,'#{current_engine.name}')")
+ 0
+ end
+ end
+ end
+
+ def set_schema_version(version)
+ ActiveRecord::Base.connection.update("UPDATE #{self.class.schema_info_table_name} SET version = #{down? ? version.to_i - 1 : version.to_i} WHERE engine_name = '#{self.current_engine.name}'")
+ end
+ end
+end
diff --git a/vendor/plugins/engines/lib/engines/routing_extensions.rb b/vendor/plugins/engines/lib/engines/routing_extensions.rb
new file mode 100644
index 0000000..00f6b63
--- /dev/null
+++ b/vendor/plugins/engines/lib/engines/routing_extensions.rb
@@ -0,0 +1,28 @@
+module ActionController
+ module Routing
+
+ class << self
+ # This holds the global list of valid controller paths
+ attr_accessor :controller_paths
+ end
+
+ class ControllerComponent
+ class << self
+ protected
+ def safe_load_paths #:nodoc:
+ if defined?(RAILS_ROOT)
+ paths = $LOAD_PATH.select do |base|
+ base = File.expand_path(base)
+ # Check that the path matches one of the allowed paths in controller_paths
+ base.match(/^#{ActionController::Routing.controller_paths.map { |p| File.expand_path(p) } * '|'}/)
+ end
+ Engines.log.debug "Engines safe_load_paths: #{paths.inspect}"
+ paths
+ else
+ $LOAD_PATH
+ end
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/engines/lib/engines/ruby_extensions.rb b/vendor/plugins/engines/lib/engines/ruby_extensions.rb
new file mode 100644
index 0000000..189ca46
--- /dev/null
+++ b/vendor/plugins/engines/lib/engines/ruby_extensions.rb
@@ -0,0 +1,113 @@
+#--
+# Add these methods to the top-level module so that they are available in all
+# modules, etc
+#++
+class ::Module
+ # Defines a constant within a module/class ONLY if that constant does
+ # not already exist.
+ #
+ # This can be used to implement defaults in plugins/engines/libraries, e.g.
+ # if a plugin module exists:
+ # module MyPlugin
+ # default_constant :MyDefault, "the_default_value"
+ # end
+ #
+ # then developers can override this default by defining that constant at
+ # some point *before* the module/plugin gets loaded (such as environment.rb)
+ def default_constant(name, value)
+ if !(name.is_a?(String) or name.is_a?(Symbol))
+ raise "Cannot use a #{name.class.name} ['#{name}'] object as a constant name"
+ end
+ if !self.const_defined?(name)
+ self.class_eval("#{name} = #{value.inspect}")
+ end
+ end
+
+ # A mechanism for defining configuration of Modules. With this
+ # mechanism, default values for configuration can be provided within shareable
+ # code, and the end user can customise the configuration without having to
+ # provide all values.
+ #
+ # Example:
+ #
+ # module MyModule
+ # config :param_one, "some value"
+ # config :param_two, 12345
+ # end
+ #
+ # Those values can now be accessed by the following method
+ #
+ # MyModule.config :param_one
+ # => "some value"
+ # MyModule.config :param_two
+ # => 12345
+ #
+ # ... or, if you have overrriden the method 'config'
+ #
+ # MyModule::CONFIG[:param_one]
+ # => "some value"
+ # MyModule::CONFIG[:param_two]
+ # => 12345
+ #
+ # Once a value is stored in the configuration, it will not be altered
+ # by subsequent assignments, unless a special flag is given:
+ #
+ # (later on in your code, most likely in another file)
+ # module MyModule
+ # config :param_one, "another value"
+ # config :param_two, 98765, :force
+ # end
+ #
+ # The configuration is now:
+ #
+ # MyModule.config :param_one
+ # => "some value" # not changed
+ # MyModule.config :param_two
+ # => 98765
+ #
+ # Configuration values can also be given as a Hash:
+ #
+ # MyModule.config :param1 => 'value1', :param2 => 'value2'
+ #
+ # Setting of these values can also be forced:
+ #
+ # MyModule.config :param1 => 'value3', :param2 => 'value4', :force => true
+ #
+ # A value of anything other than false or nil given for the :force key will
+ # result in the new values *always* being set.
+ def config(*args)
+
+ raise "config expects at least one argument" if args.empty?
+
+ # extract the arguments
+ if args[0].is_a?(Hash)
+ override = args[0][:force]
+ args[0].delete(:force)
+ args[0].each { |key, value| _handle_config(key, value, override)}
+ else
+ _handle_config(*args)
+ end
+ end
+
+ private
+ # Actually set the config values
+ def _handle_config(name, value=nil, override=false)
+ if !self.const_defined?("CONFIG")
+ self.class_eval("CONFIG = {}")
+ end
+
+ if value != nil
+ if override or self::CONFIG[name] == nil
+ self::CONFIG[name] = value
+ end
+ else
+ # if we pass an array of config keys to config(),
+ # get the array of values back
+ if name.is_a? Array
+ name.map { |c| self::CONFIG[c] }
+ else
+ self::CONFIG[name]
+ end
+ end
+ end
+end
diff --git a/vendor/plugins/engines/lib/engines/testing_extensions.rb b/vendor/plugins/engines/lib/engines/testing_extensions.rb
new file mode 100644
index 0000000..6d9755d
--- /dev/null
+++ b/vendor/plugins/engines/lib/engines/testing_extensions.rb
@@ -0,0 +1,327 @@
+# The Engines testing extensions enable developers to load fixtures into specific
+# tables irrespective of the name of the fixtures file. This work is heavily based on
+# patches made by Duane Johnson (canadaduane), viewable at
+# http://dev.rubyonrails.org/ticket/1911
+#
+# Engine developers should supply fixture files in the /test/fixtures directory
+# as normal. Within their tests, they should load the fixtures using the 'fixture' command
+# (rather than the normal 'fixtures' command). For example:
+#
+# class UserTest < Test::Unit::TestCase
+# fixture :users, :table_name => LoginEngine.config(:user_table), :class_name => "User"
+#
+# ...
+#
+# This will ensure that the fixtures/users.yml file will get loaded into the correct
+# table, and will use the correct model object class.
+
+
+
+# A FixtureGroup is a set of fixtures identified by a name. Normally, this is the name of the
+# corresponding fixture filename. For example, when you declare the use of fixtures in a
+# TestUnit class, like so:
+# fixtures :users
+# you are creating a FixtureGroup whose name is 'users', and whose defaults are set such that the
+# +class_name+, +file_name+ and +table_name+ are guessed from the FixtureGroup's name.
+class FixtureGroup
+ attr_accessor :table_name, :class_name, :connection
+ attr_reader :group_name, :file_name
+
+ def initialize(file_name, optional_names = {})
+ self.file_name = file_name
+ self.group_name = optional_names[:group_name] || file_name
+ if optional_names[:table_name]
+ self.table_name = optional_names[:table_name]
+ self.class_name = optional_names[:class_name] || Inflector.classify(@table_name.to_s.gsub('.','_'))
+ elsif optional_names[:class_name]
+ self.class_name = optional_names[:class_name]
+ if Object.const_defined?(@class_name)
+ model_class = Object.const_get(@class_name)
+ self.table_name = ActiveRecord::Base.table_name_prefix + model_class.table_name + ActiveRecord::Base.table_name_suffix
+ end
+ end
+
+ # In case either :table_name or :class_name was not set:
+ self.table_name ||= ActiveRecord::Base.table_name_prefix + @group_name.to_s + ActiveRecord::Base.table_name_suffix
+ self.class_name ||= Inflector.classify(@table_name.to_s.gsub('.','_'))
+ end
+
+ def file_name=(name)
+ @file_name = name.to_s
+ end
+
+ def group_name=(name)
+ @group_name = name.to_sym
+ end
+
+ def class_file_name
+ Inflector.underscore(@class_name)
+ end
+
+ # Instantiate an array of FixtureGroup objects from an array of strings (table_names)
+ def self.array_from_names(names)
+ names.collect { |n| FixtureGroup.new(n) }
+ end
+
+ def hash
+ @group_name.hash
+ end
+
+ def eql?(other)
+ @group_name.eql? other.group_name
+ end
+end
+
+class Fixtures < YAML::Omap
+
+ def self.instantiate_fixtures(object, fixture_group_name, fixtures, load_instances=true)
+ old_logger_level = ActiveRecord::Base.logger.level
+ ActiveRecord::Base.logger.level = Logger::ERROR
+
+ # table_name.to_s.gsub('.','_') replaced by 'fixture_group_name'
+ object.instance_variable_set "@#{fixture_group_name}", fixtures
+ if load_instances
+ ActiveRecord::Base.silence do
+ fixtures.each do |name, fixture|
+ begin
+ if model = fixture.find
+ object.instance_variable_set "@#{name}", model
+ end
+ rescue FixtureClassNotFound
+ # Let's hope the developer has included it himself
+ end
+ end
+ end
+ end
+
+ ActiveRecord::Base.logger.level = old_logger_level
+ end
+
+ # this doesn't really need to be overridden...
+ def self.instantiate_all_loaded_fixtures(object, load_instances=true)
+ all_loaded_fixtures.each do |fixture_group_name, fixtures|
+ Fixtures.instantiate_fixtures(object, fixture_group_name, fixtures, load_instances)
+ end
+ end
+
+ def self.create_fixtures(fixtures_directory, *fixture_groups)
+ connection = block_given? ? yield : ActiveRecord::Base.connection
+ fixture_groups.flatten!
+
+ # Backwards compatibility: Allow an array of table names to be passed in, but just use them
+ # to create an array of FixtureGroup objects
+ if not fixture_groups.empty? and fixture_groups.first.is_a?(String)
+ fixture_groups = FixtureGroup.array_from_names(fixture_groups)
+ end
+
+ ActiveRecord::Base.silence do
+ fixtures_map = {}
+ fixtures = fixture_groups.map do |group|
+ fixtures_map[group.group_name] = Fixtures.new(connection, fixtures_directory, group)
+ end
+ # Make sure all refs to all_loaded_fixtures use group_name as hash index, not table_name
+ all_loaded_fixtures.merge! fixtures_map
+
+ connection.transaction do
+ fixtures.reverse.each { |fixture| fixture.delete_existing_fixtures }
+ fixtures.each { |fixture| fixture.insert_fixtures }
+
+ # Cap primary key sequences to max(pk).
+ if connection.respond_to?(:reset_pk_sequence!)
+ fixture_groups.each do |fg|
+ connection.reset_pk_sequence!(fg.table_name)
+ end
+ end
+ end
+
+ return fixtures.size > 1 ? fixtures : fixtures.first
+ end
+ end
+
+
+ attr_accessor :connection, :fixtures_directory, :file_filter
+ attr_accessor :fixture_group
+
+ def initialize(connection, fixtures_directory, fixture_group, file_filter = DEFAULT_FILTER_RE)
+ @connection, @fixtures_directory = connection, fixtures_directory
+ @fixture_group = fixture_group
+ @file_filter = file_filter
+ read_fixture_files
+ end
+
+ def delete_existing_fixtures
+ @connection.delete "DELETE FROM #{@fixture_group.table_name}", 'Fixture Delete'
+ end
+
+ def insert_fixtures
+ values.each do |fixture|
+ @connection.execute "INSERT INTO #{@fixture_group.table_name} (#{fixture.key_list}) VALUES (#{fixture.value_list})", 'Fixture Insert'
+ end
+ end
+
+ private
+ def read_fixture_files
+ if File.file?(yaml_file_path)
+ read_yaml_fixture_files
+ elsif File.file?(csv_file_path)
+ read_csv_fixture_files
+ elsif File.file?(deprecated_yaml_file_path)
+ raise Fixture::FormatError, ".yml extension required: rename #{deprecated_yaml_file_path} to #{yaml_file_path}"
+ elsif File.directory?(single_file_fixtures_path)
+ read_standard_fixture_files
+ else
+ raise Fixture::FixtureError, "Couldn't find a yaml, csv or standard file to load at #{@fixtures_directory} (#{@fixture_group.file_name})."
+ end
+ end
+
+ def read_yaml_fixture_files
+ # YAML fixtures
+ begin
+ if yaml = YAML::load(erb_render(IO.read(yaml_file_path)))
+ yaml = yaml.value if yaml.respond_to?(:type_id) and yaml.respond_to?(:value)
+ yaml.each do |name, data|
+ self[name] = Fixture.new(data, fixture_group.class_name)
+ end
+ end
+ rescue Exception=>boom
+ raise Fixture::FormatError, "a YAML error occured parsing #{yaml_file_path}. Please note that YAML must be consistently indented using spaces. Tabs are not allowed. Please have a look at http://www.yaml.org/faq.html\nThe exact error was:\n #{boom.class}: #{boom}"
+ end
+ end
+
+ def read_csv_fixture_files
+ # CSV fixtures
+ reader = CSV::Reader.create(erb_render(IO.read(csv_file_path)))
+ header = reader.shift
+ i = 0
+ reader.each do |row|
+ data = {}
+ row.each_with_index { |cell, j| data[header[j].to_s.strip] = cell.to_s.strip }
+ self["#{@fixture_group.class_file_name}_#{i+=1}"]= Fixture.new(data, @fixture_group.class_name)
+ end
+ end
+
+ def read_standard_fixture_files
+ # Standard fixtures
+ path = File.join(@fixtures_directory, @fixture_group.file_name)
+ Dir.entries(path).each do |file|
+ path = File.join(@fixtures_directory, @fixture_group.file_name, file)
+ if File.file?(path) and file !~ @file_filter
+ self[file] = Fixture.new(path, @fixture_group.class_name)
+ end
+ end
+ end
+
+ def yaml_file_path
+ fixture_path_with_extension ".yml"
+ end
+
+ def deprecated_yaml_file_path
+ fixture_path_with_extension ".yaml"
+ end
+
+ def csv_file_path
+ fixture_path_with_extension ".csv"
+ end
+
+ def single_file_fixtures_path
+ fixture_path_with_extension ""
+ end
+
+ def fixture_path_with_extension(ext)
+ File.join(@fixtures_directory, @fixture_group.file_name + ext)
+ end
+
+ def erb_render(fixture_content)
+ ERB.new(fixture_content).result
+ end
+
+end
+
+module Test #:nodoc:
+ module Unit #:nodoc:
+ class TestCase #:nodoc:
+ cattr_accessor :fixtures_directory
+ class_inheritable_accessor :fixture_groups
+ class_inheritable_accessor :fixture_table_names
+ class_inheritable_accessor :use_transactional_fixtures
+ class_inheritable_accessor :use_instantiated_fixtures # true, false, or :no_instances
+ class_inheritable_accessor :pre_loaded_fixtures
+
+ self.fixture_groups = []
+ self.use_transactional_fixtures = false
+ self.use_instantiated_fixtures = true
+ self.pre_loaded_fixtures = false
+
+ @@already_loaded_fixtures = {}
+
+ # Backwards compatibility
+ def self.fixture_path=(path); self.fixtures_directory = path; end
+ def self.fixture_path; self.fixtures_directory; end
+ def fixture_group_names; fixture_groups.collect { |g| g.group_name }; end
+ def fixture_table_names; fixture_group_names; end
+
+ def self.fixture(file_name, options = {})
+ self.fixture_groups |= [FixtureGroup.new(file_name, options)]
+ require_fixture_classes
+ setup_fixture_accessors
+ end
+
+ def self.fixtures(*file_names)
+ self.fixture_groups |= FixtureGroup.array_from_names(file_names.flatten)
+ require_fixture_classes
+ setup_fixture_accessors
+ end
+
+ def self.require_fixture_classes(fixture_groups_override = nil)
+ (fixture_groups_override || fixture_groups).each do |group|
+ begin
+ require group.class_file_name
+ rescue LoadError
+ # Let's hope the developer has included it himself
+ end
+ end
+ end
+
+ def self.setup_fixture_accessors(fixture_groups_override=nil)
+ (fixture_groups_override || fixture_groups).each do |group|
+ define_method(group.group_name) do |fixture, *optionals|
+ force_reload = optionals.shift
+ @fixture_cache[group.group_name] ||= Hash.new
+ @fixture_cache[group.group_name][fixture] = nil if force_reload
+ @fixture_cache[group.group_name][fixture] ||= @loaded_fixtures[group.group_name][fixture.to_s].find
+ end
+ end
+ end
+
+ private
+ def load_fixtures
+ @loaded_fixtures = {}
+ fixtures = Fixtures.create_fixtures(fixtures_directory, fixture_groups)
+ unless fixtures.nil?
+ if fixtures.instance_of?(Fixtures)
+ @loaded_fixtures[fixtures.fixture_group.group_name] = fixtures
+ else
+ fixtures.each { |f| @loaded_fixtures[f.fixture_group.group_name] = f }
+ end
+ end
+ end
+
+ def instantiate_fixtures
+ if pre_loaded_fixtures
+ raise RuntimeError, 'Load fixtures before instantiating them.' if Fixtures.all_loaded_fixtures.empty?
+ unless @@required_fixture_classes
+ groups = Fixtures.all_loaded_fixtures.values.collect { |f| f.group_name }
+ self.class.require_fixture_classes groups
+ @@required_fixture_classes = true
+ end
+ Fixtures.instantiate_all_loaded_fixtures(self, load_instances?)
+ else
+ raise RuntimeError, 'Load fixtures before instantiating them.' if @loaded_fixtures.nil?
+ @loaded_fixtures.each do |fixture_group_name, fixtures|
+ Fixtures.instantiate_fixtures(self, fixture_group_name, fixtures, load_instances?)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/plugins/engines/tasks/deprecated_engines.rake b/vendor/plugins/engines/tasks/deprecated_engines.rake
new file mode 100644
index 0000000..33e2a28
--- /dev/null
+++ b/vendor/plugins/engines/tasks/deprecated_engines.rake
@@ -0,0 +1,7 @@
+# Old-style engines rake tasks.
+# NOTE: THESE ARE DEPRICATED! PLEASE USE THE NEW STYLE!
+
+task :engine_info => "engines:info"
+task :engine_migrate => "db:migrate:engines"
+task :enginedoc => "doc:engines"
+task :load_plugin_fixtures => "db:fixtures:engines:load"
\ No newline at end of file
diff --git a/vendor/plugins/engines/tasks/engines.rake b/vendor/plugins/engines/tasks/engines.rake
new file mode 100644
index 0000000..21bb763
--- /dev/null
+++ b/vendor/plugins/engines/tasks/engines.rake
@@ -0,0 +1,176 @@
+module Engines
+ module RakeTasks
+ def self.all_engines
+ # An engine is informally defined as any subdirectory in vendor/plugins
+ # which ends in '_engine', '_bundle', or contains an 'init_engine.rb' file.
+ engine_base_dirs = ['vendor/plugins']
+ # The engine root may be different; if possible try to include
+ # those directories too
+ if Engines.const_defined?(:CONFIG)
+ engine_base_dirs << Engines::CONFIG[:root]
+ end
+ engine_base_dirs.map! {|d| [d + '/*_engine/*',
+ d + '/*_bundle/*',
+ d + '/*/init_engine.rb']}.flatten!
+ engine_dirs = FileList.new(*engine_base_dirs)
+ engine_dirs.map do |engine|
+ File.basename(File.dirname(engine))
+ end.uniq
+ end
+ end
+end
+
+
+namespace :engines do
+ desc "Display version information about active engines"
+ task :info => :environment do
+ if ENV["ENGINE"]
+ e = Engines.get(ENV["ENGINE"])
+ header = "Details for engine '#{e.name}':"
+ puts header
+ puts "-" * header.length
+ puts "Version: #{e.version}"
+ puts "Details: #{e.info}"
+ else
+ puts "Engines plugin: #{Engines.version}"
+ Engines.each do |e|
+ puts "#{e.name}: #{e.version}"
+ end
+ end
+ end
+end
+
+namespace :db do
+ namespace :fixtures do
+ namespace :engines do
+
+ desc "Load plugin/engine fixtures into the current environment's database."
+ task :load => :environment do
+ require 'active_record/fixtures'
+ ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
+ plugin = ENV['ENGINE'] || '*'
+ Dir.glob(File.join(RAILS_ROOT, 'vendor', 'plugins', plugin, 'test', 'fixtures', '*.yml')).each do |fixture_file|
+ Fixtures.create_fixtures(File.dirname(fixture_file), File.basename(fixture_file, '.*'))
+ end
+ end
+
+ end
+ end
+
+
+ namespace :migrate do
+
+ desc "Migrate all engines. Target specific version with VERSION=x, specific engine with ENGINE=x"
+ task :engines => :environment do
+ engines_to_migrate = ENV["ENGINE"] ? [Engines.get(ENV["ENGINE"])].compact : Engines.active
+ if engines_to_migrate.empty?
+ puts "Couldn't find an engine called '#{ENV["ENGINE"]}'"
+ else
+ if ENV["VERSION"] && !ENV["ENGINE"]
+ # ignore the VERSION, since it makes no sense in this context; we wouldn't
+ # want to revert ALL engines to the same version because of a misttype
+ puts "Ignoring the given version (#{ENV["VERSION"]})."
+ puts "To control individual engine versions, use the ENGINE= argument"
+ else
+ engines_to_migrate.each do |engine|
+ Engines::EngineMigrator.current_engine = engine
+ migration_directory = File.join(engine.root, 'db', 'migrate')
+ if File.exist?(migration_directory)
+ puts "Migrating engine '#{engine.name}'"
+ Engines::EngineMigrator.migrate(migration_directory, ENV["VERSION"] ? ENV["VERSION"].to_i : nil)
+ else
+ puts "The db/migrate directory for engine '#{engine.name}' appears to be missing."
+ puts "Should be: #{migration_directory}"
+ end
+ end
+ if ActiveRecord::Base.schema_format == :ruby && !engines_to_migrate.empty?
+ Rake::Task[:db_schema_dump].invoke
+ end
+ end
+ end
+ end
+
+ namespace :engines do
+ Engines::RakeTasks.all_engines.each do |engine_name|
+ desc "Migrate the '#{engine_name}' engine. Target specific version with VERSION=x"
+ task engine_name => :environment do
+ ENV['ENGINE'] = engine_name; Rake::Task['db:migrate:engines'].invoke
+ end
+ end
+ end
+
+ end
+end
+
+
+# this is just a rip-off from the plugin stuff in railties/lib/tasks/documentation.rake,
+# because the default plugindoc stuff doesn't support subdirectories like /app or
+# /component.
+namespace :doc do
+
+ desc "Generate documation for all installed engines"
+ task :engines => Engines::RakeTasks.all_engines.map {|engine| "doc:engines:#{engine}"}
+
+ namespace :engines do
+ # Define doc tasks for each engine
+ Engines::RakeTasks.all_engines.each do |engine_name|
+ desc "Generation documentation for the '#{engine_name}' engine"
+ task engine_name => :environment do
+ engine_base = "vendor/plugins/#{engine_name}"
+ options = []
+ files = Rake::FileList.new
+ options << "-o doc/plugins/#{engine_name}"
+ options << "--title '#{engine_name.titlecase} Documentation'"
+ options << '--line-numbers --inline-source'
+ options << '--all' #Â include protected methods
+ options << '-T html'
+
+ files.include("#{engine_base}/lib/**/*.rb")
+ files.include("#{engine_base}/app/**/*.rb") # include the app directory
+ files.include("#{engine_base}/components/**/*.rb") # include the components directory
+ if File.exists?("#{engine_base}/README")
+ files.include("#{engine_base}/README")
+ options << "--main '#{engine_base}/README'"
+ end
+ files.include("#{engine_base}/CHANGELOG") if File.exists?("#{engine_base}/CHANGELOG")
+
+ options << files.to_s
+
+ sh %(rdoc #{options * ' '})
+ end
+ end
+ end
+end
+
+namespace :test do
+ desc "Run the engine tests in vendor/plugins/**/test (or specify with ENGINE=name)"
+ # NOTE: we're using the Rails 1.0 non-namespaced task here, just to maintain
+ # compatibility with Rails 1.0
+ # TODO: make this work with Engines.config(:root)
+
+ namespace :engines do
+ Engines::RakeTasks.all_engines.each do |engine_name|
+ desc "Run the engine tests for '#{engine_name}'"
+ Rake::TestTask.new(engine_name => :prepare_test_database) do |t|
+ t.libs << 'test'
+ t.pattern = "vendor/plugins/#{engine_name}/test/**/*_test.rb"
+ t.verbose = true
+ end
+ end
+ end
+
+ Rake::TestTask.new(:engines => [:warn_about_multiple_engines_testing, :prepare_test_database]) do |t|
+ t.libs << "test"
+ engines = ENV['ENGINE'] || '**'
+ t.pattern = "vendor/plugins/#{engines}/test/**/*_test.rb"
+ t.verbose = true
+ end
+
+ task :warn_about_multiple_engines_testing do
+ puts %{-~============== A Moste Polite Warninge ==================~-
+You may experience issues testing multiple engines at once.
+Please test engines individual for the moment.
+-~===============( ... as you were ... )===================~-
+}
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/engines/test/action_view_extensions_test.rb b/vendor/plugins/engines/test/action_view_extensions_test.rb
new file mode 100644
index 0000000..8951785
--- /dev/null
+++ b/vendor/plugins/engines/test/action_view_extensions_test.rb
@@ -0,0 +1,9 @@
+ENV["RAILS_ENV"] = "test"
+require File.expand_path(File.dirname(__FILE__) + '/../../../../config/environment')
+require 'test_help'
+
+class ActionViewExtensionsTest < Test::Unit::TestCase
+ def test_stylesheet_path
+ assert true
+ end
+end
\ No newline at end of file
diff --git a/vendor/plugins/engines/test/ruby_extensions_test.rb b/vendor/plugins/engines/test/ruby_extensions_test.rb
new file mode 100644
index 0000000..c904f67
--- /dev/null
+++ b/vendor/plugins/engines/test/ruby_extensions_test.rb
@@ -0,0 +1,115 @@
+ENV["RAILS_ENV"] = "test"
+require File.expand_path(File.dirname(__FILE__) + '/../../../../config/environment')
+require 'test_help'
+
+class RubyExtensionsTest < Test::Unit::TestCase
+
+ def setup
+ # create the module to be used for config testing
+ eval "module TestModule end"
+ end
+
+ def teardown
+ # remove the TestModule constant from our scope
+ self.class.class_eval { remove_const :TestModule }
+ end
+
+
+ #
+ # Module.config
+ #
+
+ def test_config_no_arguments
+ assert_raise(RuntimeError) { TestModule.config }
+ end
+
+ def test_config_array_arguments
+ TestModule.config :monkey, 123
+ assert_equal(123, TestModule.config(:monkey))
+ end
+
+ def test_config_hash_arguments
+ TestModule.config :monkey => 123, :donkey => 456
+ assert_equal(123, TestModule.config(:monkey))
+ assert_equal(456, TestModule.config(:donkey))
+ end
+
+ def test_config_can_store_hash
+ TestModule.config :hash, :key1 => 'val1', :key2 => 'val2'
+ assert_equal({:key1 => 'val1', :key2 => 'val2'}, TestModule.config(:hash))
+ end
+
+ def test_config_cant_overwrite_existing_config_values
+ TestModule.config :monkey, 123
+ assert_equal(123, TestModule.config(:monkey))
+ TestModule.config :monkey, 456
+ assert_equal(123, TestModule.config(:monkey))
+
+ TestModule.config :monkey => 456
+ assert_equal(123, TestModule.config(:monkey))
+
+ # in this case, the resulting Hash only has {:baboon => "goodbye!"} - that's Ruby, users beware.
+ TestModule.config :baboon => "hello", :baboon => "goodbye!"
+ assert_equal("goodbye!", TestModule.config(:baboon))
+ end
+
+ def test_config_force_new_value
+ TestModule.config :monkey, 123
+ TestModule.config :man, 321
+ assert_equal(123, TestModule.config(:monkey))
+ assert_equal(321, TestModule.config(:man))
+ TestModule.config :monkey, 456, :force
+ assert_equal(456, TestModule.config(:monkey))
+ TestModule.config :monkey => 456, :man => 654, :force => true
+ assert_equal(456, TestModule.config(:monkey))
+ assert_equal(654, TestModule.config(:man))
+ TestModule.config :monkey => 789, :man => 987, :force => false
+ assert_equal(456, TestModule.config(:monkey))
+ assert_equal(654, TestModule.config(:man))
+
+ TestModule.config :hash, :key1 => 'val1', :key2 => 'val2'
+ assert_equal({:key1 => 'val1', :key2 => 'val2'}, TestModule.config(:hash))
+ TestModule.config :hash => {:key1 => 'val3', :key2 => 'val4'}, :force => true
+ assert_equal({:key1 => 'val3', :key2 => 'val4'}, TestModule.config(:hash))
+ end
+
+ # this test is somewhat redundant, but it might be an idea to havbe it explictly anyway
+ def test_config_get_values
+ TestModule.config :monkey, 123
+ assert_equal(123, TestModule.config(:monkey))
+ end
+
+ def test_config_get_multiple_values
+ TestModule.config :monkey, 123
+ TestModule.config :donkey, 456
+ assert_equal([123, 456], TestModule.config([:monkey, :donkey]))
+ end
+
+
+ #
+ # Module.default_constant
+ #
+
+ def test_default_constant_set
+ TestModule.default_constant :Monkey, 123
+ assert_equal(123, TestModule::Monkey)
+ TestModule.default_constant "Hello", 456
+ assert_equal(456, TestModule::Hello)
+ end
+
+ def test_default_constant_cannot_set_again
+ TestModule.default_constant :Monkey, 789
+ assert_equal(789, TestModule::Monkey)
+ TestModule.default_constant :Monkey, 456
+ assert_equal(789, TestModule::Monkey)
+ end
+
+ def test_default_constant_bad_arguments
+ # constant names must be Captialized
+ assert_raise(NameError) { TestModule.default_constant :lowercase_name, 123 }
+
+ # constant names should be given as Strings or Symbols
+ assert_raise(RuntimeError) { TestModule.default_constant 123, 456 }
+ assert_raise(RuntimeError) { TestModule.default_constant Object.new, 456 }
+ end
+end
diff --git a/vendor/plugins/exception_notification/README b/vendor/plugins/exception_notification/README
new file mode 100644
index 0000000..9a47c41
--- /dev/null
+++ b/vendor/plugins/exception_notification/README
@@ -0,0 +1,111 @@
+= Exception Notifier Plugin for Rails
+
+The Exception Notifier plugin provides a mailer object and a default set of
+templates for sending email notifications when errors occur in a Rails
+application. The plugin is configurable, allowing programmers to specify:
+
+* the sender address of the email
+* the recipient addresses
+* the text used to prefix the subject line
+
+The email includes information about the current request, session, and
+environment, and also gives a backtrace of the exception.
+
+== Usage
+
+First, include the ExceptionNotifiable mixin in whichever controller you want
+to generate error emails (typically ApplicationController):
+
+ class ApplicationController < ActionController::Base
+ include ExceptionNotifiable
+ ...
+ end
+
+Then, specify the email recipients in your environment:
+
+ ExceptionNotifier.exception_recipients = %w(joe@schmoe.com bill@schmoe.com)
+
+And that's it! The defaults take care of the rest.
+
+== Configuration
+
+You can tweak other values to your liking, as well. In your environment file,
+just set any or all of the following values:
+
+ # defaults to exception.notifier@default.com
+ ExceptionNotifier.sender_address =
+ %("Application Error" )
+
+ # defaults to "[ERROR] "
+ ExceptionNotifier.email_prefix = "[APP] "
+
+Email notifications will only occur when the IP address is determined not to
+be local. You can specify certain addresses to always be local so that you'll
+get a detailed error instead of the generic error page. You do this in your
+controller (or even per-controller):
+
+ consider_local "64.72.18.143", "14.17.21.25"
+
+You can specify subnet masks as well, so that all matching addresses are
+considered local:
+
+ consider_local "64.72.18.143/24"
+
+The address "127.0.0.1" is always considered local. If you want to completely
+reset the list of all addresses (for instance, if you wanted "127.0.0.1" to
+NOT be considered local), you can simply do, somewhere in your controller:
+
+ local_addresses.clear
+
+== Customization
+
+By default, the notification email includes four parts: request, session,
+environment, and backtrace (in that order). You can customize how each of those
+sections are rendered by placing a partial named for that part in your
+app/views/exception_notifier directory (e.g., _session.rhtml). Each partial has
+access to the following variables:
+
+* @controller: the controller that caused the error
+* @request: the current request object
+* @exception: the exception that was raised
+* @host: the name of the host that made the request
+* @backtrace: a sanitized version of the exception's backtrace
+* @rails_root: a sanitized version of RAILS_ROOT
+* @data: a hash of optional data values that were passed to the notifier
+* @sections: the array of sections to include in the email
+
+You can reorder the sections, or exclude sections completely, by altering the
+ExceptionNotifier.sections variable. You can even add new sections that
+describe application-specific data--just add the section's name to the list
+(whereever you'd like), and define the corresponding partial. Then, if your
+new section requires information that isn't available by default, make sure
+it is made available to the email using the exception_data macro:
+
+ class ApplicationController < ActionController::Base
+ ...
+ protected
+ exception_data :additional_data
+
+ def additional_data
+ { :document => @document,
+ :person => @person }
+ end
+ ...
+ end
+
+In the above case, @document and @person would be made available to the email
+renderer, allowing your new section(s) to access and display them. See the
+existing sections defined by the plugin for examples of how to write your own.
+
+== Advanced Customization
+
+By default, the email notifier will only notify on critical errors. For
+ActiveRecord::RecordNotFound and ActionController::UnknownAction, it will
+simply render the contents of your public/404.html file. Other exceptions
+will render public/500.html and will send the email notification. If you want
+to use different rules for the notification, you will need to implement your
+own rescue_action_in_public method. You can look at the default implementation
+in ExceptionNotifiable for an example of how to go about that.
+
+
+Copyright (c) 2005 Jamis Buck, released under the MIT license
\ No newline at end of file
diff --git a/vendor/plugins/exception_notification/init.rb b/vendor/plugins/exception_notification/init.rb
new file mode 100644
index 0000000..b39bd95
--- /dev/null
+++ b/vendor/plugins/exception_notification/init.rb
@@ -0,0 +1 @@
+require "action_mailer"
diff --git a/vendor/plugins/exception_notification/lib/exception_notifiable.rb b/vendor/plugins/exception_notification/lib/exception_notifiable.rb
new file mode 100644
index 0000000..60875d2
--- /dev/null
+++ b/vendor/plugins/exception_notification/lib/exception_notifiable.rb
@@ -0,0 +1,90 @@
+require 'ipaddr'
+
+# Copyright (c) 2005 Jamis Buck
+#
+# 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.
+module ExceptionNotifiable
+ def self.included(target)
+ target.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ def consider_local(*args)
+ local_addresses.concat(args.flatten.map { |a| IPAddr.new(a) })
+ end
+
+ def local_addresses
+ addresses = read_inheritable_attribute(:local_addresses)
+ unless addresses
+ addresses = [IPAddr.new("127.0.0.1")]
+ write_inheritable_attribute(:local_addresses, addresses)
+ end
+ addresses
+ end
+
+ def exception_data(deliverer=self)
+ if deliverer == self
+ read_inheritable_attribute(:exception_data)
+ else
+ write_inheritable_attribute(:exception_data, deliverer)
+ end
+ end
+ end
+
+ def local_request?
+ remote = IPAddr.new(request.remote_ip)
+ !self.class.local_addresses.detect { |addr| addr.include?(remote) }.nil?
+ end
+
+ def render_404
+ respond_to do |type|
+ type.html { render :file => "#{RAILS_ROOT}/public/404.html", :status => "404 Not Found" }
+ type.all { render :nothing => true, :status => "404 Not Found" }
+ end
+ end
+
+ def render_500
+ respond_to do |type|
+ type.html { render :file => "#{RAILS_ROOT}/public/500.html", :status => "500 Error" }
+ type.all { render :nothing => true, :status => "500 Error" }
+ end
+ end
+
+ def rescue_action_in_public(exception)
+ case exception
+ when ActiveRecord::RecordNotFound, ActionController::UnknownController, ActionController::UnknownAction
+ render_404
+
+ else
+ render_500
+
+ deliverer = self.class.exception_data
+ data = case deliverer
+ when nil then {}
+ when Symbol then send(deliverer)
+ when Proc then deliverer.call(self)
+ end
+
+ ExceptionNotifier.deliver_exception_notification(exception, self,
+ request, data)
+ end
+ end
+
+end
diff --git a/vendor/plugins/exception_notification/lib/exception_notifier.rb b/vendor/plugins/exception_notification/lib/exception_notifier.rb
new file mode 100644
index 0000000..0518c3d
--- /dev/null
+++ b/vendor/plugins/exception_notification/lib/exception_notifier.rb
@@ -0,0 +1,67 @@
+require 'pathname'
+
+# Copyright (c) 2005 Jamis Buck
+#
+# 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.
+class ExceptionNotifier < ActionMailer::Base
+ @@sender_address = %("Exception Notifier" )
+ cattr_accessor :sender_address
+
+ @@exception_recipients = []
+ cattr_accessor :exception_recipients
+
+ @@email_prefix = "[ERROR] "
+ cattr_accessor :email_prefix
+
+ @@sections = %w(request session environment backtrace)
+ cattr_accessor :sections
+
+ def self.reloadable?; false; end
+
+ def exception_notification(exception, controller, request, data={})
+ subject "#{email_prefix}#{controller.controller_name}##{controller.action_name} (#{exception.class}) #{exception.message.inspect}"
+
+ recipients exception_recipients
+ from sender_address
+
+ body data.merge({ :controller => controller, :request => request,
+ :exception => exception, :host => request.env["HTTP_HOST"],
+ :backtrace => sanitize_backtrace(exception.backtrace),
+ :rails_root => rails_root, :data => data,
+ :sections => sections })
+ end
+
+ def template_root
+ "#{File.dirname(__FILE__)}/../views"
+ end
+
+ private
+
+ def sanitize_backtrace(trace)
+ re = Regexp.new(/^#{Regexp.escape(rails_root)}/)
+ trace.map { |line| Pathname.new(line.gsub(re, "[RAILS_ROOT]")).cleanpath.to_s }
+ end
+
+ def rails_root
+ return @rails_root if @rails_root
+ @rails_root = Pathname.new(RAILS_ROOT).cleanpath.to_s
+ end
+
+end
diff --git a/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb b/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb
new file mode 100644
index 0000000..4e4c48b
--- /dev/null
+++ b/vendor/plugins/exception_notification/lib/exception_notifier_helper.rb
@@ -0,0 +1,63 @@
+require 'pp'
+
+# Copyright (c) 2005 Jamis Buck
+#
+# 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.
+module ExceptionNotifierHelper
+ VIEW_PATH = "views/exception_notifier"
+ APP_PATH = "#{RAILS_ROOT}/app/#{VIEW_PATH}"
+
+ def render_section(section)
+ RAILS_DEFAULT_LOGGER.info("rendering section #{section.inspect}")
+ summary = render_overridable(section).strip
+ unless summary.blank?
+ title = render_overridable(:title, :locals => { :title => section }).strip
+ "#{title}\n\n#{summary.gsub(/^/, " ")}\n\n"
+ end
+ end
+
+ def render_overridable(partial, options={})
+ if File.exist?(path = "#{APP_PATH}/_#{partial}.rhtml")
+ render(options.merge(:file => path, :use_full_path => false))
+ elsif File.exist?(path = "#{File.dirname(__FILE__)}/../#{VIEW_PATH}/_#{partial}.rhtml")
+ render(options.merge(:file => path, :use_full_path => false))
+ else
+ ""
+ end
+ end
+
+ def inspect_model_object(model, locals={})
+ render_overridable(:inspect_model,
+ :locals => { :inspect_model => model,
+ :show_instance_variables => true,
+ :show_attributes => true }.merge(locals))
+ end
+
+ def inspect_value(value)
+ len = 512
+ result = object_to_yaml(value).gsub(/\n/, "\n ").strip
+ result = result[0,len] + "... (#{result.length-len} bytes more)" if result.length > len+20
+ result
+ end
+
+ def object_to_yaml(object)
+ object.to_yaml.sub(/^---\s*/m, "")
+ end
+end
diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml
new file mode 100644
index 0000000..7d13ba0
--- /dev/null
+++ b/vendor/plugins/exception_notification/views/exception_notifier/_backtrace.rhtml
@@ -0,0 +1 @@
+<%= @backtrace.join "\n" %>
diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml
new file mode 100644
index 0000000..f426b54
--- /dev/null
+++ b/vendor/plugins/exception_notification/views/exception_notifier/_environment.rhtml
@@ -0,0 +1,7 @@
+<% max = @request.env.keys.max { |a,b| a.length <=> b.length } -%>
+<% @request.env.keys.sort.each do |key| -%>
+* <%= "%*-s: %s" % [max.length, key, @request.env[key].to_s.strip] %>
+<% end -%>
+
+* Process: <%= $$ %>
+* Server : <%= `hostname -s`.chomp %>
diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml
new file mode 100644
index 0000000..82f9526
--- /dev/null
+++ b/vendor/plugins/exception_notification/views/exception_notifier/_inspect_model.rhtml
@@ -0,0 +1,16 @@
+<% if show_attributes -%>
+[attributes]
+<% attrs = inspect_model.attributes -%>
+<% max = attrs.keys.max { |a,b| a.length <=> b.length } -%>
+<% attrs.keys.sort.each do |attr| -%>
+* <%= "%*-s: %s" % [max.length, attr, object_to_yaml(attrs[attr]).gsub(/\n/, "\n ")] %>
+<% end -%>
+<% end -%>
+
+<% if show_instance_variables -%>
+[instance variables]
+<% inspect_model.instance_variables.sort.each do |variable| -%>
+<%- next if variable == "@attributes" -%>
+* <%= variable %>: <%= inspect_value(inspect_model.instance_variable_get(variable)) %>
+<% end -%>
+<% end -%>
diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml
new file mode 100644
index 0000000..6df4520
--- /dev/null
+++ b/vendor/plugins/exception_notification/views/exception_notifier/_request.rhtml
@@ -0,0 +1,3 @@
+* URL: <%= @request.protocol %><%= @host %><%= @request.request_uri %>
+* Parameters: <%= @request.parameters.inspect %>
+* Rails root: <%= @rails_root %>
diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml
new file mode 100644
index 0000000..8bee57d
--- /dev/null
+++ b/vendor/plugins/exception_notification/views/exception_notifier/_session.rhtml
@@ -0,0 +1,4 @@
+<% for variable in @request.session.instance_variables -%>
+<% next if variable =~ /^@db/ -%>
+* <%= variable %>: <%= PP.pp(@request.session.instance_variable_get(variable),"").gsub(/\n/, "\n ").strip %>
+<% end -%>
diff --git a/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml
new file mode 100644
index 0000000..1ed5a3f
--- /dev/null
+++ b/vendor/plugins/exception_notification/views/exception_notifier/_title.rhtml
@@ -0,0 +1,3 @@
+-------------------------------
+<%= title.to_s.humanize %>:
+-------------------------------
diff --git a/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml b/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml
new file mode 100644
index 0000000..ec30c4a
--- /dev/null
+++ b/vendor/plugins/exception_notification/views/exception_notifier/exception_notification.rhtml
@@ -0,0 +1,6 @@
+A <%= @exception.class %> occurred in <%= @controller.controller_name %>#<%= @controller.action_name %>:
+
+ <%= @exception.message %>
+ <%= @backtrace.first %>
+
+<%= @sections.map { |section| render_section(section) }.join %>
diff --git a/vendor/plugins/login_engine/CHANGELOG b/vendor/plugins/login_engine/CHANGELOG
new file mode 100644
index 0000000..3764ef2
--- /dev/null
+++ b/vendor/plugins/login_engine/CHANGELOG
@@ -0,0 +1,22 @@
+= v1.0.2
+* Added version
+* Removed errant requires no longer needed (murray.steele@gmail.com, Ticket #156, Ticket #157, Ticket #158)
+# Removed documentation/rake tasks that refer the schema.rb (Ticket #155)
+# Verified cannot be assigned via URL parameters. If more security is required, users should override the signup action itself (Ticket #169)
+# Minor view/flash message cleanup
+# Authentication by token now respects primary key prefixes (Ticket #140)
+
+= v1.0.1
+ * Added CHANGELOG
+ * Changed wording for when password forgotten to 'reset', rather than 'retrieve'. (snowblink@gmail.com)
+ * Fixed new location of engines testing extensions. (lazyatom@gmail.com)
+ * Removed schema.db from Login Engine; migrations should be used instead. (snowblink@gmail.com)
+ * Updated User Controller tests to parse the user_id and email out of the URL in the email body. (snowblink@gmail.com)
+ * Ticket #89 (lazyatom@gmail.com) User creation halts the after_save callback chain.
+ * Ticket #97 (dcorbin@machturtle.com) The forgotten_password view generates invalid HTML
+ * Ticket #112 (segabor@gmail.com) Authentication system will break even on successful login
+ * Added simple email validation to the User model. (snowblink@gmail.com)
+ This should also take care of the unit test failures detailed in Ticket #114 (morris@wolfman.com)
+ * Ticket #118 (augustz@augustz.com) SVN source for login_engine not found
+ * Ticket #119 (Goynang) Unit tests for engines fail after default install
+ * Ticket #126 (lazyatom@gmail.com) Add install.rb to login engine
diff --git a/vendor/plugins/login_engine/README b/vendor/plugins/login_engine/README
new file mode 100644
index 0000000..69b2981
--- /dev/null
+++ b/vendor/plugins/login_engine/README
@@ -0,0 +1,344 @@
+= Before we start
+
+This is a Rails Engine version of the Salted Login Generator, a most excellent login system which is sufficient for most simple cases. For the most part, this code has not been altered from its generator form, with the following notable exceptions
+
+* Localization has been removed.
+* The 'welcome' page has been changed to the 'home' page
+* A few new functions have been thrown in
+* It's... uh.... a Rails Engine now ;-)
+
+However, what I'm trying to say is that 99.9999% of the credit for this should go to Joe Hosteny, Tobias Luetke (xal) and the folks that worked on the original Salted Login generator code. I've just wrapped it into something runnable with the Rails Engine system.
+
+Please also bear in mind that this is a work in progress, and things like testing are wildly up in the air... but they will fall into place very soon. And now, on with the show.
+
+
+= Installation
+
+Installing the Login Engine is fairly simple.
+
+Your options are:
+ 1. Install as a rails plugin:
+ $ script/plugin install login_engine
+ 2. Use svn:externals
+ $ svn propedit svn:externals vendor/plugins
+
+ You can choose to use the latest stable release:
+ login_engine http://svn.rails-engines.org/plugins/login_engine
+
+ Or a tagged release (recommended for releases of your code):
+ login_engine http://svn.rails-engines.org/logine_engine/tags/
+
+There are a few configuration steps that you'll need to take to get everything running smoothly. Listed below are the changes to your application you will need to make.
+
+=== Setup your Rails application
+
+Edit your database.yml, most importantly! You might also want to move public/index.html out of the way, and set up some default routes in config/routes.rb.
+
+=== Add configuration and start engine
+
+Add the following to the bottom of environment.rb:
+
+ module LoginEngine
+ config :salt, "your-salt-here"
+ end
+
+ Engines.start :login
+
+You'll probably want to change the Salt value to something unique. You can also override any of the configuration values defined at the top of lib/user_system.rb in a similar way. Note that you don't need to start the engine with Engines.start :login_engine - instead, :login (or any name) is sufficient if the engine is a directory named _engine.
+
+
+=== Add the filters
+
+Next, edit your app/controllers/application.rb file. The beginning of your ApplicationController should look something like this:
+
+ require 'login_engine'
+
+ class ApplicationController < ActionController::Base
+ include LoginEngine
+ helper :user
+ model :user
+
+ before_filter :login_required
+
+If you don't want ALL actions to require a login, you need to read further below to learn how to restrict only certain actions.
+
+Add the following to your ApplicationHelper:
+
+ module ApplicationHelper
+ include LoginEngine
+ end
+
+This ensures that the methods to work with users in your views are available
+
+=== Set up ActionMailer
+
+If you want to disable email functions within the Login Engine, simple set the :use_email_notification config flag to false in your environment.rb file:
+
+ module LoginEngine
+
+ # ... other options...
+ config :use_email_notification, false
+
+ end
+
+You should note that retrieving forgotten passwords automatically isn't possible when the email functions are disabled. Instead, the user is presented with a message instructing them to contact the system administrator
+
+If you wish you use email notifications and account creation verification, you must properly configure ActionMailer for your mail settings. For example, you could add the following in config/environments/development.rb (for a .Mac account, and with your own username and password, obviously):
+
+ActionMailer::Base.server_settings = {
+ :address => "smtp.mac.com",
+ :port => 25,
+ :domain => "smtp.mac.com",
+ :user_name => "",
+ :password => "",
+ :authentication => :login
+}
+
+You'll need to configure it properly so that email can be sent. One of the easiest ways to test your configuration is to temporarily reraise exceptions from the signup method (so that you get the actual mailer exception string). In the rescue statement, put a single "raise" statement in. Once you've debugged any setting problems, remove that statement to get the proper flash error handling back.
+
+
+=== Create the DB schema
+
+After you have done the modifications the the ApplicationController and its helper, you can import the user model into the database. Migration information in login_engine/db/migrate/.
+
+You *MUST* check that these files aren't going to interfere with anything in your application.
+
+You can change the table name used by adding
+
+ module LoginEngine
+
+ # ... other options...
+ config :user_table, "your_table_name"
+
+ end
+
+...to the LoginEngine configuration in environment.rb. Then run from the root of your project:
+
+ rake db:migrate:engines ENGINE=login
+
+to import the schema into your database.
+
+
+== Include stylesheets
+
+If you want the default stylesheet, add the following line to your layout:
+
+ <%= engine_stylesheet 'login_engine' %>
+
+... somewhere in the section of your HTML layout file.
+
+== Integrate flash messages into your layout
+
+LoginEngine does not display any flash messages in the views it contains, and thus you must display them yourself. This allows you to integrate any flash messages into your existing layout. LoginEngine adheres to the emerging flash usage standard, namely:
+
+* :warning - warning (failure) messages
+* :notice - success messages
+* :message - neutral (reminder, informational) messages
+
+This gives you the flexibility to theme the different message classes separately. In your layout you should check for and display flash[:warning], flash[:notice] and flash[:message]. For example:
+
+ <% for name in [:notice, :warning, :message] %>
+ <% if flash[name] %>
+ <%= "
#{flash[name]}
" %>
+ <% end %>
+ <% end %>
+
+Alternately, you could look at using the flash helper plugin (available from https://opensvn.csie.org/traccgi/flash_helper_plugin/trac.cgi/), which supports the same naming convention.
+
+
+= How to use the Login Engine
+
+Now you can go around and happily add "before_filter :login_required" to the controllers which you would like to protect.
+
+After integrating the login system with your rails application navigate to your new controller's signup method. There you can create a new account. After you are done you should have a look at your DB. Your freshly created user will be there but the password will be a sha1 hashed 40 digit mess. I find this should be the minimum of security which every page offering login & password should give its customers. Now you can move to one of those controllers which you protected with the before_filter :login_required snippet. You will automatically be re-directed to your freshly created login controller and you are asked for a password. After entering valid account data you will be taken back to the controller which you requested earlier. Simple huh?
+
+=== Protection using before_filter
+
+Adding the line before_filter :login_required to your app/controllers/application.rb file will protect *all* of your applications methods, in every controller. If you only want to control access to specific controllers, remove this line from application.rb and add it to the controllers that you want to secure.
+
+Within individual controllers you can restrict which methods the filter runs on in the usual way:
+
+ before_filter :login_required, :only => [:myaccount, :changepassword]
+ before_filter :login_required, :except => [:index]
+
+=== Protection using protect?()
+
+Alternatively, you can leave the before_filter in the global application.rb file, and control which actions are restricted in individual controllers by defining a protect?() method in that controller.
+
+For instance, in the UserController we want to allow everyone access to the 'login', 'signup' and 'forgot_password' methods (otherwise noone would be able to access our site!). So a protect?() method is defined in user_controller.rb as follows:
+
+ def protect?(action)
+ if ['login', 'signup', 'forgot_password'].include?(action)
+ return false
+ else
+ return true
+ end
+ end
+
+Of course, you can override this Engine behaviour in your application - see below.
+
+== Configuration
+
+The following configuration variables are set in lib/login_engine.rb. If you wish to override them, you should set them BEFORE calling Engines.start (it is possible to set them after, but it's simpler to just do it before. Please refer to the Engine documentation for the #config method for more information).
+
+For example, the following might appear at the bottom of /config/environment.rb:
+
+ module LoginEngine
+ config :salt, 'my salt'
+ config :app_name, 'My Great App'
+ config :app_url, 'http://www.wow-great-domain.com'
+ end
+
+ Engines.start
+
+=== Configuration Options
+
++email_from+:: The email from which registration/administration emails will appear to
+ come from. Defaults to 'webmaster@your.company'.
++admin_email+:: The email address users are prompted to contact if passwords cannot
+ be emailed. Defaults to 'webmaster@your.company'.
++app_url+:: The URL of the site sent to users for signup/forgotten passwords, etc.
+ Defaults to 'http://localhost:3000/'.
++app_name+:: The application title used in emails. Defaults to 'TestApp'.
++mail_charset+:: The charset used in emails. Defaults to 'utf-8'.
++security_token_life_hours+:: The life span of security tokens, in hours. If a security
+ token is older than this when it is used to try and authenticate
+ a user, it will be discarded. In other words, the amount of time
+ new users have between signing up and clicking the link they
+ are sent. Defaults to 24 hours.
++two_column_input+:: If true, forms created with the UserHelper#form_input method will
+ use a two-column table. Defaults to true.
++changeable_fields+:: An array of fields within the user model which the user
+ is allowed to edit. The Salted Hash Login generator documentation
+ states that you should NOT include the email field in this
+ array, although I am not sure why. Defaults to +[ 'firstname', 'lastname' ]+.
++delayed_delete+:: Set to true to allow delayed deletes (i.e., delete of record
+ doesn't happen immediately after user selects delete account,
+ but rather after some expiration of time to allow this action
+ to be reverted). Defaults to false.
++delayed_delete_days+:: The time delay used for the 'delayed_delete' feature. Defaults to
+ 7 days.
++user_table+:: The table to store User objects in. Defaults to "users" (or "user" if
+ ActiveRecord pluralization is disabled).
++use_email_notification+:: If false, no emails will be sent to the user. As a consequence,
+ users who signup are immediately verified, and they cannot request
+ forgotten passwords. Defaults to true.
++confirm_account+:: An overriding flag to control whether or not user accounts must be
+ verified by email. This overrides the +user_email_notification+ flag.
+ Defaults to true.
+
+== Overriding controllers and views
+
+The standard home page is almost certainly not what you want to present to your users. Because this login system is a Rails Engine, overriding the default behaviour couldn't be simpler. To change the RHTML template shown for the home action, simple create a new file in RAILS_ROOT/app/views/user/home.rhtml (you'll probably need to create the directory user at the same time). This new view file will be used instead of the one provided in the Login Engine. Easy!
+
+
+== Tips & Tricks
+
+How do I...
+
+ ... access the user who is currently logged in
+
+ A: You can get the user object from the session using session[:user]
+ Example:
+ Welcome <%= session[:user].name %>
+
+ You can also use the 'current_user' method provided by UserHelper:
+ Example:
+ Welcome <%= current_user.name %>
+
+
+ ... restrict access to only a few methods?
+
+ A: Use before_filters build in scoping.
+ Example:
+ before_filter :login_required, :only => [:myaccount, :changepassword]
+ before_filter :login_required, :except => [:index]
+
+ ... check if a user is logged-in in my views?
+
+ A: session[:user] will tell you. Here is an example helper which you can use to make this more pretty:
+ Example:
+ def user?
+ !session[:user].nil?
+ end
+
+ ... return a user to the page they came from before logging in?
+
+ A: The user will be send back to the last url which called the method "store_location"
+ Example:
+ User was at /articles/show/1, wants to log in.
+ in articles_controller.rb, add store_location to the show function and
+ send the user to the login form.
+ After he logs in he will be send back to /articles/show/1
+
+You can find more help at http://wiki.rubyonrails.com/rails/show/SaltedLoginGenerator
+
+== Troubleshooting
+
+One of the more common problems people have seen is that after verifying an account by following the emailed URL, they are unable to login via the normal login method since the verified field is not properly set in the user model's row in the DB.
+
+The most common cause of this problem is that the DB and session get out of sync. In particular, it always happens for me after recreating the DB if I have run the server previously. To fix the problem, remove the /tmp/ruby* session files (from wherever they are for your installation) while the server is stopped, and then restart. This usually is the cause of the problem.
+
+= Notes
+
+=== Database Schemas & Testing
+
+Currently, since not all databases appear to support structure cloning, the tests will load the entire schema into your test database, potentially blowing away any other test structures you might have. If this presents an issue for your application, comment out the line in test/test_helper.rb
+
+
+= Database Schema Details
+
+You need a database table corresponding to the User model. This is provided as a Rails Schema file, but the schema is presented below for information. Note the table type for MySQL. Whatever DB you use, it must support transactions. If it does not, the functional tests will not work properly, nor will the application in the face of failures during certain DB creates and updates.
+
+ mysql syntax:
+ CREATE TABLE users (
+ id INTEGER UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ login VARCHAR(80) NOT NULL,
+ salted_password VARCHAR(40) NOT NULL,
+ email VARCHAR(60) NOT NULL,
+ firstname VARCHAR(40),
+ lastname VARCHAR(40),
+ salt CHAR(40) NOT NULL,
+ verified INT default 0,
+ role VARCHAR(40) default NULL,
+ security_token CHAR(40) default NULL,
+ token_expiry DATETIME default NULL,
+ deleted INT default 0,
+ delete_after DATETIME default NULL
+ ) TYPE=InnoDB DEFAULT CHARSET=utf8;
+
+ postgres:
+ CREATE TABLE "users" (
+ id SERIAL PRIMARY KEY
+ login VARCHAR(80) NOT NULL,
+ salted_password VARCHAR(40) NOT NULL,
+ email VARCHAR(60) NOT NULL,
+ firstname VARCHAR(40),
+ lastname VARCHAR(40),
+ salt CHAR(40) NOT NULL,
+ verified INT default 0,
+ role VARCHAR(40) default NULL,
+ security_token CHAR(40) default NULL,
+ token_expiry TIMESTAMP default NULL,
+ deleted INT default 0,
+ delete_after TIMESTAMP default NULL
+ ) WITH OIDS;
+
+ sqlite:
+ CREATE TABLE 'users' (
+ id INTEGER PRIMARY KEY,
+ login VARCHAR(80) NOT NULL,
+ salted_password VARCHAR(40) NOT NULL,
+ email VARCHAR(60) NOT NULL,
+ firstname VARCHAR(40),
+ lastname VARCHAR(40),
+ salt CHAR(40) NOT NULL,
+ verified INT default 0,
+ role VARCHAR(40) default NULL,
+ security_token CHAR(40) default NULL,
+ token_expiry DATETIME default NULL,
+ deleted INT default 0,
+ delete_after DATETIME default NULL
+ );
+
+Of course your user model can have any amount of extra fields. This is just a starting point.
diff --git a/vendor/plugins/login_engine/app/controllers/user_controller.rb b/vendor/plugins/login_engine/app/controllers/user_controller.rb
new file mode 100644
index 0000000..397f06b
--- /dev/null
+++ b/vendor/plugins/login_engine/app/controllers/user_controller.rb
@@ -0,0 +1,261 @@
+class UserController < ApplicationController
+ model :user
+
+ # Override this function in your own application to define a custom home action.
+ def home
+ if user?
+ @fullname = "#{current_user.firstname} #{current_user.lastname}"
+ else
+ @fullname = "Not logged in..."
+ end # this is a bit of a hack since the home action is used to verify user
+ # keys, where noone is logged in. We should probably create a unique
+ # 'validate_key' action instead.
+ end
+
+ # The action used to log a user in. If the user was redirected to the login page
+ # by the login_required method, they should be sent back to the page they were
+ # trying to access. If not, they will be sent to "/user/home".
+ def login
+ return if generate_blank
+ @user = User.new(params[:user])
+ if session[:user] = User.authenticate(params[:user][:login], params[:user][:password])
+ session[:user].logged_in_at = Time.now
+ session[:user].save
+ flash[:notice] = 'Login successful'
+ redirect_to_stored_or_default :action => 'home'
+ else
+ @login = params[:user][:login]
+ flash.now[:warning] = 'Login unsuccessful'
+ end
+ end
+
+ # Register as a new user. Upon successful registration, the user will be sent to
+ # "/user/login" to enter their details.
+ def signup
+ return if generate_blank
+ params[:user].delete('form')
+ params[:user].delete('verified') # you CANNOT pass this as part of the request
+ @user = User.new(params[:user])
+ begin
+ User.transaction(@user) do
+ @user.new_password = true
+ unless LoginEngine.config(:use_email_notification) and LoginEngine.config(:confirm_account)
+ @user.verified = 1
+ end
+ if @user.save
+ key = @user.generate_security_token
+ url = url_for(:action => 'home', :user_id => @user.id, :key => key)
+ flash[:notice] = 'Signup successful!'
+ if LoginEngine.config(:use_email_notification) and LoginEngine.config(:confirm_account)
+ UserNotify.deliver_signup(@user, params[:user][:password], url)
+ flash[:notice] << ' Please check your registered email account to verify your account registration and continue with the login.'
+ else
+ flash[:notice] << ' Please log in.'
+ end
+ redirect_to :action => 'login'
+ end
+ end
+ rescue Exception => e
+ flash.now[:notice] = nil
+ flash.now[:warning] = 'Error creating account: confirmation email not sent'
+ logger.error "Unable to send confirmation E-Mail:"
+ logger.error e
+ end
+ end
+
+ def logout
+ session[:user] = nil
+ redirect_to :action => 'login'
+ end
+
+ def change_password
+ return if generate_filled_in
+ if do_change_password_for(@user)
+ # since sometimes we're changing the password from within another action/template...
+ #redirect_to :action => params[:back_to] if params[:back_to]
+ redirect_back_or_default :action => 'change_password'
+ end
+ end
+
+ protected
+ def do_change_password_for(user)
+ begin
+ User.transaction(user) do
+ user.change_password(params[:user][:password], params[:user][:password_confirmation])
+ if user.save
+ if LoginEngine.config(:use_email_notification)
+ UserNotify.deliver_change_password(user, params[:user][:password])
+ flash[:notice] = "Updated password emailed to #{@user.email}"
+ else
+ flash[:notice] = "Password updated."
+ end
+ return true
+ else
+ flash[:warning] = 'There was a problem saving the password. Please retry.'
+ return false
+ end
+ end
+ rescue
+ flash[:warning] = 'Password could not be changed at this time. Please retry.'
+ end
+ end
+
+ public
+
+
+ def forgot_password
+ # Always redirect if logged in
+ if user?
+ flash[:message] = 'You are currently logged in. You may change your password now.'
+ redirect_to :action => 'change_password'
+ return
+ end
+
+ # Email disabled... we are unable to provide the password
+ if !LoginEngine.config(:use_email_notification)
+ flash[:message] = "Please contact the system admin at #{LoginEngine.config(:admin_email)} to reset your password."
+ redirect_back_or_default :action => 'login'
+ return
+ end
+
+ # Render on :get and render
+ return if generate_blank
+
+ # Handle the :post
+ if params[:user][:email].empty?
+ flash.now[:warning] = 'Please enter a valid email address.'
+ elsif (user = User.find_by_email(params[:user][:email])).nil?
+ flash.now[:warning] = "We could not find a user with the email address #{params[:user][:email]}"
+ else
+ begin
+ User.transaction(user) do
+ key = user.generate_security_token
+ url = url_for(:action => 'change_password', :user_id => user.id, :key => key)
+ UserNotify.deliver_forgot_password(user, url)
+ flash[:notice] = "Instructions on resetting your password have been emailed to #{params[:user][:email]}"
+ end
+ unless user?
+ redirect_to :action => 'login'
+ return
+ end
+ redirect_back_or_default :action => 'home'
+ rescue
+ flash.now[:warning] = "Your password could not be emailed to #{params[:user][:email]}"
+ end
+ end
+ end
+
+ def edit
+ return if generate_filled_in
+ do_edit_user(@user)
+ end
+
+ protected
+ def do_edit_user(user)
+ begin
+ User.transaction(user) do
+ user.attributes = params[:user].delete_if { |k,v| not LoginEngine.config(:changeable_fields).include?(k) }
+ if user.save
+ flash[:notice] = "User details updated"
+ else
+ flash[:warning] = "Details could not be updated! Please retry."
+ end
+ end
+ rescue
+ flash.now[:warning] = "Error updating user details. Please try again later."
+ end
+ end
+
+ public
+
+ def delete
+ get_user_to_act_on
+ if do_delete_user(@user)
+ logout
+ else
+ redirect_back_or_default :action => 'home'
+ end
+ end
+
+ protected
+ def do_delete_user(user)
+ begin
+ if LoginEngine.config(:delayed_delete)
+ User.transaction(user) do
+ key = user.set_delete_after
+ if LoginEngine.config(:use_email_notification)
+ url = url_for(:action => 'restore_deleted', :user_id => user.id, :key => key)
+ UserNotify.deliver_pending_delete(user, url)
+ end
+ end
+ else
+ destroy(@user)
+ end
+ return true
+ rescue
+ if LoginEngine.config(:use_email_notification)
+ flash.now[:warning] = 'The delete instructions were not sent. Please try again later.'
+ else
+ flash.now[:notice] = 'The account has been scheduled for deletion. It will be removed in #{LoginEngine.config(:delayed_delete_days)} days.'
+ end
+ return false
+ end
+ end
+
+ public
+
+ def restore_deleted
+ get_user_to_act_on
+ @user.deleted = 0
+ if not @user.save
+ flash.now[:warning] = "The account for #{@user['login']} was not restored. Please try the link again."
+ redirect_to :action => 'login'
+ else
+ redirect_to :action => 'home'
+ end
+ end
+
+ protected
+
+ def destroy(user)
+ UserNotify.deliver_delete(user) if LoginEngine.config(:use_email_notification)
+ flash[:notice] = "The account for #{user['login']} was successfully deleted."
+ user.destroy()
+ end
+
+ def protect?(action)
+ if ['login', 'signup', 'forgot_password'].include?(action)
+ return false
+ else
+ return true
+ end
+ end
+
+ # Generate a template user for certain actions on get
+ def generate_blank
+ case request.method
+ when :get
+ @user = User.new
+ render
+ return true
+ end
+ return false
+ end
+
+ # Generate a template user for certain actions on get
+ def generate_filled_in
+ get_user_to_act_on
+ case request.method
+ when :get
+ render
+ return true
+ end
+ return false
+ end
+
+ # returns the user object this method should act upon; only really
+ # exists for other engines operating on top of this one to redefine...
+ def get_user_to_act_on
+ @user = session[:user]
+ end
+end
diff --git a/vendor/plugins/login_engine/app/helpers/user_helper.rb b/vendor/plugins/login_engine/app/helpers/user_helper.rb
new file mode 100644
index 0000000..2dad7d8
--- /dev/null
+++ b/vendor/plugins/login_engine/app/helpers/user_helper.rb
@@ -0,0 +1,88 @@
+module UserHelper
+
+ # Abstraction to make views a little cleaner
+ def form_input(helper_method, prompt, field_name=nil, options = {}, form_name = nil)
+ form_name = "user" if form_name.nil?
+ case helper_method.to_s
+ when 'hidden_field'
+ self.hidden_field(form_name, field_name, options)
+ when /^.*button$/
+ #prompt = l(:"#{@controller.controller_name}_#{field_name}_button")
+ <<-EOL
+
\ No newline at end of file
diff --git a/vendor/plugins/login_engine/app/views/user/change_password.rhtml b/vendor/plugins/login_engine/app/views/user/change_password.rhtml
new file mode 100644
index 0000000..3b5abe4
--- /dev/null
+++ b/vendor/plugins/login_engine/app/views/user/change_password.rhtml
@@ -0,0 +1,17 @@
+
+
Change Password
+
+ <%= error_messages_for 'user' %>
+
+
+
Enter your new password in the fields below and click 'Change Password' to have a new password sent to your email inbox.
\ No newline at end of file
diff --git a/vendor/plugins/login_engine/app/views/user/edit.rhtml b/vendor/plugins/login_engine/app/views/user/edit.rhtml
new file mode 100644
index 0000000..efe7953
--- /dev/null
+++ b/vendor/plugins/login_engine/app/views/user/edit.rhtml
@@ -0,0 +1,23 @@
+
+
\ No newline at end of file
diff --git a/vendor/plugins/login_engine/app/views/user/forgot_password.rhtml b/vendor/plugins/login_engine/app/views/user/forgot_password.rhtml
new file mode 100644
index 0000000..66c7ce9
--- /dev/null
+++ b/vendor/plugins/login_engine/app/views/user/forgot_password.rhtml
@@ -0,0 +1,18 @@
+
+
Forgotten Password
+
+ <%= error_messages_for 'user' %>
+
+
+
Enter your email address in the field below and click 'Reset Password' to have instructions on how to retrieve your forgotten password emailed to you.
diff --git a/vendor/plugins/login_engine/app/views/user/home.rhtml b/vendor/plugins/login_engine/app/views/user/home.rhtml
new file mode 100644
index 0000000..019b865
--- /dev/null
+++ b/vendor/plugins/login_engine/app/views/user/home.rhtml
@@ -0,0 +1,7 @@
+
+
Welcome
+
You are now logged into the system, <%= @fullname %>...
+
Since you are here it's safe to assume the application never called store_location, otherwise you would have been redirected somewhere else after a successful login.
\n\n", mail.parts[1].body
+ end
+
+ def test_headers_removed_on_smtp_delivery
+ ActionMailer::Base.delivery_method = :smtp
+ TestMailer.deliver_cc_bcc(@recipient)
+ assert MockSMTP.deliveries[0][2].include?("root@loudthinking.com")
+ assert MockSMTP.deliveries[0][2].include?("nobody@loudthinking.com")
+ assert MockSMTP.deliveries[0][2].include?(@recipient)
+ assert_match %r{^Cc: nobody@loudthinking.com}, MockSMTP.deliveries[0][0]
+ assert_match %r{^To: #{@recipient}}, MockSMTP.deliveries[0][0]
+ assert_no_match %r{^Bcc: root@loudthinking.com}, MockSMTP.deliveries[0][0]
+ end
+
+ def test_recursive_multipart_processing
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email7")
+ mail = TMail::Mail.parse(fixture)
+ assert_equal "This is the first part.\n\nAttachment: test.rb\nAttachment: test.pdf\n\n\nAttachment: smime.p7s\n", mail.body
+ end
+
+ def test_decode_encoded_attachment_filename
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email8")
+ mail = TMail::Mail.parse(fixture)
+ attachment = mail.attachments.last
+ assert_equal "01QuienTeDijat.Pitbull.mp3", attachment.original_filename
+ end
+
+ def test_wrong_mail_header
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email9")
+ assert_raise(TMail::SyntaxError) { TMail::Mail.parse(fixture) }
+ end
+
+ def test_decode_message_with_unknown_charset
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email10")
+ mail = TMail::Mail.parse(fixture)
+ assert_nothing_raised { mail.body }
+ end
+
+ def test_decode_message_with_unquoted_atchar_in_header
+ fixture = File.read(File.dirname(__FILE__) + "/fixtures/raw_email11")
+ mail = TMail::Mail.parse(fixture)
+ assert_not_nil mail.from
+ end
+
+ def test_empty_header_values_omitted
+ result = TestMailer.create_unnamed_attachment(@recipient).encoded
+ assert_match %r{Content-Type: application/octet-stream[^;]}, result
+ assert_match %r{Content-Disposition: attachment[^;]}, result
+ end
+
+ def test_headers_with_nonalpha_chars
+ mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
+ assert !mail.from_addrs.empty?
+ assert !mail.cc_addrs.empty?
+ assert !mail.bcc_addrs.empty?
+ assert_match(/:/, mail.from_addrs.to_s)
+ assert_match(/:/, mail.cc_addrs.to_s)
+ assert_match(/:/, mail.bcc_addrs.to_s)
+ end
+
+ def test_deliver_with_mail_object
+ mail = TestMailer.create_headers_with_nonalpha_chars(@recipient)
+ assert_nothing_raised { TestMailer.deliver(mail) }
+ assert_equal 1, TestMailer.deliveries.length
+ end
+
+ def test_multipart_with_template_path_with_dots
+ mail = FunkyPathMailer.create_multipart_with_template_path_with_dots(@recipient)
+ assert_equal 2, mail.parts.length
+ end
+
+ def test_custom_content_type_attributes
+ mail = TestMailer.create_custom_content_type_attributes
+ assert_match %r{format=flowed}, mail['content-type'].to_s
+ assert_match %r{charset=utf-8}, mail['content-type'].to_s
+ end
+end
+
+class InheritableTemplateRootTest < Test::Unit::TestCase
+ def test_attr
+ expected = "#{File.dirname(__FILE__)}/fixtures/path.with.dots"
+ assert_equal expected, FunkyPathMailer.template_root
+
+ sub = Class.new(FunkyPathMailer)
+ sub.template_root = 'test/path'
+
+ assert_equal 'test/path', sub.template_root
+ assert_equal expected, FunkyPathMailer.template_root
+ end
+end
diff --git a/vendor/rails/actionmailer/test/quoting_test.rb b/vendor/rails/actionmailer/test/quoting_test.rb
new file mode 100644
index 0000000..0b145b1
--- /dev/null
+++ b/vendor/rails/actionmailer/test/quoting_test.rb
@@ -0,0 +1,45 @@
+require "#{File.dirname(__FILE__)}/abstract_unit"
+require 'tmail'
+require 'tempfile'
+
+class QuotingTest < Test::Unit::TestCase
+ def test_quote_multibyte_chars
+ original = "\303\246 \303\270 and \303\245"
+
+ result = execute_in_sandbox(<<-CODE)
+ $:.unshift(File.dirname(__FILE__) + "/../lib/")
+ $KCODE = 'u'
+ require 'jcode'
+ require 'action_mailer/quoting'
+ include ActionMailer::Quoting
+ quoted_printable(#{original.inspect}, "UTF-8")
+ CODE
+
+ unquoted = TMail::Unquoter.unquote_and_convert_to(result, nil)
+ assert_equal unquoted, original
+ end
+
+ private
+
+ # This whole thing *could* be much simpler, but I don't think Tempfile,
+ # popen and others exist on all platforms (like Windows).
+ def execute_in_sandbox(code)
+ test_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.rb"
+ res_name = "#{File.dirname(__FILE__)}/am-quoting-test.#{$$}.out"
+
+ File.open(test_name, "w+") do |file|
+ file.write(<<-CODE)
+ block = Proc.new do
+ #{code}
+ end
+ puts block.call
+ CODE
+ end
+
+ system("ruby #{test_name} > #{res_name}") or raise "could not run test in sandbox"
+ File.read(res_name)
+ ensure
+ File.delete(test_name) rescue nil
+ File.delete(res_name) rescue nil
+ end
+end
diff --git a/vendor/rails/actionmailer/test/tmail_test.rb b/vendor/rails/actionmailer/test/tmail_test.rb
new file mode 100644
index 0000000..7d83a68
--- /dev/null
+++ b/vendor/rails/actionmailer/test/tmail_test.rb
@@ -0,0 +1,13 @@
+require "#{File.dirname(__FILE__)}/abstract_unit"
+
+class TMailMailTest < Test::Unit::TestCase
+ def test_body
+ m = TMail::Mail.new
+ expected = 'something_with_underscores'
+ m.encoding = 'quoted-printable'
+ quoted_body = [expected].pack('*M')
+ m.body = quoted_body
+ assert_equal "something_with_underscores=\n", m.quoted_body
+ assert_equal expected, m.body
+ end
+end
diff --git a/vendor/rails/actionpack/CHANGELOG b/vendor/rails/actionpack/CHANGELOG
new file mode 100644
index 0000000..faf5228
--- /dev/null
+++ b/vendor/rails/actionpack/CHANGELOG
@@ -0,0 +1,2769 @@
+*SVN*
+
+* Deprecation: check whether instance variables have been monkeyed with before assigning them to deprecation proxies. Raises a RuntimeError if so. [Jeremy Kemper]
+
+* Add support for the param_name parameter to the auto_complete_field helper. #5026 [david.a.williams@gmail.com]
+
+* Deprecation! @params, @session, @flash will be removed after 1.2. Use the corresponding instance methods instead. You'll get printed warnings during tests and logged warnings in dev mode when you access either instance variable directly. [Jeremy Kemper]
+
+* Make Routing noisy when an anchor regexp is assigned to a segment. #5674 [francois.beausoleil@gmail.com]
+
+* Added months and years to the resolution of DateHelper#distance_of_time_in_words, such that "60 days ago" becomes "2 months ago" #5611 [pjhyett@gmail.com]
+
+* Short documentation to mention use of Mime::Type.register. #5710 [choonkeat@gmail.com]
+
+* Make controller_path available as an instance method. #5724 [jmckible@gmail.com]
+
+* Update query parser to support adjacent hashes. [Nicholas Seckar]
+
+* Make action caching aware of different formats for the same action so that, e.g. foo.xml is cached separately from foo.html. Implicitly set content type when reading in cached content with mime revealing extensions so the entire onous isn't on the webserver. [Marcel Molina Jr.]
+
+* Restrict Request Method hacking with ?_method to POST requests. [Rick Olson]
+
+* Fix bug when passing multiple options to SimplyRestful, like :new => { :preview => :get, :draft => :get }. [Rick Olson, Josh Susser, Lars Pind]
+
+* Dup the options passed to map.resources so that multiple resources get the same options. [Rick Olson]
+
+* Fixed the new_#{resource}_url route and added named route tests for Simply Restful. [Rick Olson]
+
+* Added map.resources from the Simply Restful plugin [DHH]. Examples (the API has changed to use plurals!):
+
+ map.resources :messages
+ map.resources :messages, :comments
+ map.resources :messages, :new => { :preview => :post }
+
+* Fixed that integration simulation of XHRs should set Accept header as well [Edward Frederick]
+
+* TestRequest#reset_session should restore a TestSession, not a hash [Koz]
+
+* Don't search a load-path of '.' for controller files [Jamis Buck]
+
+* Update integration.rb to require test_process explicitly instead of via Dependencies. [Nicholas Seckar]
+
+* Fixed that you can still access the flash after the flash has been reset in reset_session. Closes #5584 [lmarlow@yahoo.com]
+
+* Allow form_for and fields_for to work with indexed form inputs. [Jeremy Kemper, Matt Lyon]
+
+ <% form_for 'post[]', @post do |f| -%>
+ <% end -%>
+
+* Remove leak in development mode by replacing define_method with module_eval. [Nicholas Seckar]
+
+* Provide support for decimal columns to form helpers. Closes #5672. [dave@pragprog.com]
+
+* Update documentation for erb trim syntax. #5651 [matt@mattmargolis.net]
+
+* Pass :id => nil or :class => nil to error_messages_for to supress that html attribute. #3586 [olivier_ansaldi@yahoo.com, sebastien@goetzilla.info]
+
+* Reset @html_document between requests so assert_tag works. #4810 [jarkko@jlaine.net, easleydp@gmail.com]
+
+* Update render :partial documentation. #5646 [matt@mattmargolis.net]
+
+* Integration tests behave well with render_component. #4632 [edward.frederick@revolution.com, dev.rubyonrails@maxdunn.com]
+
+* Added exception handling of missing layouts #5373 [chris@ozmm.org]
+
+* Fixed that real files and symlinks should be treated the same when compiling templates #5438 [zachary@panandscan.com]
+
+* Fixed that the flash should be reset when reset_session is called #5584 [shugo@ruby-lang.org]
+
+* Added special case for "1 Byte" in NumberHelper#number_to_human_size #5593 [murpyh@rubychan.de]
+
+* Fixed proper form-encoded parameter parsing for requests with "Content-Type: application/x-www-form-urlencoded; charset=utf-8" (note the presence of a charset directive) [DHH]
+
+* Add route_name_path method to generate only the path for a named routes. For example, map.person will add person_path. [Nicholas Seckar]
+
+* Avoid naming collision among compiled view methods. [Jeremy Kemper]
+
+* Fix CGI extensions when they expect string but get nil in Windows. Closes #5276 [mislav@nippur.irb.hr]
+
+* Determine the correct template_root for deeply nested components. #2841 [s.brink@web.de]
+
+* Fix that routes with *path segments in the recall can generate URLs. [Rick]
+
+* Fix strip_links so that it doesn't hang on multiline tags [Jamis Buck]
+
+* Remove problematic control chars in rescue template. #5316 [Stefan Kaes]
+
+* Make sure passed routing options are not mutated by routing code. #5314 [Blair Zajac]
+
+* Make sure changing the controller from foo/bar to bing/bang does not change relative to foo. [Jamis Buck]
+
+* Escape the path before routing recognition. #3671
+
+* Make sure :id and friends are unescaped properly. #5275 [me@julik.nl]
+
+* Fix documentation for with_routing to reflect new reality. #5281 [rramdas@gmail.com]
+
+* Rewind readable CGI params so others may reread them (such as CGI::Session when passing the session id in a multipart form). #210 [mklame@atxeu.com, matthew@walker.wattle.id.au]
+
+* Added Mime::TEXT (text/plain) and Mime::ICS (text/calendar) as new default types [DHH]
+
+* Added Mime::Type.register(string, symbol, synonyms = []) for adding new custom mime types [DHH]. Example: Mime::Type.register("image/gif", :gif)
+
+* Added support for Mime objects in render :content_type option [DHH]. Example: render :text => some_atom, :content_type => Mime::ATOM
+
+* Add :status option to send_data and send_file. Defaults to '200 OK'. #5243 [Manfred Stienstra ]
+
+* Routing rewrite. Simpler, faster, easier to understand. The published API for config/routes.rb is unchanged, but nearly everything else is different, so expect breakage in plugins and libs that try to fiddle with routes. [Nicholas Seckar, Jamis Buck]
+
+ map.connect '/foo/:id', :controller => '...', :action => '...'
+ map.connect '/foo/:id.:format', :controller => '...', :action => '...'
+ map.connect '/foo/:id', ..., :conditions => { :method => :get }
+
+* Cope with missing content type and length headers. Parse parameters from multipart and urlencoded request bodies only. [Jeremy Kemper]
+
+* Accept multipart PUT parameters. #5235 [guy.naor@famundo.com]
+
+* Added interrogation of params[:format] to determine Accept type. If :format is specified and matches a declared extension, like "rss" or "xml", that mime type will be put in front of the accept handler. This means you can link to the same action from different extensions and use that fact to determine output [DHH]. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find :all
+
+ respond_to do |format|
+ format.html
+ format.xml { render :xml => @posts.to_xml }
+ format.rss { render :action => "feed.rxml" }
+ end
+ end
+ end
+
+ # returns HTML when requested by a browser, since the browser
+ # has the HTML mimetype at the top of its priority list
+ Accept: text/html
+ GET /weblog
+
+ # returns the XML
+ Accept: application/xml
+ GET /weblog
+
+ # returns the HTML
+ Accept: application/xml
+ GET /weblog.html
+
+ # returns the XML
+ Accept: text/html
+ GET /weblog.xml
+
+ All this relies on the fact that you have a route that includes .:format.
+
+* Expanded :method option in FormTagHelper#form_tag, FormHelper#form_for, PrototypeHelper#remote_form_for, PrototypeHelper#remote_form_tag, and PrototypeHelper#link_to_remote to allow for verbs other than GET and POST by automatically creating a hidden form field named _method, which will simulate the other verbs over post [DHH]
+
+* Added :method option to UrlHelper#link_to, which allows for using other verbs than GET for the link. This replaces the :post option, which is now deprecated. Example: link_to "Destroy", person_url(:id => person), :method => :delete [DHH]
+
+* follow_redirect doesn't complain about being redirected to the same controller. #5153 [dymo@mk.ukrtelecom.ua]
+
+* Add layout attribute to response object with the name of the layout that was rendered, or nil if none rendered. [Kevin Clark kevin.clark@gmail.com]
+
+* Fix NoMethodError when parsing params like &&. [Adam Greenfield]
+
+* Fix flip flopped logic in docs for url_for's :only_path option. Closes #4998. [esad@esse.at]
+
+* form.text_area handles the :size option just like the original text_area (:size => '60x10' becomes cols="60" rows="10"). [Jeremy Kemper]
+
+* Excise ingrown code from FormOptionsHelper#options_for_select. #5008 [anonymous]
+
+* Small fix in routing to allow dynamic routes (broken after [4242]) [Rick]
+
+ map.connect '*path', :controller => 'files', :action => 'show'
+
+* Replace alias method chaining with Module#alias_method_chain. [Marcel Molina Jr.]
+
+* Replace Ruby's deprecated append_features in favor of included. [Marcel Molina Jr.]
+
+* Use #flush between switching from #write to #syswrite. Closes #4907. [Blair Zajac ]
+
+* Documentation fix: integration test scripts don't require integration_test. Closes #4914. [Frederick Ros ]
+
+* ActionController::Base Summary documentation rewrite. Closes #4900. [kevin.clark@gmail.com]
+
+* Fix text_helper.rb documentation rendering. Closes #4725. [Frederick Ros]
+
+* Fixes bad rendering of JavaScriptMacrosHelper rdoc (closes #4910) [Frederick Ros]
+
+* Allow error_messages_for to report errors for multiple objects, as well as support for customizing the name of the object in the error summary header. Closes #4186. [andrew@redlinesoftware.com, Marcel Molina Jr.]
+
+ error_messages_for :account, :user, :subscription, :object_name => :account
+
+* Enhance documentation for setting headers in integration tests. Skip auto HTTP prepending when its already there. Closes #4079. [Rick Olson]
+
+* Documentation for AbstractRequest. Closes #4895. [kevin.clark@gmail.com]
+
+* Refactor various InstanceTag instance method to class methods. Closes #4800. [skaes@web.de]
+
+* Remove all remaining references to @params in the documentation. [Marcel Molina Jr.]
+
+* Add documentation for redirect_to :back's RedirectBackError exception. [Marcel Molina Jr.]
+
+* Update layout and content_for documentation to use yield rather than magic @content_for instance variables. [Marcel Molina Jr.]
+
+* Fix assert_redirected_to tests according to real-world usage. Also, don't fail if you add an extra :controller option: [Rick]
+
+ redirect_to :action => 'new'
+ assert_redirected_to :controller => 'monkeys', :action => 'new'
+
+* Cache CgiRequest#request_parameters so that multiple calls don't re-parse multipart data. [Rick]
+
+* Diff compared routing options. Allow #assert_recognizes to take a second arg as a hash to specify optional request method [Rick]
+
+ assert_recognizes({:controller => 'users', :action => 'index'}, 'users')
+ assert_recognizes({:controller => 'users', :action => 'create'}, {:path => 'users', :method => :post})
+
+* Diff compared options with #assert_redirected_to [Rick]
+
+* Add support in routes for semicolon delimited "subpaths", like /books/:id;:action [Jamis Buck]
+
+* Change link_to_function and button_to_function to (optionally) take an update_page block instead of a JavaScript string. Closes #4804. [zraii@comcast.net, Sam Stephenson]
+
+* Fixed that remote_form_for can leave out the object parameter and default to the instance variable of the object_name, just like form_for [DHH]
+
+* Modify routing so that you can say :require => { :method => :post } for a route, and the route will never be selected unless the request method is POST. Only works for route recognition, not for route generation. [Jamis Buck]
+
+* Added :add_headers option to verify which merges a hash of name/value pairs into the response's headers hash if the prerequisites cannot be satisfied. [Sam Stephenson]
+ ex. verify :only => :speak, :method => :post,
+ :render => { :status => 405, :text => "Must be post" },
+ :add_headers => { "Allow" => "POST" }
+
+* Added ActionController.filter_parameter_logging that makes it easy to remove passwords, credit card numbers, and other sensitive information from being logged when a request is handled #1897 [jeremye@bsa.ca.gov]
+
+
+*1.12.1* (April 6th, 2006)
+
+* Fixed that template extensions would be cached development mode #4624 [Stefan Kaes]
+
+* Update to Prototype 1.5.0_rc0 [Sam Stephenson]
+
+* Honor skipping filters conditionally for only certain actions even when the parent class sets that filter to conditionally be executed only for the same actions. #4522 [Marcel Molina Jr.]
+
+* Delegate xml_http_request in integration tests to the session instance. [Jamis Buck]
+
+* Update the diagnostics template skip the useless '' text. [Nicholas Seckar]
+
+* CHANGED DEFAULT: Don't parse YAML input by default, but keep it available as an easy option [DHH]
+
+* Add additional autocompleter options [aballai, Thomas Fuchs]
+
+* Fixed fragment caching of binary data on Windows #4493 [bellis@deepthought.org]
+
+* Applied Prototype $() performance patches (#4465, #4477) and updated script.aculo.us [Sam Stephenson, Thomas Fuchs]
+
+* Added automated timestamping to AssetTagHelper methods for stylesheets, javascripts, and images when Action Controller is run under Rails [DHH]. Example:
+
+ image_tag("rails.png") # => ''
+
+ ...to avoid frequent stats (not a problem for most people), you can set RAILS_ASSET_ID in the ENV to avoid stats:
+
+ ENV["RAILS_ASSET_ID"] = "2345"
+ image_tag("rails.png") # => ''
+
+ This can be used by deployment managers to set the asset id by application revision
+
+
+*1.12.0* (March 27th, 2006)
+
+* Add documentation for respond_to. [Jamis Buck]
+
+* Fixed require of bluecloth and redcloth when gems haven't been loaded #4446 [murphy@cYcnus.de]
+
+* Update to Prototype 1.5.0_pre1 [Sam Stephenson]
+
+* Change #form_for and #fields_for so that the second argument is not required [Dave Thomas]
+
+ <% form_for :post, @post, :url => { :action => 'create' } do |f| -%>
+
+ becomes...
+
+ <% form_for :post, :url => { :action => 'create' } do |f| -%>
+
+* Update to script.aculo.us 1.6 [Thomas Fuchs]
+
+* Enable application/x-yaml processing by default [Jamis Buck]
+
+* Fix double url escaping of remote_function. Add :escape => false option to ActionView's url_for. [Nicholas Seckar]
+
+* Add :script option to in_place_editor to support evalScripts (closes #4194) [codyfauser@gmail.com]
+
+* Fix mixed case enumerable methods in the JavaScript Collection Proxy (closes #4314) [codyfauser@gmail.com]
+
+* Undo accidental escaping for mail_to; add regression test. [Nicholas Seckar]
+
+* Added nicer message for assert_redirected_to (closes #4294) [court3nay]
+
+ assert_redirected_to :action => 'other_host', :only_path => false
+
+ when it was expecting...
+
+ redirected_to :action => 'other_host', :only_path => true, :host => 'other.test.host'
+
+ gives the error message...
+
+ response is not a redirection to all of the options supplied (redirection is <{:only_path=>false, :host=>"other.test.host", :action=>"other_host"}>), difference: <{:only_path=>"true", :host=>"other.test.host"}>
+
+* Change url_for to escape the resulting URLs when called from a view. [Nicholas Seckar, coffee2code]
+
+* Added easy support for testing file uploads with fixture_file_upload #4105 [turnip@turnipspatch.com]. Example:
+
+ # Looks in Test::Unit::TestCase.fixture_path + '/files/spongebob.png'
+ post :change_avatar, :avatar => fixture_file_upload('/files/spongebob.png', 'image/png')
+
+* Fixed UrlHelper#current_page? to behave even when url-escaped entities are present #3929 [jeremy@planetargon.com]
+
+* Add ability for relative_url_root to be specified via an environment variable RAILS_RELATIVE_URL_ROOT. [isaac@reuben.com, Nicholas Seckar]
+
+* Fixed link_to "somewhere", :post => true to produce valid XHTML by using the parentnode instead of document.body for the instant form #3007 [Bob Silva]
+
+* Added :function option to PrototypeHelper#observe_field/observe_form that allows you to call a function instead of submitting an ajax call as the trigger #4268 [jonathan@daikini.com]
+
+* Make Mime::Type.parse consider q values (if any) [Jamis Buck]
+
+* XML-formatted requests are typecast according to "type" attributes for :xml_simple [Jamis Buck]
+
+* Added protection against proxy setups treating requests as local even when they're not #3898 [stephen_purcell@yahoo.com]
+
+* Added TestRequest#raw_post that simulate raw_post from CgiRequest #3042 [francois.beausoleil@gmail.com]
+
+* Underscore dasherized keys in formatted requests [Jamis Buck]
+
+* Add MimeResponds::Responder#any for managing multiple types with identical responses [Jamis Buck]
+
+* Make the xml_http_request testing method set the HTTP_ACCEPT header [Jamis Buck]
+
+* Add Verification to scaffolds. Prevent destructive actions using GET [Michael Koziarski]
+
+* Avoid hitting the filesystem when using layouts by using a File.directory? cache. [Stefan Kaes, Nicholas Seckar]
+
+* Simplify ActionController::Base#controller_path [Nicholas Seckar]
+
+* Added simple alert() notifications for RJS exceptions when config.action_view.debug_rjs = true. [Sam Stephenson]
+
+* Added :content_type option to render, so you can change the content type on the fly [DHH]. Example: render :action => "atom.rxml", :content_type => "application/atom+xml"
+
+* CHANGED DEFAULT: The default content type for .rxml is now application/xml instead of type/xml, see http://www.xml.com/pub/a/2004/07/21/dive.html for reason [DHH]
+
+* Added option to render action/template/file of a specific extension (and here by template type). This means you can have multiple templates with the same name but a different extension [DHH]. Example:
+
+ class WeblogController < ActionController::Base
+ def index
+ @posts = Post.find :all
+
+ respond_to do |type|
+ type.html # using defaults, which will render weblog/index.rhtml
+ type.xml { render :action => "index.rxml" }
+ type.js { render :action => "index.rjs" }
+ end
+ end
+ end
+
+* Added better support for using the same actions to output for different sources depending on the Accept header [DHH]. Example:
+
+ class WeblogController < ActionController::Base
+ def create
+ @post = Post.create(params[:post])
+
+ respond_to do |type|
+ type.js { render } # renders create.rjs
+ type.html { redirect_to :action => "index" }
+ type.xml do
+ headers["Location"] = url_for(:action => "show", :id => @post)
+ render(:nothing, :status => "201 Created")
+ end
+ end
+ end
+ end
+
+* Added Base#render(:xml => xml) that works just like Base#render(:text => text), but sets the content-type to text/xml and the charset to UTF-8 [DHH]
+
+* Integration test's url_for now runs in the context of the last request (if any) so after post /products/show/1 url_for :action => 'new' will yield /product/new [Tobias Luetke]
+
+* Re-added mixed-in helper methods for the JavascriptGenerator. Moved JavascriptGenerators methods to a module that is mixed in after the helpers are added. Also fixed that variables set in the enumeration methods like #collect are set correctly. Documentation added for the enumeration methods [Rick Olson]. Examples:
+
+ page.select('#items li').collect('items') do |element|
+ element.hide
+ end
+ # => var items = $$('#items li').collect(function(value, index) { return value.hide(); });
+
+* Added plugin support for parameter parsers, which allows for better support for REST web services. By default, posts submitted with the application/xml content type is handled by creating a XmlSimple hash with the same name as the root element of the submitted xml. More handlers can easily be registered like this:
+
+ # Assign a new param parser to a new content type
+ ActionController::Base.param_parsers['application/atom+xml'] = Proc.new do |data|
+ node = REXML::Document.new(post)
+ { node.root.name => node.root }
+ end
+
+ # Assign the default XmlSimple to a new content type
+ ActionController::Base.param_parsers['application/backpack+xml'] = :xml_simple
+
+Default YAML web services were retired, ActionController::Base.param_parsers carries an example which shows how to get this functionality back. As part of this new plugin support, request.[formatted_post?, xml_post?, yaml_post? and post_format] were all deprecated in favor of request.content_type [Tobias Luetke]
+
+* Fixed Effect.Appear in effects.js to work with floats in Safari #3524, #3813, #3044 [Thomas Fuchs]
+
+* Fixed that default image extension was not appended when using a full URL with AssetTagHelper#image_tag #4032, #3728 [rubyonrails@beautifulpixel.com]
+
+* Added that page caching will only happen if the response code is less than 400 #4033 [g.bucher@teti.ch]
+
+* Add ActionController::IntegrationTest to allow high-level testing of the way the controllers and routes all work together [Jamis Buck]
+
+* Added support to AssetTagHelper#javascript_include_tag for having :defaults appear anywhere in the list, so you can now make one call ala javascript_include_tag(:defaults, "my_scripts") or javascript_include_tag("my_scripts", :defaults) depending on how you want the load order #3506 [Bob Silva]
+
+* Added support for visual effects scoped queues to the visual_effect helper #3530 [Abdur-Rahman Advany]
+
+* Added .rxml (and any non-rhtml template, really) supportfor CaptureHelper#content_for and CaptureHelper#capture #3287 [Brian Takita]
+
+* Added script.aculo.us drag and drop helpers to RJS [Thomas Fuchs]. Examples:
+
+ page.draggable 'product-1'
+ page.drop_receiving 'wastebasket', :url => { :action => 'delete' }
+ page.sortable 'todolist', :url => { action => 'change_order' }
+
+* Fixed that form elements would strip the trailing [] from the first parameter #3545 [ruby@bobsilva.com]
+
+* During controller resolution, update the NameError suppression to check for the expected constant. [Nicholas Seckar]
+
+* Update script.aculo.us to V1.5.3 [Thomas Fuchs]
+
+* Added various InPlaceEditor options, #3746, #3891, #3896, #3906 [Bill Burcham, ruairi, sl33p3r]
+
+* Added :count option to pagination that'll make it possible for the ActiveRecord::Base.count call to using something else than * for the count. Especially important for count queries using DISTINCT #3839 [skaes]
+
+* Update script.aculo.us to V1.5.2 [Thomas Fuchs]
+
+* Added element and collection proxies to RJS [DHH]. Examples:
+
+ page['blank_slate'] # => $('blank_slate');
+ page['blank_slate'].show # => $('blank_slate').show();
+ page['blank_slate'].show('first').up # => $('blank_slate').show('first').up();
+
+ page.select('p') # => $$('p');
+ page.select('p.welcome b').first # => $$('p.welcome b').first();
+ page.select('p.welcome b').first.hide # => $$('p.welcome b').first().hide();
+
+* Add JavaScriptGenerator#replace for replacing an element's "outer HTML". #3246 [tom@craz8.com, Sam Stephenson]
+
+* Remove over-engineered form_for code for a leaner implementation. [Nicholas Seckar]
+
+* Document form_for's :html option. [Nicholas Seckar]
+
+* Major components cleanup and speedup. #3527 [Stefan Kaes]
+
+* Fix problems with pagination and :include. [Kevin Clark]
+
+* Add ActiveRecordTestCase for testing AR integration. [Kevin Clark]
+
+* Add Unit Tests for pagination [Kevin Clark]
+
+* Add :html option for specifying form tag options in form_for. [Sam Stephenson]
+
+* Replace dubious controller parent class in filter docs. #3655, #3722 [info@rhalff.com, eigentone@gmail.com]
+
+* Don't interpret the :value option on text_area as an html attribute. Set the text_area's value. #3752 [gabriel@gironda.org]
+
+* Fix remote_form_for creates a non-ajax form. [Rick Olson]
+
+* Don't let arbitrary classes match as controllers -- a potentially dangerous bug. [Nicholas Seckar]
+
+* Fix Routing tests. Fix routing where failing to match a controller would prevent the rest of routes from being attempted. [Nicholas Seckar]
+
+* Add :builder => option to form_for and friends. [Nicholas Seckar, Rick Olson]
+
+* Fix controller resolution to avoid accidentally inheriting a controller from a parent module. [Nicholas Seckar]
+
+* Set sweeper's @controller to nil after a request so that the controller may be collected between requests. [Nicholas Seckar]
+
+* Subclasses of ActionController::Caching::Sweeper should be Reloadable. [Rick Olson]
+
+* Document the :xhr option for verifications. #3666 [leeo]
+
+* Added :only and :except controls to skip_before/after_filter just like for when you add filters [DHH]
+
+* Ensure that the instance variables are copied to the template when performing render :update. [Nicholas Seckar]
+
+* Add the ability to call JavaScriptGenerator methods from helpers called in update blocks. [Sam Stephenson] Example:
+ module ApplicationHelper
+ def update_time
+ page.replace_html 'time', Time.now.to_s(:db)
+ page.visual_effect :highlight, 'time'
+ end
+ end
+
+ class UserController < ApplicationController
+ def poll
+ render :update { |page| page.update_time }
+ end
+ end
+
+* Add render(:update) to ActionView::Base. [Sam Stephenson]
+
+* Fix render(:update) to not render layouts. [Sam Stephenson]
+
+* Fixed that SSL would not correctly be detected when running lighttpd/fcgi behind lighttpd w/mod_proxy #3548 [stephen_purcell@yahoo.com]
+
+* Added the possibility to specify atomatic expiration for the memcachd session container #3571 [Stefan Kaes]
+
+* Change layout discovery to take into account the change in semantics with File.join and nil arguments. [Marcel Molina Jr.]
+
+* Raise a RedirectBackError if redirect_to :back is called when there's no HTTP_REFERER defined #3049 [kevin.clark@gmail.com]
+
+* Treat timestamps like datetimes for scaffolding purposes #3388 [Maik Schmidt]
+
+* Fix IE bug with link_to "something", :post => true #3443 [Justin Palmer]
+
+* Extract Test::Unit::TestCase test process behavior into an ActionController::TestProcess module. [Sam Stephenson]
+
+* Pass along blocks from render_to_string to render. [Sam Stephenson]
+
+* Add render :update for inline RJS. [Sam Stephenson] Example:
+ class UserController < ApplicationController
+ def refresh
+ render :update do |page|
+ page.replace_html 'user_list', :partial => 'user', :collection => @users
+ page.visual_effect :highlight, 'user_list'
+ end
+ end
+ end
+
+* allow nil objects for error_messages_for [Michael Koziarski]
+
+* Refactor human_size to exclude decimal place if it is zero. [Marcel Molina Jr.]
+
+* Update to Prototype 1.5.0_pre0 [Sam Stephenson]
+
+* Automatically discover layouts when a controller is namespaced. #2199, #3424 [me@jonnii.com rails@jeffcole.net Marcel Molina Jr.]
+
+* Add support for multiple proxy servers to CgiRequest#host [gaetanot@comcast.net]
+
+* Documentation typo fix. #2367 [Blair Zajac]
+
+* Remove Upload Progress. #2871 [Sean Treadway]
+
+* Fix typo in function name mapping in auto_complete_field. #2929 #3446 [doppler@gmail.com phil.ross@gmail.com]
+
+* Allow auto-discovery of third party template library layouts. [Marcel Molina Jr.]
+
+* Have the form builder output radio button, not check box, when calling the radio button helper. #3331 [LouisStAmour@gmail.com]
+
+* Added assignment of the Autocompleter object created by JavaScriptMacroHelper#auto_complete_field to a local javascript variables [DHH]
+
+* Added :on option for PrototypeHelper#observe_field that allows you to specify a different callback hook to have the observer trigger on [DHH]
+
+* Added JavaScriptHelper#button_to_function that works just like JavaScriptHelper#link_to_function but uses a button instead of a href [DHH]
+
+* Added that JavaScriptHelper#link_to_function will honor existing :onclick definitions when adding the function call [DHH]
+
+* Added :disable_with option to FormTagHelper#submit_tag to allow for easily disabled submit buttons with different text [DHH]
+
+* Make auto_link handle nil by returning quickly if blank? [Scott Barron]
+
+* Make auto_link match urls with a port number specified. [Marcel Molina Jr.]
+
+* Added support for toggling visual effects to ScriptaculousHelper::visual_effect, #3323. [Thomas Fuchs]
+
+* Update to script.aculo.us to 1.5.0 rev. 3343 [Thomas Fuchs]
+
+* Added :select option for JavaScriptMacroHelper#auto_complete_field that makes it easier to only use part of the auto-complete suggestion as the value for insertion [Thomas Fuchs]
+
+* Added delayed execution of Javascript from within RJS #3264 [devslashnull@gmail.com]. Example:
+
+ page.delay(20) do
+ page.visual_effect :fade, 'notice'
+ end
+
+* Add session ID to default logging, but remove the verbose description of every step [DHH]
+
+* Add the following RJS methods: [Sam Stephenson]
+
+ * alert - Displays an alert() dialog
+ * redirect_to - Changes window.location.href to simulate a browser redirect
+ * call - Calls a JavaScript function
+ * assign - Assigns to a JavaScript variable
+ * << - Inserts an arbitrary JavaScript string
+
+* Fix incorrect documentation for form_for [Nicholas Seckar]
+
+* Don't include a layout when rendering an rjs template using render's :template option. [Marcel Molina Jr.]
+
+*1.1.2* (December 13th, 2005)
+
+* Become part of Rails 1.0
+
+* Update to script.aculo.us 1.5.0 final (equals 1.5.0_rc6) [Thomas Fuchs]
+
+* Update to Prototype 1.4.0 final [Sam Stephenson]
+
+* Added form_remote_for (form_for meets form_remote_tag) [DHH]
+
+* Update to script.aculo.us 1.5.0_rc6
+
+* More robust relative url root discovery for SCGI compatibility. This solves the 'SCGI routes problem' -- you no longer need to prefix all your routes with the name of the SCGI mountpoint. #3070 [Dave Ringoen]
+
+* Fix docs for text_area_tag. #3083. [Christopher Cotton]
+
+* Change form_for and fields_for method signatures to take object name and object as separate arguments rather than as a Hash. [DHH]
+
+* Introduce :selected option to the select helper. Allows you to specify a selection other than the current value of object.method. Specify :selected => nil to leave all options unselected. #2991 [Jonathan Viney ]
+
+* Initialize @optional in routing code to avoid warnings about uninitialized access to an instance variable. [Nicholas Seckar]
+
+* Make ActionController's render honor the :locals option when rendering a :file. #1665. [Emanuel Borsboom, Marcel Molina Jr.]
+
+* Allow assert_tag(:conditions) to match the empty string when a tag has no children. Closes #2959. [Jamis Buck]
+
+* Update html-scanner to handle CDATA sections better. Closes #2970. [Jamis Buck]
+
+* Don't put flash in session if sessions are disabled. [Jeremy Kemper]
+
+* Strip out trailing &_= for raw post bodies. Closes #2868. [Sam Stephenson]
+
+* Pass multiple arguments to Element.show and Element.hide in JavaScriptGenerator instead of using iterators. [Sam Stephenson]
+
+* Improve expire_fragment documentation. #2966 [court3nay@gmail.com]
+
+* Correct docs for automatic layout assignment. #2610. [Charles M. Gerungan]
+
+* Always create new AR sessions rather than trying too hard to avoid database traffic. #2731 [Jeremy Kemper]
+
+* Update to Prototype 1.4.0_rc4. Closes #2943 (old Array.prototype.reverse behavior can be obtained by passing false as an argument). [Sam Stephenson]
+
+* Use Element.update('id', 'html') instead of $('id').innerHTML = 'html' in JavaScriptGenerator#replace_html so that script tags are evaluated. [Sam Stephenson]
+
+* Make rjs templates always implicitly skip out on layouts. [Marcel Molina Jr.]
+
+* Correct length for the truncate text helper. #2913 [Stefan Kaes]
+
+* Update to Prototype 1.4.0_rc3. Closes #1893, #2505, #2550, #2748, #2783. [Sam Stephenson]
+
+* Add support for new rjs templates which wrap an update_page block. [Marcel Molina Jr.]
+
+* Rename Version constant to VERSION. #2802 [Marcel Molina Jr.]
+
+* Correct time_zone_options_for_select docs. #2892 [pudeyo@rpi.com]
+
+* Remove the unused, slow response_dump and session_dump variables from error pages. #1222 [lmarlow@yahoo.com]
+
+* Performance tweaks: use Set instead of Array to speed up prototype helper include? calls. Avoid logging code if logger is nil. Inline commonly-called template presence checks. #2880, #2881, #2882, #2883 [Stefan Kaes]
+
+* MemCache store may be given multiple addresses. #2869 [Ryan Carver ]
+
+* Handle cookie parsing irregularity for certain Nokia phones. #2530 [zaitzow@gmail.com]
+
+* Added PrototypeHelper::JavaScriptGenerator and PrototypeHelper#update_page for easily modifying multiple elements in an Ajax response. [Sam Stephenson] Example:
+
+ update_page do |page|
+ page.insert_html :bottom, 'list', '
Last item
'
+ page.visual_effect :highlight, 'list'
+ page.hide 'status-indicator', 'cancel-link'
+ end
+
+ generates the following JavaScript:
+
+ new Insertion.Bottom("list", "
Last item
");
+ new Effect.Highlight("list");
+ ["status-indicator", "cancel-link"].each(Element.hide);
+
+* Refactored JavaScriptHelper into PrototypeHelper and ScriptaculousHelper [Sam Stephenson]
+
+* Update to latest script.aculo.us version (as of [3031])
+
+* Updated docs for in_place_editor, fixes a couple bugs and offers extended support for external controls [Justin Palmer]
+
+* Update documentation for render :file. #2858 [Tom Werner]
+
+* Only include builtin filters whose filenames match /^[a-z][a-z_]*_helper.rb$/ to avoid including operating system metadata such as ._foo_helper.rb. #2855 [court3nay@gmail.com]
+
+* Added FormHelper#form_for and FormHelper#fields_for that makes it easier to work with forms for single objects also if they don't reside in instance variables [DHH]. Examples:
+
+ <% form_for :person, @person, :url => { :action => "update" } do |f| %>
+ First name: <%= f.text_field :first_name %>
+ Last name : <%= f.text_field :last_name %>
+ Biography : <%= f.text_area :biography %>
+ Admin? : <%= f.check_box :admin %>
+ <% end %>
+
+ <% form_for :person, person, :url => { :action => "update" } do |person_form| %>
+ First name: <%= person_form.text_field :first_name %>
+ Last name : <%= person_form.text_field :last_name %>
+
+ <% fields_for :permission => person.permission do |permission_fields| %>
+ Admin? : <%= permission_fields.check_box :admin %>
+ <% end %>
+ <% end %>
+
+* options_for_select allows any objects which respond_to? :first and :last rather than restricting to Array and Range. #2824 [Jacob Robbins , Jeremy Kemper]
+
+* The auto_link text helper accepts an optional block to format the link text for each url and email address. Example: auto_link(post.body) { |text| truncate(text, 10) } [Jeremy Kemper]
+
+* assert_tag uses exact matches for string conditions, instead of partial matches. Use regex to do partial matches. #2799 [Jamis Buck]
+
+* CGI::Session::ActiveRecordStore.data_column_name = 'foobar' to use a different session data column than the 'data' default. [nbpwie102@sneakemail.com]
+
+* Do not raise an exception when default helper is missing; log a debug message instead. It's nice to delete empty helpers. [Jeremy Kemper]
+
+* Controllers with acronyms in their names (e.g. PDFController) require the correct default helper (PDFHelper in file pdf_helper.rb). #2262 [jeff@opendbms.com]
+
+
+*1.11.0* (November 7th, 2005)
+
+* Added request as instance method to views, so you can do <%= request.env["HTTP_REFERER"] %>, just like you can already access response, session, and the likes [DHH]
+
+* Fix conflict with assert_tag and Glue gem #2255 [david.felstead@gmail.com]
+
+* Add documentation to assert_tag indicating that it only works with well-formed XHTML #1937, #2570 [Jamis Buck]
+
+* Added action_pack.rb stub so that ActionPack::Version loads properly [Sam Stephenson]
+
+* Added short-hand to assert_tag so assert_tag :tag => "span" can be written as assert_tag "span" [DHH]
+
+* Added skip_before_filter/skip_after_filter for easier control of the filter chain in inheritance hierachies [DHH]. Example:
+
+ class ApplicationController < ActionController::Base
+ before_filter :authenticate
+ end
+
+ class WeblogController < ApplicationController
+ # will run the :authenticate filter
+ end
+
+ class SignupController < ActionController::Base
+ # will not run the :authenticate filter
+ skip_before_filter :authenticate
+ end
+
+* Added redirect_to :back as a short-hand for redirect_to(request.env["HTTP_REFERER"]) [DHH]
+
+* Change javascript_include_tag :defaults to not use script.aculo.us loader, which facilitates the use of plugins for future script.aculo.us and third party javascript extensions, and provide register_javascript_include_default for plugins to specify additional JavaScript files to load. Removed slider.js and builder.js from actionpack. [Thomas Fuchs]
+
+* Fix problem where redirecting components can cause an infinite loop [Rick Olson]
+
+* Added support for the queue option on visual_effect [Thomas Fuchs]
+
+* Update script.aculo.us to V1.5_rc4 [Thomas Fuchs]
+
+* Fix that render :text didn't interpolate instance variables #2629, #2626 [skaes]
+
+* Fix line number detection and escape RAILS_ROOT in backtrace Regexp [Nicholas Seckar]
+
+* Fixed document.getElementsByClassName from Prototype to be speedy again [Sam Stephenson]
+
+* Recognize ./#{RAILS_ROOT} as RAILS_ROOT in error traces [Nicholas Seckar]
+
+* Remove ARStore session fingerprinting [Nicholas Seckar]
+
+* Fix obscure bug in ARStore [Nicholas Seckar]
+
+* Added TextHelper#strip_tags for removing HTML tags from a string (using HTMLTokenizer) #2229 [marcin@junkheap.net]
+
+* Added a reader for flash.now, so it's possible to do stuff like flash.now[:alert] ||= 'New if not set' #2422 [Caio Chassot]
+
+
+*1.10.2* (October 26th, 2005)
+
+* Reset template variables after using render_to_string [skaes@web.de]
+
+* Expose the session model backing CGI::Session
+
+* Abbreviate RAILS_ROOT in traces
+
+
+*1.10.1* (October 19th, 2005)
+
+* Update error trace templates [Nicholas Seckar]
+
+* Stop showing generated routing code in application traces [Nicholas Seckar]
+
+
+*1.10.0* (October 16th, 2005)
+
+* Make string-keys locals assigns optional. Add documentation describing depreciated state [skaes@web.de]
+
+* Improve line number detection for template errors [Nicholas Seckar]
+
+* Update/clean up documentation (rdoc)
+
+* Upgrade to Prototype 1.4.0_rc0 [Sam Stephenson]
+
+* Added assert_vaild. Reports the proper AR error messages as fail message when the passed record is invalid [Tobias Luetke]
+
+* Add temporary support for passing locals to render using string keys [Nicholas Seckar]
+
+* Clean up error pages by providing better backtraces [Nicholas Seckar]
+
+* Raise an exception if an attempt is made to insert more session data into the ActiveRecordStore data column than the column can hold. #2234. [justin@textdrive.com]
+
+* Removed references to assertions.rb from actionpack assert's backtraces. Makes error reports in functional unit tests much less noisy. [Tobias Luetke]
+
+* Updated and clarified documentation for JavaScriptHelper to be more concise about the various options for including the JavaScript libs. [Thomas Fuchs]
+
+* Hide "Retry with Breakpoint" button on error pages until feature is functional. [DHH]
+
+* Fix Request#host_with_port to use the standard port when Rails is behind a proxy. [Nicholas Seckar]
+
+* Escape query strings in the href attribute of URLs created by url_helper. #2333 [Michael Schuerig ]
+
+* Improved line number reporting for template errors [Nicholas Seckar]
+
+* Added :locals support for render :inline #2463 [mdabney@cavoksolutions.com]
+
+* Unset the X-Requested-With header when using the xhr wrapper in functional tests so that future requests aren't accidentally xhr'ed #2352 [me@julik.nl, Sam Stephenson]
+
+* Unescape paths before writing cache to file system. #1877. [Damien Pollet]
+
+* Wrap javascript_tag contents in a CDATA section and add a cdata_section method to TagHelper #1691 [Michael Schuerig, Sam Stephenson]
+
+* Misc doc fixes (typos/grammar/etc). #2445. [coffee2code]
+
+* Speed improvement for session_options. #2287. [skaes@web.de]
+
+* Make cacheing binary files friendly with Windows. #1975. [Rich Olson]
+
+* Convert boolean form options form the tag_helper. #809. [Michael Schuerig ]
+
+* Fixed that an instance variable with the same name as a partial should be implicitly passed as the partial :object #2269 [court3nay]
+
+* Update Prototype to V1.4.0_pre11, script.aculo.us to [2502] [Thomas Fuchs]
+
+* Make assert_tag :children count appropriately. Closes #2181. [jamie@bravenet.com]
+
+* Forced newer versions of RedCloth to use hard breaks [DHH]
+
+* Added new scriptaculous options for auto_complete_field #2343 [m.stienstra@fngtps.com]
+
+* Don't prepend the asset host if the string is already a fully-qualified URL
+
+* Updated to script.aculo.us V1.5.0_rc2 and Prototype to V1.4.0_pre7 [Thomas Fuchs]
+
+* Undo condition change made in [2345] to prevent normal parameters arriving as StringIO.
+
+* Tolerate consecutive delimiters in query parameters. #2295 [darashi@gmail.com]
+
+* Streamline render process, code cleaning. Closes #2294. [skae]
+
+* Keep flash after components are rendered. #2291 [Rick Olson, Scott]
+
+* Shorten IE file upload path to filename only to match other browsers. #1507 [court3nay@gmail.com]
+
+* Fix open/save dialog in IE not opening files send with send_file/send_data, #2279 [Thomas Fuchs]
+
+* Fixed that auto_discovery_link_tag couldn't take a string as the URL [DHH]
+
+* Fixed problem with send_file and WEBrick using stdout #1812 [DHH]
+
+* Optimized tag_options to not sort keys, which is no longer necessary when assert_dom_equal and friend is available #1995 [skae]
+
+* Added assert_dom_equal and assert_dom_not_equal to compare tags generated by the helpers in an order-indifferent manner #1995 [skae]
+
+* Fixed that Request#domain caused an exception if the domain header wasn't set in the original http request #1795 [Michael Koziarski]
+
+* Make the truncate() helper multi-byte safe (assuming $KCODE has been set to something other than "NONE") #2103
+
+* Add routing tests from #1945 [ben@groovie.org]
+
+* Add a routing test case covering #2101 [Nicholas Seckar]
+
+* Cache relative_url_root for all webservers, not just Apache #2193 [skae]
+
+* Speed up cookie use by decreasing string copying #2194 [skae]
+
+* Fixed access to "Host" header with requests made by crappy old HTTP/1.0 clients #2124 [Marcel Molina]
+
+* Added easy assignment of fragment cache store through use of symbols for included stores (old way still works too)
+
+ Before:
+ ActionController::Base.fragment_cache_store =
+ ActionController::Base::Caching::Fragments::FileStore.new("/path/to/cache/directory")
+
+ After:
+ ActionController::Base.fragment_cache_store = :file_store, "/path/to/cache/directory"
+
+* Added ActionController::Base.session_store=, session_store, and session_options to make it easier to tweak the session options (instead of going straight to ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS)
+
+* Added TextHelper#cycle to cycle over an array of values on each hit (useful for alternating row colors etc) #2154 [dave-ml@dribin.org]
+
+* Ensure that request.path never returns nil. Closes #1675 [Nicholas Seckar]
+
+* Add ability to specify Route Regexps for controllers. Closes #1917. [Sebastian Kanthak]
+
+* Provide Named Route's hash methods as helper methods. Closes #1744. [Nicholas Seckar, Steve Purcell]
+
+* Added :multipart option to ActiveRecordHelper#form to make it possible to add file input fields #2034 [jstirk@oobleyboo.com]
+
+* Moved auto-completion and in-place editing into the Macros module and their helper counterparts into JavaScriptMacrosHelper
+
+* Added in-place editing support in the spirit of auto complete with ActionController::Base.in_place_edit_for, JavascriptHelper#in_place_editor_field, and Javascript support from script.aculo.us #2038 [Jon Tirsen]
+
+* Added :disabled option to all data selects that'll make the elements inaccessible for change #2167, #253 [eigentone]
+
+* Fixed that TextHelper#auto_link_urls would include punctuation in the links #2166, #1671 [eigentone]
+
+* Fixed that number_to_currency(1000, {:precision => 0})) should return "$1,000", instead of "$1,000." #2122 [sd@notso.net]
+
+* Allow link_to_remote to use any DOM-element as the parent of the form elements to be submitted #2137 [erik@ruby-lang.nl]. Example:
+
+
+
+* Fixed that render :partial would fail when :object was a Hash (due to backwards compatibility issues) #2148 [Sam Stephenson]
+
+* Fixed JavascriptHelper#auto_complete_for to only include unique items #2153 [Thomas Fuchs]
+
+* Fixed all AssetHelper methods to work with relative paths, such that javascript_include_tag('stdlib/standard') will look in /javascripts/stdlib/standard instead of '/stdlib/standard/' #1963
+
+* Avoid extending view instance with helper modules each request. Closes #1979
+
+* Performance improvements to CGI methods. Closes #1980 [Skaes]
+
+* Added :post option to UrlHelper#link_to that makes it possible to do POST requests through normal ahref links using Javascript
+
+* Fixed overwrite_params
+
+* Added ActionController::Base.benchmark and ActionController::Base.silence to allow for easy benchmarking and turning off the log
+
+* Updated vendor copy of html-scanner to support better xml parsing
+
+* Added :popup option to UrlHelper#link_to #1996 [gabriel.gironda@gmail.com]. Examples:
+
+ link_to "Help", { :action => "help" }, :popup => true
+ link_to "Busy loop", { :action => "busy" }, :popup => ['new_window', 'height=300,width=600']
+
+* Drop trailing \000 if present on RAW_POST_DATA (works around bug in Safari Ajax implementation) #918
+
+* Fix observe_field to fall back to event-based observation if frequency <= 0 #1916 [michael@schubert.cx]
+
+* Allow use of the :with option for submit_to_remote #1936 [jon@instance-design.co.uk]
+
+* AbstractRequest#domain returns nil when host is an ip address #2012 [kevin.clark@gmail.com]
+
+* ActionController documentation update #2051 [fbeausoleil@ftml.net]
+
+* Yield @content_for_ variables to templates #2058 [Sam Stephenson]
+
+* Make rendering an empty partial collection behave like :nothing => true #2080 [Sam Stephenson]
+
+* Add option to specify the singular name used by pagination.
+
+* Use string key to obtain action value. Allows indifferent hashes to be disabled.
+
+* Added ActionView::Base.cache_template_loading back.
+
+* Rewrote compiled templates to decrease code complexity. Removed template load caching in favour of compiled caching. Fixed template error messages. [Nicholas Seckar]
+
+* Fix Routing to handle :some_param => nil better. [Nicholas Seckar, Luminas]
+
+* Add support for :include with pagination (subject to existing constraints for :include with :limit and :offset) #1478 [michael@schubert.cx]
+
+* Prevent the benchmark module from blowing up if a non-HTTP/1.1 request is processed
+
+* Added :use_short_month option to select_month helper to show month names as abbreviations
+
+* Make link_to escape the javascript in the confirm option #1964 [nicolas.pouillard@gmail.com]
+
+* Make assert_redirected_to properly check URL's passed as strings #1910 [Scott Barron]
+
+* Make sure :layout => false is always used when rendering inside a layout
+
+* Use raise instead of assert_not_nil in Test::Unit::TestCase#process to ensure that the test variables (controller, request, response) have been set
+
+* Make sure assigns are built for every request when testing #1866
+
+* Allow remote_addr to be queried on TestRequest #1668
+
+* Fixed bug when a partial render was passing a local with the same name as the partial
+
+* Improved performance of test app req/sec with ~10% refactoring the render method #1823 [Stefan Kaes]
+
+* Improved performance of test app req/sec with 5-30% through a series of Action Pack optimizations #1811 [Stefan Kaes]
+
+* Changed caching/expiration/hit to report using the DEBUG log level and errors to use the ERROR log level instead of both using INFO
+
+* Added support for per-action session management #1763
+
+* Improved rendering speed on complicated templates by up to 100% (the more complex the templates, the higher the speedup) #1234 [Stephan Kaes]. This did necessasitate a change to the internals of ActionView#render_template that now has four parameters. Developers of custom view handlers (like Amrita) need to update for that.
+
+* Added options hash as third argument to FormHelper#input, so you can do input('person', 'zip', :size=>10) #1719 [jeremye@bsa.ca.gov]
+
+* Added Base#expires_in(seconds)/Base#expires_now to control HTTP content cache headers #1755 [Thomas Fuchs]
+
+* Fixed line number reporting for Builder template errors #1753 [piotr]
+
+* Fixed assert_routing so that testing controllers in modules works as expected [Nicholas Seckar, Rick Olson]
+
+* Fixed bug with :success/:failure callbacks for the JavaScriptHelper methods #1730 [court3nay/Thomas Fuchs]
+
+* Added named_route method to RouteSet instances so that RouteSet instance methods do not prevent certain names from being used. [Nicholas Seckar]
+
+* Fixed routes so that routes which do not specify :action in the path or in the requirements have a default of :action => 'index', In addition, fixed url generation so that :action => 'index' does not need to be provided for such urls. [Nicholas Seckar, Markjuh]
+
+* Worked around a Safari bug where it wouldn't pass headers through if the response was zero length by having render :nothing return ' ' instead of ''
+
+* Fixed Request#subdomains to handle "foo.foo.com" correctly
+
+
+*1.9.1* (11 July, 2005)
+
+* Fixed that auto_complete_for didn't force the input string to lower case even as the db comparison was
+
+* Fixed that Action View should always use the included Builder, never attempt to require the gem, to ensure compatibility
+
+* Added that nil options are not included in tags, so tag("p", :ignore => nil) now returns not but that tag("p", :ignore => "") still includes it #1465 [michael@schuerig.de]
+
+* Fixed that UrlHelper#link_to_unless/link_to_if used html_escape on the name if no link was to be applied. This is unnecessary and breaks its use with images #1649 [joergd@pobox.com]
+
+* Improved error message for DoubleRenderError
+
+* Fixed routing to allow for testing of *path components #1650 [Nicholas Seckar]
+
+* Added :handle as an option to sortable_element to restrict the drag handle to a given class #1642 [thejohnny]
+
+* Added a bunch of script.aculo.us features #1644, #1677, #1695 [Thomas Fuchs]
+ * Effect.ScrollTo, to smoothly scroll the page to an element
+ * Better Firefox flickering handling on SlideUp/SlideDown
+ * Removed a possible memory leak in IE with draggables
+ * Added support for cancelling dragging my hitting ESC
+ * Added capability to remove draggables/droppables and redeclare sortables in dragdrop.js (this makes it possible to call sortable_element on the same element more than once, e.g. in AJAX returns that modify the sortable element. all current sortable 'stuff' on the element will be discarded and the sortable will be rebuilt)
+ * Always reset background color on Effect.Highlight; this make change backwards-compatibility, to be sure include style="background-color:(target-color)" on your elements or else elements will fall back to their CSS rules (which is a good thing in most circumstances)
+ * Removed circular references from element to prevent memory leaks (still not completely gone in IE)
+ * Changes to class extension in effects.js
+ * Make Effect.Highlight restore any previously set background color when finishing (makes effect work with CSS classes that set a background color)
+ * Fixed myriads of memory leaks in IE and Gecko-based browsers [David ZĂĽlke]
+ * Added incremental and local autocompleting and loads of documentation to controls.js [Ivan Krstic]
+ * Extended the auto_complete_field helper to accept tokens option
+ * Changed object extension mechanism to favor Object.extend to make script.aculo.us easily adaptable to support 3rd party libs like IE7.js [David ZĂĽlke]
+
+* Fixed that named routes didn't use the default values for action and possible other parameters #1534 [Nicholas Seckar]
+
+* Fixed JavascriptHelper#visual_effect to use camelize such that :blind_up will work #1639 [pelletierm@eastmedia.net]
+
+* Fixed that a SessionRestoreError was thrown if a model object was placed in the session that wasn't available to all controllers. This means that it's no longer necessary to use the 'model :post' work-around in ApplicationController to have a Post model in your session.
+
+
+*1.9.0* (6 July, 2005)
+
+* Added logging of the request URI in the benchmark statement (makes it easy to grep for slow actions)
+
+* Added javascript_include_tag :defaults shortcut that'll include all the default javascripts included with Action Pack (prototype, effects, controls, dragdrop)
+
+* Cache several controller variables that are expensive to calculate #1229 [skaes@web.de]
+
+* The session class backing CGI::Session::ActiveRecordStore may be replaced with any class that duck-types with a subset of Active Record. See docs for details #1238 [skaes@web.de]
+
+* Fixed that hashes was not working properly when passed by GET to lighttpd #849 [Nicholas Seckar]
+
+* Fixed assert_template nil will be true when no template was rendered #1565 [maceywj@telus.net]
+
+* Added :prompt option to FormOptions#select (and the users of it, like FormOptions#select_country etc) to create "Please select" style descriptors #1181 [Michael Schuerig]
+
+* Added JavascriptHelper#update_element_function, which returns a Javascript function (or expression) that'll update a DOM element according to the options passed #933 [mortonda@dgrmm.net]. Examples:
+
+ <%= update_element_function("products", :action => :insert, :position => :bottom, :content => "
+ <% end %>
+
+* Added :field_name option to DateHelper#select_(year|month|day) to deviate from the year/month/day defaults #1266 [Marcel Molina]
+
+* Added JavascriptHelper#draggable_element and JavascriptHelper#drop_receiving_element to facilitate easy dragging and dropping through the script.aculo.us libraries #1578 [Thomas Fuchs]
+
+* Added that UrlHelper#mail_to will now also encode the default link title #749 [f.svehla@gmail.com]
+
+* Removed the default option of wrap=virtual on FormHelper#text_area to ensure XHTML compatibility #1300 [thomas@columbus.rr.com]
+
+* Adds the ability to include XML CDATA tags using Builder #1563 [Josh Knowles]. Example:
+
+ xml.cdata! "some text" # =>
+
+* Added evaluation of
+ #
+ # javascript_include_tag "common.javascript", "/elsewhere/cools" # =>
+ #
+ #
+ #
+ # javascript_include_tag :defaults # =>
+ #
+ #
+ # ...
+ # *see below
+ #
+ # If there's an application.js file in your public/javascripts directory,
+ # javascript_include_tag :defaults will automatically include it. This file
+ # facilitates the inclusion of small snippets of JavaScript code, along the lines of
+ # controllers/application.rb and helpers/application_helper.rb.
+ def javascript_include_tag(*sources)
+ options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
+
+ if sources.include?(:defaults)
+ sources = sources[0..(sources.index(:defaults))] +
+ @@javascript_default_sources.dup +
+ sources[(sources.index(:defaults) + 1)..sources.length]
+
+ sources.delete(:defaults)
+ sources << "application" if defined?(RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/public/javascripts/application.js")
+ end
+
+ sources.collect { |source|
+ source = javascript_path(source)
+ content_tag("script", "", { "type" => "text/javascript", "src" => source }.merge(options))
+ }.join("\n")
+ end
+
+ # Register one or more additional JavaScript files to be included when
+ #
+ # javascript_include_tag :defaults
+ #
+ # is called. This method is intended to be called only from plugin initialization
+ # to register extra .js files the plugin installed in public/javascripts.
+ def self.register_javascript_include_default(*sources)
+ @@javascript_default_sources.concat(sources)
+ end
+
+ def self.reset_javascript_include_default #:nodoc:
+ @@javascript_default_sources = JAVASCRIPT_DEFAULT_SOURCES.dup
+ end
+
+ # Returns path to a stylesheet asset. Example:
+ #
+ # stylesheet_path "style" # => /stylesheets/style.css
+ def stylesheet_path(source)
+ compute_public_path(source, 'stylesheets', 'css')
+ end
+
+ # Returns a css link tag per source given as argument. Examples:
+ #
+ # stylesheet_link_tag "style" # =>
+ #
+ #
+ # stylesheet_link_tag "style", :media => "all" # =>
+ #
+ #
+ # stylesheet_link_tag "random.styles", "/css/stylish" # =>
+ #
+ #
+ def stylesheet_link_tag(*sources)
+ options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
+ sources.collect { |source|
+ source = stylesheet_path(source)
+ tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
+ }.join("\n")
+ end
+
+ # Returns path to an image asset. Example:
+ #
+ # The +src+ can be supplied as a...
+ # * full path, like "/my_images/image.gif"
+ # * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
+ # * file name without extension, like "logo", that gets expanded to "/images/logo.png"
+ def image_path(source)
+ compute_public_path(source, 'images', 'png')
+ end
+
+ # Returns an image tag converting the +options+ into html options on the tag, but with these special cases:
+ #
+ # * :alt - If no alt text is given, the file name part of the +src+ is used (capitalized and without the extension)
+ # * :size - Supplied as "XxY", so "30x45" becomes width="30" and height="45"
+ #
+ # The +src+ can be supplied as a...
+ # * full path, like "/my_images/image.gif"
+ # * file name, like "rss.gif", that gets expanded to "/images/rss.gif"
+ # * file name without extension, like "logo", that gets expanded to "/images/logo.png"
+ def image_tag(source, options = {})
+ options.symbolize_keys!
+
+ options[:src] = image_path(source)
+ options[:alt] ||= File.basename(options[:src], '.*').split('.').first.capitalize
+
+ if options[:size]
+ options[:width], options[:height] = options[:size].split("x")
+ options.delete :size
+ end
+
+ tag("img", options)
+ end
+
+ private
+ def compute_public_path(source, dir, ext)
+ source = "/#{dir}/#{source}" unless source.first == "/" || source.include?(":")
+ source << ".#{ext}" unless source.split("/").last.include?(".")
+ source << '?' + rails_asset_id(source) if defined?(RAILS_ROOT) && %r{^[-a-z]+://} !~ source
+ source = "#{@controller.request.relative_url_root}#{source}" unless %r{^[-a-z]+://} =~ source
+ source = ActionController::Base.asset_host + source unless source.include?(":")
+ source
+ end
+
+ def rails_asset_id(source)
+ ENV["RAILS_ASSET_ID"] ||
+ File.mtime("#{RAILS_ROOT}/public/#{source}").to_i.to_s rescue ""
+ end
+ end
+ end
+end
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb
new file mode 100644
index 0000000..1d53be5
--- /dev/null
+++ b/vendor/rails/actionpack/lib/action_view/helpers/benchmark_helper.rb
@@ -0,0 +1,24 @@
+require 'benchmark'
+
+module ActionView
+ module Helpers
+ module BenchmarkHelper
+ # Measures the execution time of a block in a template and reports the result to the log. Example:
+ #
+ # <% benchmark "Notes section" do %>
+ # <%= expensive_notes_operation %>
+ # <% end %>
+ #
+ # Will add something like "Notes section (0.34523)" to the log.
+ #
+ # You may give an optional logger level as the second argument
+ # (:debug, :info, :warn, :error). The default is :info.
+ def benchmark(message = "Benchmarking", level = :info)
+ if @logger
+ real = Benchmark.realtime { yield }
+ @logger.send level, "#{message} (#{'%.5f' % real})"
+ end
+ end
+ end
+ end
+end
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb
new file mode 100644
index 0000000..de2707a
--- /dev/null
+++ b/vendor/rails/actionpack/lib/action_view/helpers/cache_helper.rb
@@ -0,0 +1,10 @@
+module ActionView
+ module Helpers
+ # See ActionController::Caching::Fragments for usage instructions.
+ module CacheHelper
+ def cache(name = {}, &block)
+ @controller.cache_erb_fragment(block, name)
+ end
+ end
+ end
+end
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb
new file mode 100644
index 0000000..28b3e29
--- /dev/null
+++ b/vendor/rails/actionpack/lib/action_view/helpers/capture_helper.rb
@@ -0,0 +1,128 @@
+module ActionView
+ module Helpers
+ # Capture lets you extract parts of code which
+ # can be used in other points of the template or even layout file.
+ #
+ # == Capturing a block into an instance variable
+ #
+ # <% @script = capture do %>
+ # [some html...]
+ # <% end %>
+ #
+ # == Add javascript to header using content_for
+ #
+ # content_for("name") is a wrapper for capture which will
+ # make the fragment available by name to a yielding layout or template.
+ #
+ # layout.rhtml:
+ #
+ #
+ #
+ # layout with js
+ #
+ #
+ #
+ # <%= yield %>
+ #
+ #
+ #
+ # view.rhtml
+ #
+ # This page shows an alert box!
+ #
+ # <% content_for("script") do %>
+ # alert('hello world')
+ # <% end %>
+ #
+ # Normal view text
+ module CaptureHelper
+ # Capture allows you to extract a part of the template into an
+ # instance variable. You can use this instance variable anywhere
+ # in your templates and even in your layout.
+ #
+ # Example of capture being used in a .rhtml page:
+ #
+ # <% @greeting = capture do %>
+ # Welcome To my shiny new web page!
+ # <% end %>
+ #
+ # Example of capture being used in a .rxml page:
+ #
+ # @greeting = capture do
+ # 'Welcome To my shiny new web page!'
+ # end
+ def capture(*args, &block)
+ # execute the block
+ begin
+ buffer = eval("_erbout", block.binding)
+ rescue
+ buffer = nil
+ end
+
+ if buffer.nil?
+ capture_block(*args, &block)
+ else
+ capture_erb_with_buffer(buffer, *args, &block)
+ end
+ end
+
+ # Calling content_for stores the block of markup for later use.
+ # Subsequently, you can make calls to it by name with yield
+ # in another template or in the layout.
+ #
+ # Example:
+ #
+ # <% content_for("header") do %>
+ # alert('hello world')
+ # <% end %>
+ #
+ # You can use yield :header anywhere in your templates.
+ #
+ # <%= yield :header %>
+ #
+ # NOTE: Beware that content_for is ignored in caches. So you shouldn't use it
+ # for elements that are going to be fragment cached.
+ #
+ # The deprecated way of accessing a content_for block was to use a instance variable
+ # named @@content_for_#{name_of_the_content_block}@. So <%= content_for('footer') %>
+ # would be avaiable as <%= @content_for_footer %>. The preferred notation now is
+ # <%= yield :footer %>.
+ def content_for(name, &block)
+ eval "@content_for_#{name} = (@content_for_#{name} || '') + capture(&block)"
+ end
+
+ private
+ def capture_block(*args, &block)
+ block.call(*args)
+ end
+
+ def capture_erb(*args, &block)
+ buffer = eval("_erbout", block.binding)
+ capture_erb_with_buffer(buffer, *args, &block)
+ end
+
+ def capture_erb_with_buffer(buffer, *args, &block)
+ pos = buffer.length
+ block.call(*args)
+
+ # extract the block
+ data = buffer[pos..-1]
+
+ # replace it in the original with empty string
+ buffer[pos..-1] = ''
+
+ data
+ end
+
+ def erb_content_for(name, &block)
+ eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_erb(&block)"
+ end
+
+ def block_content_for(name, &block)
+ eval "@content_for_#{name} = (@content_for_#{name} || '') + capture_block(&block)"
+ end
+ end
+ end
+end
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb
new file mode 100755
index 0000000..7c65b0d
--- /dev/null
+++ b/vendor/rails/actionpack/lib/action_view/helpers/date_helper.rb
@@ -0,0 +1,313 @@
+require "date"
+
+module ActionView
+ module Helpers
+ # The Date Helper primarily creates select/option tags for different kinds of dates and date elements. All of the select-type methods
+ # share a number of common options that are as follows:
+ #
+ # * :prefix - overwrites the default prefix of "date" used for the select names. So specifying "birthday" would give
+ # birthday[month] instead of date[month] if passed to the select_month method.
+ # * :include_blank - set to true if it should be possible to set an empty date.
+ # * :discard_type - set to true if you want to discard the type part of the select name. If set to true, the select_month
+ # method would use simply "date" (which can be overwritten using :prefix) instead of "date[month]".
+ module DateHelper
+ DEFAULT_PREFIX = 'date' unless const_defined?('DEFAULT_PREFIX')
+
+ # Reports the approximate distance in time between two Time objects or integers.
+ # For example, if the distance is 47 minutes, it'll return
+ # "about 1 hour". See the source for the complete wording list.
+ #
+ # Integers are interpreted as seconds. So,
+ # distance_of_time_in_words(50) returns "less than a minute".
+ #
+ # Set include_seconds to true if you want more detailed approximations if distance < 1 minute
+ def distance_of_time_in_words(from_time, to_time = 0, include_seconds = false)
+ from_time = from_time.to_time if from_time.respond_to?(:to_time)
+ to_time = to_time.to_time if to_time.respond_to?(:to_time)
+ distance_in_minutes = (((to_time - from_time).abs)/60).round
+ distance_in_seconds = ((to_time - from_time).abs).round
+
+ case distance_in_minutes
+ when 0..1
+ return (distance_in_minutes==0) ? 'less than a minute' : '1 minute' unless include_seconds
+ case distance_in_seconds
+ when 0..5 then 'less than 5 seconds'
+ when 6..10 then 'less than 10 seconds'
+ when 11..20 then 'less than 20 seconds'
+ when 21..40 then 'half a minute'
+ when 41..59 then 'less than a minute'
+ else '1 minute'
+ end
+
+ when 2..45 then "#{distance_in_minutes} minutes"
+ when 46..90 then 'about 1 hour'
+ when 90..1440 then "about #{(distance_in_minutes.to_f / 60.0).round} hours"
+ when 1441..2880 then '1 day'
+ when 2881..43220 then "#{(distance_in_minutes / 1440).round} days"
+ when 43201..86400 then 'about 1 month'
+ when 86401..525960 then "#{(distance_in_minutes / 43200).round} months"
+ when 525961..1051920 then 'about 1 year'
+ else "over #{(distance_in_minutes / 525600).round} years"
+ end
+ end
+
+ # Like distance_of_time_in_words, but where to_time is fixed to Time.now.
+ def time_ago_in_words(from_time, include_seconds = false)
+ distance_of_time_in_words(from_time, Time.now, include_seconds)
+ end
+
+ alias_method :distance_of_time_in_words_to_now, :time_ago_in_words
+
+ # Returns a set of select tags (one for year, month, and day) pre-selected for accessing a specified date-based attribute (identified by
+ # +method+) on an object assigned to the template (identified by +object+). It's possible to tailor the selects through the +options+ hash,
+ # which accepts all the keys that each of the individual select builders do (like :use_month_numbers for select_month) as well as a range of
+ # discard options. The discard options are :discard_year, :discard_month and :discard_day. Set to true, they'll
+ # drop the respective select. Discarding the month select will also automatically discard the day select. It's also possible to explicitly
+ # set the order of the tags using the :order option with an array of symbols :year, :month and :day in
+ # the desired order. Symbols may be omitted and the respective select is not included.
+ #
+ # Passing :disabled => true as part of the +options+ will make elements inaccessible for change.
+ #
+ # NOTE: Discarded selects will default to 1. So if no month select is available, January will be assumed.
+ #
+ # Examples:
+ #
+ # date_select("post", "written_on")
+ # date_select("post", "written_on", :start_year => 1995)
+ # date_select("post", "written_on", :start_year => 1995, :use_month_numbers => true,
+ # :discard_day => true, :include_blank => true)
+ # date_select("post", "written_on", :order => [:day, :month, :year])
+ # date_select("user", "birthday", :order => [:month, :day])
+ #
+ # The selects are prepared for multi-parameter assignment to an Active Record object.
+ def date_select(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_date_select_tag(options)
+ end
+
+ # Returns a set of select tags (one for year, month, day, hour, and minute) pre-selected for accessing a specified datetime-based
+ # attribute (identified by +method+) on an object assigned to the template (identified by +object+). Examples:
+ #
+ # datetime_select("post", "written_on")
+ # datetime_select("post", "written_on", :start_year => 1995)
+ #
+ # The selects are prepared for multi-parameter assignment to an Active Record object.
+ def datetime_select(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_datetime_select_tag(options)
+ end
+
+ # Returns a set of html select-tags (one for year, month, and day) pre-selected with the +date+.
+ def select_date(date = Date.today, options = {})
+ select_year(date, options) + select_month(date, options) + select_day(date, options)
+ end
+
+ # Returns a set of html select-tags (one for year, month, day, hour, and minute) pre-selected with the +datetime+.
+ def select_datetime(datetime = Time.now, options = {})
+ select_year(datetime, options) + select_month(datetime, options) + select_day(datetime, options) +
+ select_hour(datetime, options) + select_minute(datetime, options)
+ end
+
+ # Returns a set of html select-tags (one for hour and minute)
+ def select_time(datetime = Time.now, options = {})
+ h = select_hour(datetime, options) + select_minute(datetime, options) + (options[:include_seconds] ? select_second(datetime, options) : '')
+ end
+
+ # Returns a select tag with options for each of the seconds 0 through 59 with the current second selected.
+ # The second can also be substituted for a second number.
+ # Override the field name using the :field_name option, 'second' by default.
+ def select_second(datetime, options = {})
+ second_options = []
+
+ 0.upto(59) do |second|
+ second_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.sec) == second) ?
+ %(\n) :
+ %(\n)
+ )
+ end
+
+ select_html(options[:field_name] || 'second', second_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
+ end
+
+ # Returns a select tag with options for each of the minutes 0 through 59 with the current minute selected.
+ # Also can return a select tag with options by minute_step from 0 through 59 with the 00 minute selected
+ # The minute can also be substituted for a minute number.
+ # Override the field name using the :field_name option, 'minute' by default.
+ def select_minute(datetime, options = {})
+ minute_options = []
+
+ 0.step(59, options[:minute_step] || 1) do |minute|
+ minute_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.min) == minute) ?
+ %(\n) :
+ %(\n)
+ )
+ end
+
+ select_html(options[:field_name] || 'minute', minute_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
+ end
+
+ # Returns a select tag with options for each of the hours 0 through 23 with the current hour selected.
+ # The hour can also be substituted for a hour number.
+ # Override the field name using the :field_name option, 'hour' by default.
+ def select_hour(datetime, options = {})
+ hour_options = []
+
+ 0.upto(23) do |hour|
+ hour_options << ((datetime && (datetime.kind_of?(Fixnum) ? datetime : datetime.hour) == hour) ?
+ %(\n) :
+ %(\n)
+ )
+ end
+
+ select_html(options[:field_name] || 'hour', hour_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
+ end
+
+ # Returns a select tag with options for each of the days 1 through 31 with the current day selected.
+ # The date can also be substituted for a hour number.
+ # Override the field name using the :field_name option, 'day' by default.
+ def select_day(date, options = {})
+ day_options = []
+
+ 1.upto(31) do |day|
+ day_options << ((date && (date.kind_of?(Fixnum) ? date : date.day) == day) ?
+ %(\n) :
+ %(\n)
+ )
+ end
+
+ select_html(options[:field_name] || 'day', day_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
+ end
+
+ # Returns a select tag with options for each of the months January through December with the current month selected.
+ # The month names are presented as keys (what's shown to the user) and the month numbers (1-12) are used as values
+ # (what's submitted to the server). It's also possible to use month numbers for the presentation instead of names --
+ # set the :use_month_numbers key in +options+ to true for this to happen. If you want both numbers and names,
+ # set the :add_month_numbers key in +options+ to true. Examples:
+ #
+ # select_month(Date.today) # Will use keys like "January", "March"
+ # select_month(Date.today, :use_month_numbers => true) # Will use keys like "1", "3"
+ # select_month(Date.today, :add_month_numbers => true) # Will use keys like "1 - January", "3 - March"
+ #
+ # Override the field name using the :field_name option, 'month' by default.
+ #
+ # If you would prefer to show month names as abbreviations, set the
+ # :use_short_month key in +options+ to true.
+ def select_month(date, options = {})
+ month_options = []
+ month_names = options[:use_short_month] ? Date::ABBR_MONTHNAMES : Date::MONTHNAMES
+
+ 1.upto(12) do |month_number|
+ month_name = if options[:use_month_numbers]
+ month_number
+ elsif options[:add_month_numbers]
+ month_number.to_s + ' - ' + month_names[month_number]
+ else
+ month_names[month_number]
+ end
+
+ month_options << ((date && (date.kind_of?(Fixnum) ? date : date.month) == month_number) ?
+ %(\n) :
+ %(\n)
+ )
+ end
+
+ select_html(options[:field_name] || 'month', month_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
+ end
+
+ # Returns a select tag with options for each of the five years on each side of the current, which is selected. The five year radius
+ # can be changed using the :start_year and :end_year keys in the +options+. Both ascending and descending year
+ # lists are supported by making :start_year less than or greater than :end_year. The date can also be
+ # substituted for a year given as a number. Example:
+ #
+ # select_year(Date.today, :start_year => 1992, :end_year => 2007) # ascending year values
+ # select_year(Date.today, :start_year => 2005, :end_year => 1900) # descending year values
+ #
+ # Override the field name using the :field_name option, 'year' by default.
+ def select_year(date, options = {})
+ year_options = []
+ y = date ? (date.kind_of?(Fixnum) ? (y = (date == 0) ? Date.today.year : date) : date.year) : Date.today.year
+
+ start_year, end_year = (options[:start_year] || y-5), (options[:end_year] || y+5)
+ step_val = start_year < end_year ? 1 : -1
+
+ start_year.step(end_year, step_val) do |year|
+ year_options << ((date && (date.kind_of?(Fixnum) ? date : date.year) == year) ?
+ %(\n) :
+ %(\n)
+ )
+ end
+
+ select_html(options[:field_name] || 'year', year_options, options[:prefix], options[:include_blank], options[:discard_type], options[:disabled])
+ end
+
+ private
+ def select_html(type, options, prefix = nil, include_blank = false, discard_type = false, disabled = false)
+ select_html = %(\n"
+ end
+
+ def leading_zero_on_single_digits(number)
+ number > 9 ? number : "0#{number}"
+ end
+ end
+
+ class InstanceTag #:nodoc:
+ include DateHelper
+
+ def to_date_select_tag(options = {})
+ defaults = { :discard_type => true }
+ options = defaults.merge(options)
+ options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
+ value = value(object)
+ date = options[:include_blank] ? (value || 0) : (value || Date.today)
+
+ date_select = ''
+ options[:order] = [:month, :year, :day] if options[:month_before_year] # For backwards compatibility
+ options[:order] ||= [:year, :month, :day]
+
+ position = {:year => 1, :month => 2, :day => 3}
+
+ discard = {}
+ discard[:year] = true if options[:discard_year]
+ discard[:month] = true if options[:discard_month]
+ discard[:day] = true if options[:discard_day] or options[:discard_month]
+
+ options[:order].each do |param|
+ date_select << self.send("select_#{param}", date, options_with_prefix.call(position[param])) unless discard[param]
+ end
+
+ date_select
+ end
+
+ def to_datetime_select_tag(options = {})
+ defaults = { :discard_type => true }
+ options = defaults.merge(options)
+ options_with_prefix = Proc.new { |position| options.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
+ value = value(object)
+ datetime = options[:include_blank] ? (value || nil) : (value || Time.now)
+
+ datetime_select = select_year(datetime, options_with_prefix.call(1))
+ datetime_select << select_month(datetime, options_with_prefix.call(2)) unless options[:discard_month]
+ datetime_select << select_day(datetime, options_with_prefix.call(3)) unless options[:discard_day] || options[:discard_month]
+ datetime_select << ' — ' + select_hour(datetime, options_with_prefix.call(4)) unless options[:discard_hour]
+ datetime_select << ' : ' + select_minute(datetime, options_with_prefix.call(5)) unless options[:discard_minute] || options[:discard_hour]
+
+ datetime_select
+ end
+ end
+
+ class FormBuilder
+ def date_select(method, options = {})
+ @template.date_select(@object_name, method, options.merge(:object => @object))
+ end
+
+ def datetime_select(method, options = {})
+ @template.datetime_select(@object_name, method, options.merge(:object => @object))
+ end
+ end
+ end
+end
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb
new file mode 100644
index 0000000..8baea6f
--- /dev/null
+++ b/vendor/rails/actionpack/lib/action_view/helpers/debug_helper.rb
@@ -0,0 +1,17 @@
+module ActionView
+ module Helpers
+ # Provides a set of methods for making it easier to locate problems.
+ module DebugHelper
+ # Returns a
-tag set with the +object+ dumped by YAML. Very readable way to inspect an object.
+ def debug(object)
+ begin
+ Marshal::dump(object)
+ "
#{h(object.to_yaml).gsub(" ", " ")}
"
+ rescue Object => e
+ # Object couldn't be dumped, perhaps because of singleton methods -- this is the fallback
+ "#{h(object.inspect)}"
+ end
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb
new file mode 100644
index 0000000..43185e3
--- /dev/null
+++ b/vendor/rails/actionpack/lib/action_view/helpers/form_helper.rb
@@ -0,0 +1,439 @@
+require 'cgi'
+require File.dirname(__FILE__) + '/date_helper'
+require File.dirname(__FILE__) + '/tag_helper'
+
+module ActionView
+ module Helpers
+ # Provides a set of methods for working with forms and especially forms related to objects assigned to the template.
+ # The following is an example of a complete form for a person object that works for both creates and updates built
+ # with all the form helpers. The @person object was assigned by an action on the controller:
+ #
+ #
+ # ...is compiled to:
+ #
+ #
+ #
+ # If the object name contains square brackets the id for the object will be inserted. Example:
+ #
+ # <%= text_field "person[]", "name" %>
+ #
+ # ...becomes:
+ #
+ #
+ #
+ # If the helper is being used to generate a repetitive sequence of similar form elements, for example in a partial
+ # used by render_collection_of_partials, the "index" option may come in handy. Example:
+ #
+ # <%= text_field "person", "name", "index" => 1 %>
+ #
+ # becomes
+ #
+ #
+ #
+ # There's also methods for helping to build form tags in link:classes/ActionView/Helpers/FormOptionsHelper.html,
+ # link:classes/ActionView/Helpers/DateHelper.html, and link:classes/ActionView/Helpers/ActiveRecordHelper.html
+ module FormHelper
+ # Creates a form and a scope around a specific model object, which is then used as a base for questioning about
+ # values for the fields. Examples:
+ #
+ # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
+ # First name: <%= f.text_field :first_name %>
+ # Last name : <%= f.text_field :last_name %>
+ # Biography : <%= f.text_area :biography %>
+ # Admin? : <%= f.check_box :admin %>
+ # <% end %>
+ #
+ # Worth noting is that the form_for tag is called in a ERb evaluation block, not a ERb output block. So that's <% %>,
+ # not <%= %>. Also worth noting is that the form_for yields a form_builder object, in this example as f, which emulates
+ # the API for the stand-alone FormHelper methods, but without the object name. So instead of text_field :person, :name,
+ # you get away with f.text_field :name.
+ #
+ # That in itself is a modest increase in comfort. The big news is that form_for allows us to more easily escape the instance
+ # variable convention, so while the stand-alone approach would require text_field :person, :name, :object => person
+ # to work with local variables instead of instance ones, the form_for calls remain the same. You simply declare once with
+ # :person, person and all subsequent field calls save :person and :object => person.
+ #
+ # Also note that form_for doesn't create an exclusive scope. It's still possible to use both the stand-alone FormHelper methods
+ # and methods from FormTagHelper. Example:
+ #
+ # <% form_for :person, @person, :url => { :action => "update" } do |f| %>
+ # First name: <%= f.text_field :first_name %>
+ # Last name : <%= f.text_field :last_name %>
+ # Biography : <%= text_area :person, :biography %>
+ # Admin? : <%= check_box_tag "person[admin]", @person.company.admin? %>
+ # <% end %>
+ #
+ # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
+ # Like collection_select and datetime_select.
+ #
+ # Html attributes for the form tag can be given as :html => {...}. Example:
+ #
+ # <% form_for :person, @person, :html => {:id => 'person_form'} do |f| %>
+ # ...
+ # <% end %>
+ #
+ # You can also build forms using a customized FormBuilder class. Subclass FormBuilder and override or define some more helpers,
+ # then use your custom builder like so:
+ #
+ # <% form_for :person, @person, :url => { :action => "update" }, :builder => LabellingFormBuilder do |f| %>
+ # <%= f.text_field :first_name %>
+ # <%= f.text_field :last_name %>
+ # <%= text_area :person, :biography %>
+ # <%= check_box_tag "person[admin]", @person.company.admin? %>
+ # <% end %>
+ #
+ # In many cases you will want to wrap the above in another helper, such as:
+ #
+ # def labelled_form_for(name, object, options, &proc)
+ # form_for(name, object, options.merge(:builder => LabellingFormBuiler), &proc)
+ # end
+ #
+ def form_for(object_name, *args, &proc)
+ raise ArgumentError, "Missing block" unless block_given?
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ concat(form_tag(options.delete(:url) || {}, options.delete(:html) || {}), proc.binding)
+ fields_for(object_name, *(args << options), &proc)
+ concat('', proc.binding)
+ end
+
+ # Creates a scope around a specific model object like form_for, but doesn't create the form tags themselves. This makes
+ # fields_for suitable for specifying additional model objects in the same form. Example:
+ #
+ # <% form_for :person, @person, :url => { :action => "update" } do |person_form| %>
+ # First name: <%= person_form.text_field :first_name %>
+ # Last name : <%= person_form.text_field :last_name %>
+ #
+ # <% fields_for :permission, @person.permission do |permission_fields| %>
+ # Admin? : <%= permission_fields.check_box :admin %>
+ # <% end %>
+ # <% end %>
+ #
+ # Note: This also works for the methods in FormOptionHelper and DateHelper that are designed to work with an object as base.
+ # Like collection_select and datetime_select.
+ def fields_for(object_name, *args, &proc)
+ raise ArgumentError, "Missing block" unless block_given?
+ options = args.last.is_a?(Hash) ? args.pop : {}
+ object = args.first
+ yield((options[:builder] || FormBuilder).new(object_name, object, self, options, proc))
+ end
+
+ # Returns an input tag of the "text" type tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+.
+ #
+ # Examples (call, result):
+ # text_field("post", "title", "size" => 20)
+ #
+ def text_field(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("text", options)
+ end
+
+ # Works just like text_field, but returns an input tag of the "password" type instead.
+ def password_field(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("password", options)
+ end
+
+ # Works just like text_field, but returns an input tag of the "hidden" type instead.
+ def hidden_field(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("hidden", options)
+ end
+
+ # Works just like text_field, but returns an input tag of the "file" type instead, which won't have a default value.
+ def file_field(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_input_field_tag("file", options)
+ end
+
+ # Returns a textarea opening and closing tag set tailored for accessing a specified attribute (identified by +method+)
+ # on an object assigned to the template (identified by +object+). Additional options on the input tag can be passed as a
+ # hash with +options+.
+ #
+ # Example (call, result):
+ # text_area("post", "body", "cols" => 20, "rows" => 40)
+ #
+ def text_area(object_name, method, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_text_area_tag(options)
+ end
+
+ # Returns a checkbox tag tailored for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). It's intended that +method+ returns an integer and if that
+ # integer is above zero, then the checkbox is checked. Additional options on the input tag can be passed as a
+ # hash with +options+. The +checked_value+ defaults to 1 while the default +unchecked_value+
+ # is set to 0 which is convenient for boolean values. Usually unchecked checkboxes don't post anything.
+ # We work around this problem by adding a hidden value with the same name as the checkbox.
+ #
+ # Example (call, result). Imagine that @post.validated? returns 1:
+ # check_box("post", "validated")
+ #
+ #
+ #
+ # Example (call, result). Imagine that @puppy.gooddog returns no:
+ # check_box("puppy", "gooddog", {}, "yes", "no")
+ #
+ #
+ def check_box(object_name, method, options = {}, checked_value = "1", unchecked_value = "0")
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_check_box_tag(options, checked_value, unchecked_value)
+ end
+
+ # Returns a radio button tag for accessing a specified attribute (identified by +method+) on an object
+ # assigned to the template (identified by +object+). If the current value of +method+ is +tag_value+ the
+ # radio button will be checked. Additional options on the input tag can be passed as a
+ # hash with +options+.
+ # Example (call, result). Imagine that @post.category returns "rails":
+ # radio_button("post", "category", "rails")
+ # radio_button("post", "category", "java")
+ #
+ #
+ #
+ def radio_button(object_name, method, tag_value, options = {})
+ InstanceTag.new(object_name, method, self, nil, options.delete(:object)).to_radio_button_tag(tag_value, options)
+ end
+ end
+
+ class InstanceTag #:nodoc:
+ include Helpers::TagHelper
+
+ attr_reader :method_name, :object_name
+
+ DEFAULT_FIELD_OPTIONS = { "size" => 30 }.freeze unless const_defined?(:DEFAULT_FIELD_OPTIONS)
+ DEFAULT_RADIO_OPTIONS = { }.freeze unless const_defined?(:DEFAULT_RADIO_OPTIONS)
+ DEFAULT_TEXT_AREA_OPTIONS = { "cols" => 40, "rows" => 20 }.freeze unless const_defined?(:DEFAULT_TEXT_AREA_OPTIONS)
+ DEFAULT_DATE_OPTIONS = { :discard_type => true }.freeze unless const_defined?(:DEFAULT_DATE_OPTIONS)
+
+ def initialize(object_name, method_name, template_object, local_binding = nil, object = nil)
+ @object_name, @method_name = object_name.to_s.dup, method_name.to_s.dup
+ @template_object, @local_binding = template_object, local_binding
+ @object = object
+ if @object_name.sub!(/\[\]$/,"")
+ if object ||= @template_object.instance_variable_get("@#{Regexp.last_match.pre_match}") and object.respond_to?(:id_before_type_cast)
+ @auto_index = object.id_before_type_cast
+ else
+ raise ArgumentError, "object[] naming but object param and @object var don't exist or don't respond to id_before_type_cast: #{object.inspect}"
+ end
+ end
+ end
+
+ def to_input_field_tag(field_type, options = {})
+ options = options.stringify_keys
+ options["size"] ||= options["maxlength"] || DEFAULT_FIELD_OPTIONS["size"]
+ options = DEFAULT_FIELD_OPTIONS.merge(options)
+ if field_type == "hidden"
+ options.delete("size")
+ end
+ options["type"] = field_type
+ options["value"] ||= value_before_type_cast(object) unless field_type == "file"
+ add_default_name_and_id(options)
+ tag("input", options)
+ end
+
+ def to_radio_button_tag(tag_value, options = {})
+ options = DEFAULT_RADIO_OPTIONS.merge(options.stringify_keys)
+ options["type"] = "radio"
+ options["value"] = tag_value
+ if options.has_key?("checked")
+ cv = options.delete "checked"
+ checked = cv == true || cv == "checked"
+ else
+ checked = self.class.radio_button_checked?(value(object), tag_value)
+ end
+ options["checked"] = "checked" if checked
+ pretty_tag_value = tag_value.to_s.gsub(/\s/, "_").gsub(/\W/, "").downcase
+ options["id"] = defined?(@auto_index) ?
+ "#{@object_name}_#{@auto_index}_#{@method_name}_#{pretty_tag_value}" :
+ "#{@object_name}_#{@method_name}_#{pretty_tag_value}"
+ add_default_name_and_id(options)
+ tag("input", options)
+ end
+
+ def to_text_area_tag(options = {})
+ options = DEFAULT_TEXT_AREA_OPTIONS.merge(options.stringify_keys)
+ add_default_name_and_id(options)
+
+ if size = options.delete("size")
+ options["cols"], options["rows"] = size.split("x")
+ end
+
+ content_tag("textarea", html_escape(options.delete('value') || value_before_type_cast(object)), options)
+ end
+
+ def to_check_box_tag(options = {}, checked_value = "1", unchecked_value = "0")
+ options = options.stringify_keys
+ options["type"] = "checkbox"
+ options["value"] = checked_value
+ if options.has_key?("checked")
+ cv = options.delete "checked"
+ checked = cv == true || cv == "checked"
+ else
+ checked = self.class.check_box_checked?(value(object), checked_value)
+ end
+ options["checked"] = "checked" if checked
+ add_default_name_and_id(options)
+ tag("input", options) << tag("input", "name" => options["name"], "type" => "hidden", "value" => unchecked_value)
+ end
+
+ def to_date_tag()
+ defaults = DEFAULT_DATE_OPTIONS.dup
+ date = value(object) || Date.today
+ options = Proc.new { |position| defaults.merge(:prefix => "#{@object_name}[#{@method_name}(#{position}i)]") }
+ html_day_select(date, options.call(3)) +
+ html_month_select(date, options.call(2)) +
+ html_year_select(date, options.call(1))
+ end
+
+ def to_boolean_select_tag(options = {})
+ options = options.stringify_keys
+ add_default_name_and_id(options)
+ value = value(object)
+ tag_text = ""
+ end
+
+ def to_content_tag(tag_name, options = {})
+ content_tag(tag_name, value(object), options)
+ end
+
+ def object
+ @object || @template_object.instance_variable_get("@#{@object_name}")
+ end
+
+ def value(object)
+ self.class.value(object, @method_name)
+ end
+
+ def value_before_type_cast(object)
+ self.class.value_before_type_cast(object, @method_name)
+ end
+
+ class << self
+ def value(object, method_name)
+ object.send method_name unless object.nil?
+ end
+
+ def value_before_type_cast(object, method_name)
+ unless object.nil?
+ object.respond_to?(method_name + "_before_type_cast") ?
+ object.send(method_name + "_before_type_cast") :
+ object.send(method_name)
+ end
+ end
+
+ def check_box_checked?(value, checked_value)
+ case value
+ when TrueClass, FalseClass
+ value
+ when NilClass
+ false
+ when Integer
+ value != 0
+ when String
+ value == checked_value
+ else
+ value.to_i != 0
+ end
+ end
+
+ def radio_button_checked?(value, checked_value)
+ value.to_s == checked_value.to_s
+ end
+ end
+
+ private
+ def add_default_name_and_id(options)
+ if options.has_key?("index")
+ options["name"] ||= tag_name_with_index(options["index"])
+ options["id"] ||= tag_id_with_index(options["index"])
+ options.delete("index")
+ elsif defined?(@auto_index)
+ options["name"] ||= tag_name_with_index(@auto_index)
+ options["id"] ||= tag_id_with_index(@auto_index)
+ else
+ options["name"] ||= tag_name
+ options["id"] ||= tag_id
+ end
+ end
+
+ def tag_name
+ "#{@object_name}[#{@method_name}]"
+ end
+
+ def tag_name_with_index(index)
+ "#{@object_name}[#{index}][#{@method_name}]"
+ end
+
+ def tag_id
+ "#{@object_name}_#{@method_name}"
+ end
+
+ def tag_id_with_index(index)
+ "#{@object_name}_#{index}_#{@method_name}"
+ end
+ end
+
+ class FormBuilder #:nodoc:
+ # The methods which wrap a form helper call.
+ class_inheritable_accessor :field_helpers
+ self.field_helpers = (FormHelper.instance_methods - ['form_for'])
+
+ attr_accessor :object_name, :object
+
+ def initialize(object_name, object, template, options, proc)
+ @object_name, @object, @template, @options, @proc = object_name, object, template, options, proc
+ end
+
+ (field_helpers - %w(check_box radio_button)).each do |selector|
+ src = <<-end_src
+ def #{selector}(method, options = {})
+ @template.send(#{selector.inspect}, @object_name, method, options.merge(:object => @object))
+ end
+ end_src
+ class_eval src, __FILE__, __LINE__
+ end
+
+ def check_box(method, options = {}, checked_value = "1", unchecked_value = "0")
+ @template.check_box(@object_name, method, options.merge(:object => @object), checked_value, unchecked_value)
+ end
+
+ def radio_button(method, tag_value, options = {})
+ @template.radio_button(@object_name, method, tag_value, options.merge(:object => @object))
+ end
+ end
+ end
+end
diff --git a/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb
new file mode 100644
index 0000000..12a5f7f
--- /dev/null
+++ b/vendor/rails/actionpack/lib/action_view/helpers/form_options_helper.rb
@@ -0,0 +1,363 @@
+require 'cgi'
+require 'erb'
+require File.dirname(__FILE__) + '/form_helper'
+
+module ActionView
+ module Helpers
+ # Provides a number of methods for turning different kinds of containers into a set of option tags.
+ # == Options
+ # The collection_select, country_select, select,
+ # and time_zone_select methods take an options parameter,
+ # a hash.
+ #
+ # * :include_blank - set to true if the first option element of the select element is a blank. Useful if there is not a default value required for the select element. For example,
+ #
+ # select("post", "category", Post::CATEGORIES, {:include_blank => true})
+ #
+ # could become:
+ #
+ #
+ #
+ # * :prompt - set to true or a prompt string. When the select element doesn't have a value yet, this prepends an option with a generic prompt -- "Please select" -- or the given prompt string.
+ #
+ # Another common case is a select tag for an belongs_to-associated object. For example,
+ #
+ # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] })
+ #
+ # could become:
+ #
+ #
+ module FormOptionsHelper
+ include ERB::Util
+
+ # Create a select tag and a series of contained option tags for the provided object and method.
+ # The option currently held by the object will be selected, provided that the object is available.
+ # See options_for_select for the required format of the choices parameter.
+ #
+ # Example with @post.person_id => 1:
+ # select("post", "person_id", Person.find_all.collect {|p| [ p.name, p.id ] }, { :include_blank => true })
+ #
+ # could become:
+ #
+ #
+ #
+ # This can be used to provide a default set of options in the standard way: before rendering the create form, a
+ # new model instance is assigned the default options and bound to @model_name. Usually this model is not saved
+ # to the database. Instead, a second model object is created when the create request is received.
+ # This allows the user to submit a form page more than once with the expected results of creating multiple records.
+ # In addition, this allows a single partial to be used to generate form inputs for both edit and create forms.
+ #
+ # By default, post.person_id is the selected option. Specify :selected => value to use a different selection
+ # or :selected => nil to leave all options unselected.
+ def select(object, method, choices, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_select_tag(choices, options, html_options)
+ end
+
+ # Return select and option tags for the given object and method using options_from_collection_for_select to generate the list of option tags.
+ def collection_select(object, method, collection, value_method, text_method, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_collection_select_tag(collection, value_method, text_method, options, html_options)
+ end
+
+ # Return select and option tags for the given object and method, using country_options_for_select to generate the list of option tags.
+ def country_select(object, method, priority_countries = nil, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_country_select_tag(priority_countries, options, html_options)
+ end
+
+ # Return select and option tags for the given object and method, using
+ # #time_zone_options_for_select to generate the list of option tags.
+ #
+ # In addition to the :include_blank option documented above,
+ # this method also supports a :model option, which defaults
+ # to TimeZone. This may be used by users to specify a different time
+ # zone model object. (See #time_zone_options_for_select for more
+ # information.)
+ def time_zone_select(object, method, priority_zones = nil, options = {}, html_options = {})
+ InstanceTag.new(object, method, self, nil, options.delete(:object)).to_time_zone_select_tag(priority_zones, options, html_options)
+ end
+
+ # Accepts a container (hash, array, enumerable, your type) and returns a string of option tags. Given a container
+ # where the elements respond to first and last (such as a two-element array), the "lasts" serve as option values and
+ # the "firsts" as option text. Hashes are turned into this form automatically, so the keys become "firsts" and values
+ # become lasts. If +selected+ is specified, the matching "last" or element will get the selected option-tag. +Selected+
+ # may also be an array of values to be selected when using a multiple select.
+ #
+ # Examples (call, result):
+ # options_for_select([["Dollar", "$"], ["Kroner", "DKK"]])
+ # \n
+ #
+ # options_for_select([ "VISA", "MasterCard" ], "MasterCard")
+ # \n
+ #
+ # options_for_select({ "Basic" => "$20", "Plus" => "$40" }, "$40")
+ # \n
+ #
+ # options_for_select([ "VISA", "MasterCard", "Discover" ], ["VISA", "Discover"])
+ # \n\n
+ #
+ # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
+ def options_for_select(container, selected = nil)
+ container = container.to_a if Hash === container
+
+ options_for_select = container.inject([]) do |options, element|
+ if !element.is_a?(String) and element.respond_to?(:first) and element.respond_to?(:last)
+ is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element.last) : element.last == selected) )
+ if is_selected
+ options << ""
+ else
+ options << ""
+ end
+ else
+ is_selected = ( (selected.respond_to?(:include?) && !selected.is_a?(String) ? selected.include?(element) : element == selected) )
+ options << ((is_selected) ? "" : "")
+ end
+ end
+
+ options_for_select.join("\n")
+ end
+
+ # Returns a string of option tags that have been compiled by iterating over the +collection+ and assigning the
+ # the result of a call to the +value_method+ as the option value and the +text_method+ as the option text.
+ # If +selected_value+ is specified, the element returning a match on +value_method+ will get the selected option tag.
+ #
+ # Example (call, result). Imagine a loop iterating over each +person+ in @project.people to generate an input tag:
+ # options_from_collection_for_select(@project.people, "id", "name")
+ #
+ #
+ # NOTE: Only the option tags are returned, you have to wrap this call in a regular HTML select tag.
+ def options_from_collection_for_select(collection, value_method, text_method, selected_value = nil)
+ options_for_select(
+ collection.inject([]) { |options, object| options << [ object.send(text_method), object.send(value_method) ] },
+ selected_value
+ )
+ end
+
+ # Returns a string of option tags, like options_from_collection_for_select, but surrounds them with