diff --git a/.gitignore b/.gitignore index 08a043ec2..58088853f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,19 @@ + +# ide files +.idea +*.iml + +# confid files config/*.yml config/webistrano_config.rb -tmp/* -log/* + +# mac files *~ .DS_Store *.tmproj -Gemfile.lock + +# rails3 default +.bundle +db/*.sqlite3 +log/*.log +tmp/ diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 93a7d8c73..5c00b6228 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,3 +1,10 @@ +1.5 - JS 1.0 +* added rails 3 support (merged from janschumann) + +* added ldap and extra rights (merged from bradly) + +* included auth sources ui in user edit view + edge diff --git a/Gemfile b/Gemfile index 2a4a0eb8a..1a8c934d6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,15 +1,50 @@ -source "http://rubygems.org" - -gem 'bundler', "~>1.0.10" -gem "rails", "2.3.11" -gem "mysql" -gem "erubis" -gem "rake" -gem "syntax", "1.0.0" -gem "capistrano", "2.6.0" -gem "open4", "0.9.3" -gem "exception_notification", "2.3.3.0" +source 'http://rubygems.org' + +gem 'rails', '3.0.20' + +# Bundle edge Rails instead: +# gem 'rails', :git => 'git://github.com/rails/rails.git' + +gem 'mysql2', '~> 0.2.17' + +gem 'net-ssh', :require => 'net/ssh' +gem 'net-scp', :require => 'net/scp' +gem 'net-sftp', :require => 'net/sftp' +gem 'net-ldap', :require => 'net/ldap' +gem 'net-ssh-gateway', :require => 'net/ssh/gateway' +gem 'capistrano' +gem 'highline' +gem 'open4' +gem 'syntax' + +# could not make it work +#gem 'exception_notification', :require => 'exception_notifier' + +gem 'prototype_legacy_helper', '0.0.0', :git => 'git://github.com/rails/prototype_legacy_helper.git' + +# Use unicorn as the web server +# gem 'unicorn' + +# Deploy with Capistrano +# gem 'capistrano' + +# To use debugger (ruby-debug for Ruby 1.8.7+, ruby-debug19 for Ruby 1.9.2+) +# gem 'ruby-debug' +# gem 'ruby-debug19', :require => 'ruby-debug' + +# Bundle the extra gems: +# gem 'bj' +# gem 'nokogiri' +# gem 'sqlite3-ruby', :require => 'sqlite3' +# gem 'aws-s3', :require => 'aws/s3' + +# Bundle gems for the local environment. Make sure to +# put test-only gems in this group so their generators +# and rake tasks are available in development mode: +# group :development, :test do +# gem 'webrat' +# end group :test do - gem "mocha", "0.9.8" + gem "mocha", "0.13.2", :require => false end diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..41ad78d18 --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,115 @@ +GIT + remote: git://github.com/rails/prototype_legacy_helper.git + revision: a2cd95c3e3c1a4f7a9566efdab5ce59c886cb05f + specs: + prototype_legacy_helper (0.0.0) + +GEM + remote: http://rubygems.org/ + specs: + abstract (1.0.0) + actionmailer (3.0.20) + actionpack (= 3.0.20) + mail (~> 2.2.19) + actionpack (3.0.20) + activemodel (= 3.0.20) + activesupport (= 3.0.20) + builder (~> 2.1.2) + erubis (~> 2.6.6) + i18n (~> 0.5.0) + rack (~> 1.2.5) + rack-mount (~> 0.6.14) + rack-test (~> 0.5.7) + tzinfo (~> 0.3.23) + activemodel (3.0.20) + activesupport (= 3.0.20) + builder (~> 2.1.2) + i18n (~> 0.5.0) + activerecord (3.0.20) + activemodel (= 3.0.20) + activesupport (= 3.0.20) + arel (~> 2.0.10) + tzinfo (~> 0.3.23) + activeresource (3.0.20) + activemodel (= 3.0.20) + activesupport (= 3.0.20) + activesupport (3.0.20) + arel (2.0.10) + builder (2.1.2) + capistrano (2.6.0) + highline + net-scp (>= 1.0.0) + net-sftp (>= 2.0.0) + net-ssh (>= 2.0.14) + net-ssh-gateway (>= 1.1.0) + erubis (2.6.6) + abstract (>= 1.0.0) + highline (1.6.19) + i18n (0.5.0) + json (1.8.0) + mail (2.2.20) + activesupport (>= 2.3.6) + i18n (>= 0.4.0) + mime-types (~> 1.16) + treetop (~> 1.4.8) + metaclass (0.0.1) + mime-types (1.24) + mocha (0.13.2) + metaclass (~> 0.0.1) + mysql2 (0.2.20) + net-ldap (0.3.1) + net-scp (1.1.1) + net-ssh (>= 2.6.5) + net-sftp (2.1.2) + net-ssh (>= 2.6.5) + net-ssh (2.6.7) + net-ssh-gateway (1.2.0) + net-ssh (>= 2.6.5) + open4 (0.9.3) + polyglot (0.3.3) + rack (1.2.8) + rack-mount (0.6.14) + rack (>= 1.0.0) + rack-test (0.5.7) + rack (>= 1.0) + rails (3.0.20) + actionmailer (= 3.0.20) + actionpack (= 3.0.20) + activerecord (= 3.0.20) + activeresource (= 3.0.20) + activesupport (= 3.0.20) + bundler (~> 1.0) + railties (= 3.0.20) + railties (3.0.20) + actionpack (= 3.0.20) + activesupport (= 3.0.20) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (~> 0.14.4) + rake (10.1.0) + rdoc (3.12.2) + json (~> 1.4) + syntax (1.0.0) + thor (0.14.6) + treetop (1.4.14) + polyglot + polyglot (>= 0.3.1) + tzinfo (0.3.37) + +PLATFORMS + ruby + +DEPENDENCIES + capistrano + highline + mocha (= 0.13.2) + mysql2 (~> 0.2.17) + net-ldap + net-scp + net-sftp + net-ssh + net-ssh-gateway + open4 + prototype_legacy_helper (= 0.0.0)! + rails (= 3.0.20) + syntax diff --git a/README b/README new file mode 100644 index 000000000..8c5cf7513 --- /dev/null +++ b/README @@ -0,0 +1,25 @@ +== Welcome to Webistrano on Rails 3 including LDAP authentication + + + +== Getting Started + +To install this version just clone the repo into your local directory and run "bundle install". +This should download all needed dependencies. If you encounter problems check if ruby 1.9.x and bundler +(gem install bundle) are installed. + +== Configure Webistrano + +To configure webistrano please remove (or copy the files) the sample files in the config dir and edit it accordingly. + +== First run + +To initialize the database you have to run a rake command. You can execute it via "bundle exec rake db:migrate" this will +execute the migration of the development environment. If you want to migrate or initialize another environment just run +the command like that: "RAILS_ENV=production bundle exec rake db:migrate". + +To do a dry run you can just execute "script/rails s" and a server should be launched on http://localhost:3000. + +== Passenger configuration +TBD + diff --git a/Rakefile b/Rakefile index 3bb0e8592..bb86b5ea4 100644 --- a/Rakefile +++ b/Rakefile @@ -1,10 +1,7 @@ # 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 File.expand_path('../config/application', __FILE__) require 'rake' -require 'rake/testtask' -require 'rake/rdoctask' -require 'tasks/rails' +Www::Application.load_tasks diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 2a081f7f6..9d92ffafe 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,22 +1,33 @@ class ApplicationController < ActionController::Base include BrowserFilters - include ExceptionNotification::Notifiable + + # could not male it work + # include ExceptionNotification::Notifiable include AuthenticatedSystem - + before_filter CASClient::Frameworks::Rails::Filter if WebistranoConfig[:authentication_method] == :cas - before_filter :login_from_cookie, :login_required, :ensure_not_disabled + before_filter :login_from_cookie, :login_required, :ensure_not_disabled, :setup_sidebar_vars around_filter :set_timezone layout 'application' - + helper :all # include all helpers, all the time helper_method :current_stage, :current_project # See ActionController::RequestForgeryProtection for details # Uncomment the :secret if you're not using the cookie session store protect_from_forgery - + protected + + def setup_sidebar_vars + if logged_in? + @sidebar_projects = Project.active.select { |p| current_user.can_view?(p) } + @sidebar_hosts = @sidebar_projects.collect(&:stages).flatten.uniq.collect(&:hosts).flatten.uniq + @sidebar_recipes = @sidebar_projects.collect(&:stages).flatten.uniq.collect(&:recipes).flatten.uniq + @sidebar_users = User.find(:all, :order => "login ASC") + end + end def set_timezone # default timezone is UTC @@ -24,34 +35,59 @@ def set_timezone yield Time.zone = 'UTC' end - + def load_project - @project = Project.find(params[:project_id]) + @project = Project.active.find(params[:project_id] || params[:id]) end - + def load_stage load_project @stage = @project.stages.find(params[:stage_id]) end - + def current_stage @stage end - + def current_project @project end - + def ensure_admin if logged_in? && current_user.admin? return true else - flash[:notice] = "Action not allowed" - redirect_to home_path - return false + handle_no_access end end + def handle_no_access(messsage = "Action not allowed") + flash[:notice] = messsage + redirect_to home_path + return false + end + + def ensure_can_access_project(project=nil) + project ||= @project + @can_access_project = current_user.can_view?(project) or handle_no_access + end + + def ensure_can_manage_projects + @can_manage_projects = current_user.can_manage_projects? or handle_no_access + end + + def ensure_can_edit_project + @can_edit_project = current_user.can_edit?(@project) or handle_no_access + end + + def ensure_can_manage_hosts + current_user.can_manage_hosts? or handle_no_access + end + + def ensure_can_manage_recipes + current_user.can_manage_recipes? or handle_no_access + end + def ensure_not_disabled if logged_in? && current_user.disabled? logout @@ -60,7 +96,7 @@ def ensure_not_disabled return true end end - + def logout self.current_user.forget_me if logged_in? cookies.delete :auth_token @@ -72,5 +108,5 @@ def logout redirect_to "#{CASClient::Frameworks::Rails::Filter.config[:logout_url]}?serviceUrl=#{home_url}" end end - + end diff --git a/app/controllers/auth_sources_controller.rb b/app/controllers/auth_sources_controller.rb new file mode 100644 index 000000000..144ea6941 --- /dev/null +++ b/app/controllers/auth_sources_controller.rb @@ -0,0 +1,64 @@ +class AuthSourcesController < ApplicationController + before_filter :ensure_admin + + def index + @auth_sources = auth_source_class.all + render "auth_sources/index" + end + + def new + @auth_source = auth_source_class.new + render 'auth_sources/new' + end + + def create + @auth_source = auth_source_class.new(params[:auth_source]) + if @auth_source.save + flash[:notice] = 'Successful creation.' + redirect_to :action => 'index' + else + render 'auth_sources/new' + end + end + + def edit + @auth_source = AuthSource.find(params[:id]) + render 'auth_sources/edit' + end + + def update + @auth_source = AuthSource.find(params[:id]) + if @auth_source.update_attributes(params[:auth_source]) + flash[:notice] = 'Successful update.' + redirect_to :action => 'index' + else + render 'auth_sources/edit' + end + end + + def test_connection + @auth_method = AuthSource.find(params[:id]) + begin + @auth_method.test_connection + flash[:notice] = 'Successful connection.' + rescue => text + flash[:error] = "Unable to connect (#{text.message})" + end + redirect_to :action => 'index' + end + + def destroy + @auth_source = AuthSource.find(params[:id]) + unless @auth_source.users.find(:first) + @auth_source.destroy + flash[:notice] = 'Successful deletion.' + end + redirect_to :action => 'index' + end + + protected + + def auth_source_class + AuthSourceLdap + end +end diff --git a/app/controllers/hosts_controller.rb b/app/controllers/hosts_controller.rb index 0ea5a9914..d83c87fcd 100644 --- a/app/controllers/hosts_controller.rb +++ b/app/controllers/hosts_controller.rb @@ -1,5 +1,5 @@ class HostsController < ApplicationController - before_filter :ensure_admin, :only => [:new, :edit, :destroy, :create, :update] + before_filter :ensure_can_manage_hosts, :only => [:new, :edit, :destroy, :create, :update] # GET /hosts # GET /hosts.xml @@ -15,8 +15,8 @@ def index # GET /hosts/1 # GET /hosts/1.xml def show - @host = Host.find(params[:id]) - @stages = @host.stages.uniq.sort_by{|x| x.project.name} + @host = Host.find(params[:id], :include => [{:stages => :project}, :roles]) + @stages = @host.stages.select{|s| current_user.can_view?(s)}.uniq.sort_by{|x| x.project.name} respond_to do |format| format.html # show.rhtml diff --git a/app/controllers/project_configurations_controller.rb b/app/controllers/project_configurations_controller.rb index 4dd324e5a..20e8688f6 100644 --- a/app/controllers/project_configurations_controller.rb +++ b/app/controllers/project_configurations_controller.rb @@ -1,6 +1,7 @@ class ProjectConfigurationsController < ApplicationController before_filter :load_project + before_filter :ensure_can_edit_project, :except => [:show] # GET /projects/1/project_configurations/1 # GET /projects/1/project_configurations/1.xml diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 80ec6b7ce..89ba56959 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,11 +1,18 @@ class ProjectsController < ApplicationController before_filter :load_templates, :only => [:new, :create, :edit, :update] - before_filter :ensure_admin, :only => [:new, :edit, :destroy, :create, :update] + before_filter :load_project, :only => [:show, :destroy, :edit, :update] + before_filter :ensure_can_access_project, :except => [:new, :create, :dashboard, :index] + before_filter :ensure_can_edit_project, :only => [:edit, :update] + before_filter :ensure_can_manage_projects, :only => [:new, :create, :destroy] # GET /projects/dashboard def dashboard - @deployments = Deployment.find(:all, :limit => 3, :order => 'created_at DESC') + @deployments = Deployment.find(:all, + :include => {:stage => :project}, + :conditions => {'stages.project_id' => @sidebar_projects}, + :order => 'deployments.created_at DESC', + :limit => 3) respond_to do |format| format.html # dashboard.rhtml @@ -15,8 +22,10 @@ def dashboard # GET /projects # GET /projects.xml def index - @projects = Project.find(:all, :order => 'name ASC') - + @projects = Project.active.select { |p| + ensure_can_access_project(p) + } + respond_to do |format| format.html # index.rhtml format.xml { render :xml => @projects.to_xml } @@ -26,7 +35,7 @@ def index # GET /projects/1 # GET /projects/1.xml def show - @project = Project.find(params[:id]) + @can_edit_project = current_user.can_edit?(@project) respond_to do |format| format.html # show.rhtml @@ -45,7 +54,6 @@ def new # GET /projects/1;edit def edit - @project = Project.find(params[:id]) end # POST /projects @@ -58,10 +66,11 @@ def create else action_to_render = 'new' end + + @project.user_ids = params[:project][:user_ids] || [] respond_to do |format| if @project.save - @project.clone(@original) if load_clone_original flash[:notice] = 'Project was successfully created.' @@ -77,12 +86,12 @@ def create # PUT /projects/1 # PUT /projects/1.xml def update - @project = Project.find(params[:id]) + @project.user_ids = params[:project][:user_ids] || [] respond_to do |format| if @project.update_attributes(params[:project]) flash[:notice] = 'Project was successfully updated.' - format.html { redirect_to project_url(@project) } + format.html { redirect_to(@project.archived? ? projects_path : project_url(@project))} format.xml { head :ok } else format.html { render :action => "edit" } @@ -94,7 +103,6 @@ def update # DELETE /projects/1 # DELETE /projects/1.xml def destroy - @project = Project.find(params[:id]) @project.destroy respond_to do |format| @@ -117,7 +125,7 @@ def load_templates def load_clone_original if params[:clone] - @original = Project.find(params[:clone]) + @original = Project.active.find(params[:clone]) else false end diff --git a/app/controllers/recipes_controller.rb b/app/controllers/recipes_controller.rb index 6f4af12c7..5ee922487 100644 --- a/app/controllers/recipes_controller.rb +++ b/app/controllers/recipes_controller.rb @@ -1,5 +1,5 @@ class RecipesController < ApplicationController - before_filter :ensure_admin, :only => [:new, :edit, :destroy, :create, :update] + before_filter :ensure_can_manage_recipes, :except => [:show, :index] # GET /recipes # GET /recipes.xml @@ -82,14 +82,8 @@ def destroy def preview @recipe = Recipe.new(params[:recipe]) - respond_to do |format| - format.js { - render :update do |page| - page.replace_html :preview, :partial => "preview", :locals => {:recipe => @recipe} - page.show :preview_fieldset - end - } - end + + render "recipes/_preview", :locals => {:recipe => @recipe}, :layout => false end private diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 8b0fff9ef..c7f61d00f 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -9,6 +9,7 @@ def new def create self.current_user = User.authenticate(params[:login], params[:password]) + if logged_in? if params[:remember_me] == "1" self.current_user.remember_me diff --git a/app/controllers/stage_configurations_controller.rb b/app/controllers/stage_configurations_controller.rb index 0f23bad4d..9fb1cc76d 100644 --- a/app/controllers/stage_configurations_controller.rb +++ b/app/controllers/stage_configurations_controller.rb @@ -1,6 +1,9 @@ class StageConfigurationsController < ApplicationController before_filter :load_stage + before_filter :load_project + before_filter :ensure_can_access_project + before_filter :ensure_can_edit_project, :except => [:dashboard, :index, :show] # GET /project/1/stage/1/stage_configurations/1 # GET /project/1/stage/1/stage_configurations/1.xml diff --git a/app/controllers/stages_controller.rb b/app/controllers/stages_controller.rb index 7990935e9..0aaf121f3 100644 --- a/app/controllers/stages_controller.rb +++ b/app/controllers/stages_controller.rb @@ -1,6 +1,8 @@ class StagesController < ApplicationController before_filter :load_project + before_filter :ensure_can_access_project + before_filter :ensure_can_edit_project, :except => [:dashboard, :index, :show] # GET /projects/1/stages.xml def index @@ -15,7 +17,8 @@ def index def show @stage = current_project.stages.find(params[:id]) @task_list = [['All tasks: ', '']] + @stage.list_tasks.collect{|task| [task[:name], task[:name]]}.sort() - + @can_edit_project = current_user.can_edit?(@project) + respond_to do |format| format.html # show.rhtml format.xml { render :xml => @stage.to_xml } diff --git a/app/controllers/stylesheets_controller.rb b/app/controllers/stylesheets_controller.rb deleted file mode 100644 index 8504d0cd6..000000000 --- a/app/controllers/stylesheets_controller.rb +++ /dev/null @@ -1,14 +0,0 @@ -class StylesheetsController < ApplicationController - - skip_before_filter :login_required - if WebistranoConfig[:authentication_method] == :cas - skip_before_filter CASClient::Frameworks::Rails::Filter - end - - caches_page :application - - def application - render :content_type => 'text/css', :layout => false - end - -end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 31caf3b77..62dafa780 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -12,14 +12,24 @@ def index def new # render new.rhtml @user = User.new + @projects = Project.active end # POST /users # POST /users.xml def create @user = User.new(params[:user]) + @projects = Project.active + if current_user.admin? @user.admin = params[:user][:admin].to_i rescue 0 + + @user.manage_projects = params[:user][:manage_projects] + @user.manage_hosts = params[:user][:manage_hosts] + @user.manage_recipes = params[:user][:manage_recipes] + @user.manage_stages = params[:user][:manage_stages] + + @user.project_ids = params[:user][:project_ids] || [] end respond_to do |format| @@ -28,7 +38,10 @@ def create format.html { redirect_to user_url(@user) } format.xml { head :created, :location => user_url(@user) } else - format.html { render :action => "new" } + format.html { + @projects = Project.active + render :action => "new" + } format.xml { render :xml => @user.errors.to_xml } end end @@ -50,6 +63,7 @@ def show # GET /users/edit/1 def edit @user = User.find(params[:id]) + @projects = Project.active end # PUT /users/1 @@ -60,6 +74,13 @@ def update if current_user.admin? @user.admin = params[:user][:admin].to_i rescue 0 + + @user.manage_projects = params[:user][:manage_projects] + @user.manage_hosts = params[:user][:manage_hosts] + @user.manage_recipes = params[:user][:manage_recipes] + @user.manage_stages = params[:user][:manage_stages] + + @user.project_ids = params[:user][:project_ids] || [] end respond_to do |format| @@ -68,7 +89,10 @@ def update format.html { redirect_to user_url(@user) } format.xml { head :ok } else - format.html { render :action => "edit" } + format.html { + @projects = Project.active + render :action => "edit" + } format.xml { render :xml => @user.errors.to_xml } end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 8c5ba5147..5de09f46e 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,6 +1,4 @@ -# Methods added to this helper will be available to all templates in the application. module ApplicationHelper - def nice_flash(text) render(:partial => 'layouts/flash', :locals => {:text => text}) end @@ -8,77 +6,75 @@ def nice_flash(text) def error_flash(text) render(:partial => 'layouts/flash_error', :locals => {:text => text}) end - + def locking_flash(text) render(:partial => 'layouts/flash_locking', :locals => {:text => text}) end - + def flashed_errors(object_name) obj = instance_variable_get("@#{object_name}") return nil if obj.errors.blank? - - - error_messages = obj.errors.full_messages.map {|msg| content_tag(:li, msg)} - html = content_tag(:p,"#{pluralize(obj.errors.size, 'error')} prohibited this #{object_name.to_s.gsub('_', ' ')} from being saved") - html << content_tag(:div, - content_tag(:ul, error_messages) - ) - - content_for(:flash_content) do + + error_messages = obj.errors.full_messages.map {|msg| content_tag(:li, msg, nil, false)} + + html = content_tag(:p,"#{pluralize(obj.errors.size, 'error')} prohibited this #{object_name.to_s.gsub('_', ' ')} from being saved", nil, false) + html << content_tag(:div, content_tag(:ul, error_messages, nil, false)) + + content_for(:flash_content) do error_flash(html) end end - + def web_friendly_text(text) return text if text.blank? - h(text).gsub("\n",'
').gsub("\r",'') + text.gsub("\n",'
').gsub("\r",'') end - + def hide_password_in_value(config) - if !config.prompt? && config.name.match(/password/) + if !config.prompt? && config.name.match(/password/) '************' else config.value end end - + def current_stage_project_description - "stage: #{link_to h(current_stage.name), project_stage_path(current_project, current_stage)} (of project #{link_to h(current_project.name), project_path(current_project)})" + "stage: #{link_to current_stage.name, project_stage_path(current_project, current_stage)} (of project #{link_to current_project.name, project_path(current_project)})" end - + # returns the open/closed status of a menu # either the active controller is used or the given status is returned - def controller_in_use_or(contr_name, status, klass) - if @controller.is_a? contr_name + def controller_in_use_or(contr_name, status, objects) + if controller.is_a? contr_name :open else - if status == :closed && (klass.count <= 3 ) + if status == :closed && (objects.length <= 3 ) # the box should be closed # open it anyway if we have less than three status = :open end - + status end end - + # returns the display:none/visible attribute # if the stages of a project should be shown def show_stages_of_project(project) a_stage_active = false - + # check each stage project.stages.each do |stage| a_stage_active = true unless active_link_class(stage).blank? end - + # check project a_stage_active = true unless active_link_class(project).blank? - + a_stage_active ? '' : 'display:none;' end - + # negation of show_stages_of_project(project) def do_not_show_stages_of_project(project) if show_stages_of_project(project).blank? @@ -87,49 +83,47 @@ def do_not_show_stages_of_project(project) '' end end - + def user_info(user) link_to user.login, user_path(user) end - + def show_if_closed(status) status != :closed ? 'display:none;' : '' end - + def show_if_opened(status) status != :open ? 'display:none;' : '' end - + # returns a CSS class if the current item is an active item def active_link_class(item) active_class = 'active_menu_link' found = false case item.class.to_s - when 'Project' - found = true if (@project && @project == item) && (@stage.blank?) - when 'Host' - found = true if @host && @host == item - when 'Recipe' - found = true if @recipe && @recipe == item - when 'User' - found = true if @user && @user == item - when 'Stage' - found = true if @stage && @stage == item + when 'Project' + found = true if (@project && @project == item) && (@stage.blank?) + when 'Host' + found = true if @host && @host == item + when 'Recipe' + found = true if @recipe && @recipe == item + when 'User' + found = true if @user && @user == item + when 'Stage' + found = true if @stage && @stage == item end - + if found active_class else '' end end + - def breadcrumb_box(&block) - out = "" - - block ? concat(out) : out + def ensure_can_access_project(project=nil) + project ||= @project + current_user.admin? || current_user.can_manage_projects? || current_user.projects.include?(project) end - + end diff --git a/app/helpers/auth_sources_helper.rb b/app/helpers/auth_sources_helper.rb new file mode 100644 index 000000000..af76d21e8 --- /dev/null +++ b/app/helpers/auth_sources_helper.rb @@ -0,0 +1,2 @@ +module AuthSourcesHelper +end diff --git a/app/models/auth_source.rb b/app/models/auth_source.rb new file mode 100644 index 000000000..84f17b1bc --- /dev/null +++ b/app/models/auth_source.rb @@ -0,0 +1,58 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +class AuthSource < ActiveRecord::Base + has_many :users + + validates_presence_of :name + validates_uniqueness_of :name + validates_length_of :name, :maximum => 60 + + def authenticate(login, password) + end + + def test_connection + end + + def auth_method_name + "Abstract" + end + + def allow_password_changes? + self.class.allow_password_changes? + end + + # Does this auth source backend allow password changes? + def self.allow_password_changes? + false + end + + # Try to authenticate a user not yet registered against available sources + def self.authenticate(login, password) + AuthSource.find(:all, :conditions => ["onthefly_register=?", true]).each do |source| + begin + logger.debug "Authenticating '#{login}' against '#{source.name}'" if logger && logger.debug? + attrs = source.authenticate(login, password) + rescue => e + logger.error "Error during authentication: #{e.message}" + attrs = nil + end + return attrs if attrs + end + return nil + end +end diff --git a/app/models/auth_source_ldap.rb b/app/models/auth_source_ldap.rb new file mode 100644 index 000000000..a619b2f85 --- /dev/null +++ b/app/models/auth_source_ldap.rb @@ -0,0 +1,98 @@ +# redMine - project management software +# Copyright (C) 2006 Jean-Philippe Lang +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +require 'net/ldap' +require 'iconv' + +class AuthSourceLdap < AuthSource + validates_presence_of :host, :port, :attr_login + validates_length_of :name, :host, :account_password, :maximum => 60, :allow_nil => true + validates_length_of :account, :base_dn, :maximum => 255, :allow_nil => true + validates_length_of :attr_login, :attr_firstname, :attr_lastname, :attr_mail, :maximum => 30, :allow_nil => true + validates_numericality_of :port, :only_integer => true + + before_validation :strip_ldap_attributes + + def after_initialize + self.port = 389 if self.port == 0 + end + + def authenticate(login, password) + return nil if login.blank? || password.blank? + attrs = [] + # get user's DN + ldap_con = initialize_ldap_con(self.account, self.account_password) + login_filter = Net::LDAP::Filter.eq( self.attr_login, login ) + object_filter = Net::LDAP::Filter.eq( "objectClass", "*" ) + dn = String.new + ldap_con.search( :base => self.base_dn, + :filter => object_filter & login_filter, + # only ask for the DN if on-the-fly registration is disabled + :attributes=> (onthefly_register? ? ['dn', self.attr_firstname, self.attr_lastname, self.attr_mail] : ['dn'])) do |entry| + dn = entry.dn + attrs = [:firstname => AuthSourceLdap.get_attr(entry, self.attr_firstname), + :lastname => AuthSourceLdap.get_attr(entry, self.attr_lastname), + :mail => AuthSourceLdap.get_attr(entry, self.attr_mail), + :auth_source_id => self.id ] if onthefly_register? + end + return nil if dn.empty? + logger.debug "DN found for #{login}: #{dn}" if logger && logger.debug? + # authenticate user + ldap_con = initialize_ldap_con(dn, password) + return nil unless ldap_con.bind + # return user's attributes + logger.debug "Authentication successful for '#{login}'" if logger && logger.debug? + attrs + rescue Net::LDAP::LdapError => text + raise "LdapError: " + text + end + + # test the connection to the LDAP + def test_connection + ldap_con = initialize_ldap_con(self.account, self.account_password) + ldap_con.open { } + rescue Net::LDAP::LdapError => text + raise "LdapError: " + text + end + + def auth_method_name + "LDAP" + end + + private + + def strip_ldap_attributes + [:attr_login, :attr_firstname, :attr_lastname, :attr_mail].each do |attr| + write_attribute(attr, read_attribute(attr).strip) unless read_attribute(attr).nil? + end + end + + def initialize_ldap_con(ldap_user, ldap_password) + options = { :host => self.host, + :port => self.port, + :encryption => (self.tls ? :simple_tls : nil) + } + options.merge!(:auth => { :method => :simple, :username => ldap_user, :password => ldap_password }) unless ldap_user.blank? && ldap_password.blank? + Net::LDAP.new options + end + + def self.get_attr(entry, attr_name) + if !attr_name.blank? + entry[attr_name].is_a?(Array) ? entry[attr_name].first : entry[attr_name] + end + end +end diff --git a/app/models/configuration_parameter.rb b/app/models/configuration_parameter.rb index a56d09d77..386054c9f 100644 --- a/app/models/configuration_parameter.rb +++ b/app/models/configuration_parameter.rb @@ -5,8 +5,10 @@ class ConfigurationParameter < ActiveRecord::Base validates_inclusion_of :prompt_on_deploy, :in => 0..1 before_validation :empty_value_if_deploy_is_set - - def validate + + validate :do_validate + + def do_validate self.errors.add('value', 'must be empty if prompt on deploy is set') if (self.prompt? && !self.value.blank?) self.errors.add('name', 'can\'t contain a colon') if (!self.name.blank? && self.name.strip.starts_with?(":")) end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index de73326ec..ee21565d4 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -26,10 +26,12 @@ class Deployment < ActiveRecord::Base STATUS_VALUES = [STATUS_SUCCESS, STATUS_FAILED, STATUS_CANCELED, STATUS_RUNNING] validates_inclusion_of :status, :in => STATUS_VALUES - + + validate :check_stage_ready, :on => :create + # check (on on creation ) that the stage is ready # his has to done only on creation as later DB logging MUST always work - def validate_on_create + def check_stage_ready unless self.stage.blank? errors.add('stage', 'is not ready to deploy') unless self.stage.deployment_possible? @@ -55,7 +57,7 @@ def self.lock_and_fire(&block) end true rescue => e - RAILS_DEFAULT_LOGGER.debug "DEPLOYMENT: could not fire deployment: #{e.inspect} #{e.backtrace.join("\n")}" + Rails.logger.debug "DEPLOYMENT: could not fire deployment: #{e.inspect} #{e.backtrace.join("\n")}" false end @@ -125,9 +127,9 @@ def complete_canceled! # deploy through Webistrano::Deployer in background (== other process) # TODO - at the moment `Unix &` hack def deploy_in_background! - unless RAILS_ENV == 'test' - RAILS_DEFAULT_LOGGER.info "Calling other ruby process in the background in order to deploy deployment #{self.id} (stage #{self.stage.id}/#{self.stage.name})" - system("sh -c \"cd #{RAILS_ROOT} && ruby script/runner -e #{RAILS_ENV} ' deployment = Deployment.find(#{self.id}); deployment.prompt_config = #{self.prompt_config.inspect.gsub('"', '\"')} ; Webistrano::Deployer.new(deployment).invoke_task! ' >> #{RAILS_ROOT}/log/#{RAILS_ENV}.log 2>&1\" &") + unless Rails.env == 'test' + Rails.logger.info "Calling other ruby process in the background in order to deploy deployment #{self.id} (stage #{self.stage.id}/#{self.stage.name})" + system("sh -c \"cd #{Rails.root.to_s} && ruby script/rails runner -e #{Rails.env} ' deployment = Deployment.find(#{self.id}); deployment.prompt_config = #{self.prompt_config.inspect.gsub('"', '\"')} ; Webistrano::Deployer.new(deployment).invoke_task! ' >> #{Rails.root.to_s}/log/#{Rails.env}.log 2>&1\" &") end end @@ -170,11 +172,6 @@ def excluded_host_ids self.read_attribute('excluded_host_ids').blank? ? [] : self.read_attribute('excluded_host_ids') end - def excluded_host_ids=(val) - val = [val] unless val.is_a?(Array) - self.write_attribute('excluded_host_ids', val.map(&:to_i)) - end - def cancelling_possible? !self.pid.blank? && !completed? end @@ -190,7 +187,9 @@ def cancel! end def clear_lock_error - self.errors.instance_variable_get("@errors").delete('lock') + if self.errors.size > 0 + Rails.logger.error "clear lock error : #{self.errors.keys}" + end end protected diff --git a/app/models/host.rb b/app/models/host.rb index a2db3f165..18dc79fde 100644 --- a/app/models/host.rb +++ b/app/models/host.rb @@ -9,12 +9,14 @@ class Host < ActiveRecord::Base attr_accessible :name before_validation :strip_whitespace - + + validate :do_validate + def strip_whitespace self.name = self.name.strip rescue nil end - def validate + def do_validate errors.add("name", "is not a valid hostname or IP") unless ( valid_ip? || valid_hostname? ) end diff --git a/app/models/project.rb b/app/models/project.rb index cf3cc5395..a8e679398 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2,7 +2,10 @@ class Project < ActiveRecord::Base has_many :stages, :dependent => :destroy, :order => 'name ASC' has_many :deployments, :through => :stages has_many :configuration_parameters, :dependent => :destroy, :class_name => "ProjectConfiguration", :order => 'name ASC' - + + has_many :user_project_links, :dependent => :destroy + has_many :users, :through => :user_project_links + validates_uniqueness_of :name validates_presence_of :name validates_length_of :name, :maximum => 250 @@ -10,7 +13,12 @@ class Project < ActiveRecord::Base after_create :create_template_defaults - attr_accessible :name, :description, :template + attr_accessible :name, :description, :template, :archived + + named_scope :active, + :include => [:users, {:stages => [:hosts, :recipes]}, :configuration_parameters, :user_project_links], + :conditions => {:archived => false}, + :order => "projects.name ASC" # creates the default configuration parameters based on the template def create_template_defaults @@ -26,6 +34,14 @@ def create_template_defaults end end + def editable_by?(_user) + _user.admin? || _user.can_manage_projects? || (!self.archived? && _user.projects.include?(self)) + end + + def viewable_by?(_user) + _user.admin? || _user.can_manage_projects? || (!self.archived? && _user.projects.include?(self)) + end + # returns a string with all custom tasks to be loaded by the Capistrano config def tasks ProjectConfiguration.templates[template]::TASKS diff --git a/app/models/recipe.rb b/app/models/recipe.rb index e5bfce6bb..f095aa931 100644 --- a/app/models/recipe.rb +++ b/app/models/recipe.rb @@ -7,14 +7,12 @@ class Recipe < ActiveRecord::Base attr_accessible :name, :body, :description - named_scope :ordered, :order => "name ASC" + scope :ordered, :order => "name ASC" version_fu rescue nil # hack to silence migration errors when the original table is not there - - def validate - check_syntax - end - + + validate :check_syntax + def check_syntax return if self.body.blank? @@ -32,7 +30,7 @@ def check_syntax errors.add(:body, "syntax error at line: #{line}") unless line.nil? end rescue => e - RAILS_DEFAULT_LOGGER.error "Error while validating recipe syntax of recipe #{self.id}: #{e.inspect} - #{e.backtrace.join("\n")}" + Rails.logger.error "Error while validating recipe syntax of recipe #{self.id}: #{e.inspect} - #{e.backtrace.join("\n")}" end end diff --git a/app/models/stage.rb b/app/models/stage.rb index 2fb9020d5..79e7af3aa 100644 --- a/app/models/stage.rb +++ b/app/models/stage.rb @@ -11,7 +11,9 @@ class Stage < ActiveRecord::Base validates_length_of :name, :maximum => 250 validates_presence_of :project, :name validates_inclusion_of :locked, :in => [0,1] - + + validate :do_validate + attr_accessible :name, :alert_emails # fake attr (Hash) that hold info why deployment is not possible @@ -21,7 +23,7 @@ class Stage < ActiveRecord::Base EMAIL_BASE_REGEX = '([^@\s\,\<\>\?\&\;\:]+)@((?:[\-a-z0-9]+\.)+[a-z]{2,})' EMAIL_REGEX = /^#{EMAIL_BASE_REGEX}$/i - def validate + def do_validate unless self.alert_emails.blank? self.alert_emails.split(" ").each do |email| unless email.match(EMAIL_REGEX) @@ -79,6 +81,14 @@ def deployment_possible? @deployment_problems.blank? end + def editable_by?(_user) + self.project.editable_by?(_user) + end + + def viewable_by?(_user) + self.project.viewable_by?(_user) + end + def needed_roles_present? # for now just check if there are any roles if self.roles.empty? @@ -126,7 +136,7 @@ def list_tasks begin deployer.list_tasks.collect { |t| {:name => t.fully_qualified_name, :description => t.description} }.delete_if{|t| t[:name] == 'shell' || t[:name] == 'invoke'} rescue Exception => e - RAILS_DEFAULT_LOGGER.error("Problem listing tasks of stage #{id}: #{e} - #{e.backtrace.join("\n")} ") + Rails.logger.error("Problem listing tasks of stage #{id}: #{e} - #{e.backtrace.join("\n")} ") [{:name => "Error", :description => "Could not load tasks - syntax error in recipe definition?"}] end end diff --git a/app/models/user.rb b/app/models/user.rb index b1da949a1..c796cca63 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,11 +1,14 @@ require 'digest/sha1' class User < ActiveRecord::Base has_many :deployments, :dependent => :nullify, :order => 'created_at DESC' + has_many :user_project_links, :dependent => :destroy + has_many :projects, :through => :user_project_links + belongs_to :auth_source # Virtual attribute for the unencrypted password attr_accessor :password - attr_accessible :login, :email, :password, :password_confirmation, :time_zone, :tz + attr_accessible :login, :email, :password, :password_confirmation, :time_zone, :tz, :auth_source_id validates_presence_of :login, :email validates_presence_of :password, :if => :password_required? @@ -17,10 +20,12 @@ class User < ActiveRecord::Base validates_uniqueness_of :login, :email, :case_sensitive => false before_save :encrypt_password - named_scope :enabled, :conditions => {:disabled => nil} - named_scope :disabled, :conditions => "disabled IS NOT NULL" - - def validate_on_update + scope :enabled, :conditions => {:disabled => nil} + scope :disabled, :conditions => "disabled IS NOT NULL" + + validate :check_for_last_admin, :on => :update + + def check_for_last_admin if User.find(self.id).admin? && !self.admin? errors.add('admin', 'status can no be revoked as there needs to be one admin left.') if User.admin_count == 1 end @@ -28,8 +33,45 @@ def validate_on_update # Authenticates a user by their login name and unencrypted password. Returns the user or nil. def self.authenticate(login, password) - u = find_by_login_and_disabled(login, nil) # need to get the salt - u && u.authenticated?(password) ? u : nil + return nil if login.blank? || password.blank? + + user = User.find_by_login_and_disabled(login, nil) + + if user + if user.local_user? + return nil unless user.authenticated?(password) + else + return nil unless user.auth_source.authenticate(login, password) + end + else + user = User.try_onthefly_registration(login, password) + end + user + end + + def self.try_onthefly_registration(login, password) + attrs = AuthSource.authenticate(login, password) + logger.debug "attrs 1: #{attrs.inspect}" + attrs = attrs.first if attrs.is_a? Array + logger.debug "attrs 2: #{attrs.inspect}" + if attrs.present? + user = User.new + user.auth_source_id = attrs[:auth_source_id] + user.email = attrs[:mail] + user.login = login + user.save! + user.reload + logger.debug "User '#{user.login}' created from external auth source: #{user.auth_source.type} - #{user.auth_source.name}" + end + user + end + + def local_user? + self.auth_source_id.blank? + end + + def remote_user? + self.auth_source_id.present? end # Encrypts some data with the salt. @@ -62,13 +104,13 @@ def remember_me_for(time) def remember_me_until(time) self.remember_token_expires_at = time self.remember_token = encrypt("#{email}--#{remember_token_expires_at}") - save(false) + save(:validate => false) end def forget_me self.remember_token_expires_at = nil self.remember_token = nil - save(false) + save(:validate => false) end def admin? @@ -105,6 +147,26 @@ def disable def enable self.update_attribute(:disabled, nil) end + + def can_edit?(obj) + obj.editable_by?(self) + end + + def can_view?(obj) + obj.viewable_by?(self) + end + + def can_manage_hosts? + self.admin? || self.manage_hosts? + end + + def can_manage_recipes? + self.admin? || self.manage_recipes? + end + + def can_manage_projects? + self.admin? || self.manage_projects? + end protected # before filter @@ -115,7 +177,7 @@ def encrypt_password end def password_required? - WebistranoConfig[:authentication_method] != :cas && (crypted_password.blank? || !password.blank?) + self.local_user? && WebistranoConfig[:authentication_method] != :cas && (crypted_password.blank? || !password.blank?) end diff --git a/app/models/user_project_link.rb b/app/models/user_project_link.rb new file mode 100644 index 000000000..220de6070 --- /dev/null +++ b/app/models/user_project_link.rb @@ -0,0 +1,4 @@ +class UserProjectLink < ActiveRecord::Base + belongs_to :user + belongs_to :project +end diff --git a/app/views/auth_sources/_form.html.erb b/app/views/auth_sources/_form.html.erb new file mode 100644 index 000000000..b09352bef --- /dev/null +++ b/app/views/auth_sources/_form.html.erb @@ -0,0 +1,46 @@ +<% flashed_errors(:auth_source) %> + +
+ +

+<%= text_field 'auth_source', 'name' %>

+ +

+<%= text_field 'auth_source', 'host' %>

+ +

+<%= text_field 'auth_source', 'port', :size => 6 %> <%= check_box 'auth_source', 'tls' %> LDAPS

+ +

+<%= text_field 'auth_source', 'account' %>

+ +

+<%= password_field 'auth_source', 'account_password', :name => 'ignore', + :value => ((@auth_source.new_record? || @auth_source.account_password.blank?) ? '' : ('x'*15)), + :onfocus => "this.value=''; this.name='auth_source[account_password]';", + :onchange => "this.name='auth_source[account_password]';" %>

+ +

+<%= text_field 'auth_source', 'base_dn', :size => 60 %>

+ +

+<%= check_box 'auth_source', 'onthefly_register' %>

+
+ +
Attributes +

+<%= text_field 'auth_source', 'attr_login', :size => 20 %>

+ +

+<%= text_field 'auth_source', 'attr_firstname', :size => 20 %>

+ +

+<%= text_field 'auth_source', 'attr_lastname', :size => 20 %>

+ +

+<%= text_field 'auth_source', 'attr_mail', :size => 20 %>

+
+ + + + diff --git a/app/views/auth_sources/edit.rhtml b/app/views/auth_sources/edit.rhtml new file mode 100644 index 000000000..965c390e7 --- /dev/null +++ b/app/views/auth_sources/edit.rhtml @@ -0,0 +1,7 @@ +

Authentication mode (<%= @auth_source.auth_method_name %>)

+ +<% form_for @auth_source do %> + <%= render :partial => 'form' %> + <%= submit_tag 'Save' %> +<% end %> + diff --git a/app/views/auth_sources/index.html.erb b/app/views/auth_sources/index.html.erb new file mode 100644 index 000000000..4186bb1d0 --- /dev/null +++ b/app/views/auth_sources/index.html.erb @@ -0,0 +1,36 @@ +
+<%= link_to 'New authentication mode', {:action => 'new'}, :class => 'icon icon-add' %> +
+ +

Authentication modes

+ + + + + + + + + + + +<% for source in @auth_sources %> + "> + + + + + + + +<% end %> + +
NameTypeHostUsers
<%= link_to source.name, :action => 'edit', :id => source%><%= source.auth_method_name %><%= source.host %><%= source.users.count %> + <%= link_to 'Test', :action => 'test_connection', :id => source %> + + <%= link_to 'Delete', { :action => 'destroy', :id => source }, + :method => :post, + :confirm => 'Are you sure ?', + :class => 'icon icon-del', + :disabled => source.users.any? %> +
diff --git a/app/views/auth_sources/new.rhtml b/app/views/auth_sources/new.rhtml new file mode 100644 index 000000000..f15b3d979 --- /dev/null +++ b/app/views/auth_sources/new.rhtml @@ -0,0 +1,6 @@ +

New authentication mode (<%= @auth_source.auth_method_name %>)

+ +<% form_tag({:action => 'create'}, :class => "tabular") do %> + <%= render :partial => 'form' %> + <%= submit_tag 'Create' %> +<% end %> diff --git a/app/views/deployments/_list_item.html.erb b/app/views/deployments/_list_item.html.erb index 18302df9e..6bf5d8821 100644 --- a/app/views/deployments/_list_item.html.erb +++ b/app/views/deployments/_list_item.html.erb @@ -1,31 +1,31 @@ - + - + - + - + - + - + - + - + diff --git a/app/views/deployments/_lock_info.html.erb b/app/views/deployments/_lock_info.html.erb index ad66c8bb5..ce7cf7558 100644 --- a/app/views/deployments/_lock_info.html.erb +++ b/app/views/deployments/_lock_info.html.erb @@ -3,7 +3,7 @@

<% if stage.locking_deployment %>

- This stage is being deployed by <%= link_to h(stage.locking_deployment.user.login), user_path(stage.locking_deployment.user) %> who executed <%=link_to h(stage.locking_deployment.task), project_stage_deployment_path(current_project, current_stage, stage.locking_deployment) %> at <%=h stage.locking_deployment.created_at.to_s(:log) %> + This stage is being deployed by <%= link_to stage.locking_deployment.user.login, user_path(stage.locking_deployment.user) %> who executed <%=link_to stage.locking_deployment.task, project_stage_deployment_path(current_project, current_stage, stage.locking_deployment) %> at <%= stage.locking_deployment.created_at.to_s(:log) %>

Override locking and deploy anyhow? diff --git a/app/views/deployments/_status.html.erb b/app/views/deployments/_status.html.erb index ddbb59977..37a64c933 100644 --- a/app/views/deployments/_status.html.erb +++ b/app/views/deployments/_status.html.erb @@ -3,14 +3,14 @@

<%= image_tag('peritor_theme/status_' + @deployment.status + '.gif', :title => @deployment.status) %>
- Status: <%=h @deployment.status %> + Status: <%= @deployment.status %> <% if @deployment.cancelling_possible? %>     <%= link_to "Cancel", cancel_project_stage_deployment_path(current_project, current_stage, @deployment), :confirm => "Are you sure? Killing this deployment will not cleanup and rollback.", :method => "post", :class => 'arrow_link' %> <% end %> <% if @deployment.completed? %>     - <%= link_to "Repeat", new_project_stage_deployment_path(current_project, current_stage) + '?repeat=' + h(@deployment.id), :class => 'arrow_link' %> + <%= link_to "Repeat", new_project_stage_deployment_path(current_project, current_stage) + "?repeat=#{@deployment.id}", :class => 'arrow_link' %> <% end %>
@@ -22,7 +22,7 @@

Task: - <%=h @deployment.task %> + <%= @deployment.task %>

Stage: @@ -38,11 +38,11 @@

Started: - <%=h @deployment.created_at.to_s(:log) %> (<%=h current_user.time_zone rescue 'UTC' %>) + <%= @deployment.created_at.to_s(:log) %> (<%= current_user.time_zone rescue 'UTC' %>)

Completed: - <%=h "#{@deployment.completed_at.to_s(:log)} (#{current_user.time_zone})" rescue nil %> + <%= "#{@deployment.completed_at.to_s(:log)} (#{current_user.time_zone})" rescue nil %>

@@ -60,9 +60,9 @@
Log -
>
+
>
-
<%=h @deployment.log.gsub('\n', "\n") rescue '' %>
+
<%= @deployment.log.gsub('\n', "\n") rescue '' %>
@@ -75,12 +75,12 @@ <% for user in @users %> - - - + + + - + <% if current_user.admin? || current_user == user -%> <% end -%> @@ -43,10 +44,9 @@ + <% content_for(:breadcrumb) do %> - <% breadcrumb_box do %> <%= link_to "Users", users_path %> - <% end %> <% end %> <% content_for(:page_title) do %> diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb index 90a455d6c..15a284857 100644 --- a/app/views/users/new.html.erb +++ b/app/views/users/new.html.erb @@ -1,4 +1,4 @@ -<% form_for :user, :url => users_path do |f| -%> +<%= form_for :user, :url => users_path do |f| -%> <%= render(:partial => 'form')%> <%= link_to 'Back to users', users_path, :class => 'arrow_link ontheright', :style => 'margin-right: 330px' %> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb index 0c5e4386c..1bc4381a4 100644 --- a/app/views/users/show.html.erb +++ b/app/views/users/show.html.erb @@ -15,19 +15,19 @@ <% end %>

Login: - <%=h @user.login %> + <%= @user.login %>

Email: - <%=h @user.email %> + <%= @user.email %>

- Timezone: <%=h @user.time_zone.to_s %> + Timezone: <%= @user.time_zone.to_s %>

- #Deployments: <%=h @user.deployments.count %> + #Deployments: <%= @user.deployments.count %>


<% if current_user.admin? || current_user == @user %> @@ -52,11 +52,9 @@ <% content_for(:breadcrumb) do %> - <% breadcrumb_box do %> <%= link_to "Manage users", users_path, :style => "float:right", :class => "arrow_link" %> <%= link_to "Users", users_path %> > User <%= link_to @user.login, user_path(@user) %> - <% end %> <% end %> <% content_for(:page_title) do %> diff --git a/config.ru b/config.ru new file mode 100644 index 000000000..6166449b9 --- /dev/null +++ b/config.ru @@ -0,0 +1,4 @@ +# This file is used by Rack-based servers to start the application. + +require ::File.expand_path('../config/environment', __FILE__) +run Www::Application diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 000000000..8429fd41b --- /dev/null +++ b/config/application.rb @@ -0,0 +1,92 @@ +require File.expand_path('../boot', __FILE__) + +# load Webistrano configuration +require File.expand_path('../webistrano_config', __FILE__) + +# moved fro preinitializer +# is this necessary +begin + require "rubygems" + require "bundler" +rescue LoadError + raise "Could not load the bundler gem. Install it with `gem install bundler`." +end + +if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24") + raise RuntimeError, "Your bundler version is too old for Rails 2.3." + + "Run `gem install bundler` to upgrade." +end + +begin + # Set up load paths for all bundled gems + ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__) + Bundler.setup +rescue Bundler::GemNotFound + raise RuntimeError, "Bundler couldn't find some gems." + + "Did you run `bundle install`?" +end +#### + +require 'rails/all' + +# If you have a Gemfile, require the gems listed there, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(:default, Rails.env) if defined?(Bundler) + +module Www + class Application < Rails::Application + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + + # Custom directories with classes and modules you want to be autoloadable. + # config.autoload_paths += %W(#{config.root}/extras) + config.autoload_paths << "#{Rails.root}/lib" + + # Only load the plugins named here, in the order given (default is alphabetical). + # :all can be used as a placeholder for all plugins not explicitly named. + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + #config.plugins = [ :browser_filters, :exception_notification, :multiple_select, :query_trace, + # :restful_authentication, :rubycas_client, :version_fu ] + config.plugins = [ :browser_filters, :version_fu, :rails_upgrade] + + # Activate observers that should always be running. + # config.active_record.observers = :cacher, :garbage_collector, :forum_observer + + # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. + # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. + # config.time_zone = 'Central Time (US & Canada)' + config.time_zone = 'UTC' + + # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. + # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] + # config.i18n.default_locale = :de + + # JavaScript files you want as :defaults (application.js is always included). + # config.action_view.javascript_expansions[:defaults] = %w(jquery rails) + + # Configure the default encoding used in templates for Ruby 1.9. + config.encoding = "utf-8" + + # Configure sensitive parameters which will be filtered from the log file. + config.filter_parameters += [:password] + + config.action_dispatch.session = { + :key => '_webistrano_session', + :secret => WebistranoConfig[:session_secret] + } + + end +end + +require 'open4' +require 'capistrano/cli' +require 'syntax/convertors/html' + +# from environment.rb needed? +# delete cached stylesheet on boot in order to delete stale versions +#File.delete("#{Rails.root.to_s}/public/stylesheets/application.css") if File.exists?("#{Rails.root.to_s}/public/stylesheets/application.css") + +# set default time_zone to UTC +#ENV['TZ'] = 'UTC' +#Time.zone = 'UTC' diff --git a/config/boot.rb b/config/boot.rb index 753f212e1..4489e5868 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,123 +1,6 @@ -# Don't change this file! -# Configure your app in config/environment.rb and config/environments/*.rb +require 'rubygems' -RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) +# Set up gems listed in the Gemfile. +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -module Rails - class << self - def boot! - unless booted? - preinitialize - pick_boot.run - end - end - - def booted? - defined? Rails::Initializer - end - - def pick_boot - (vendor_rails? ? VendorBoot : GemBoot).new - end - - def vendor_rails? - File.exist?("#{RAILS_ROOT}/vendor/rails") - end - - def preinitialize - load(preinitializer_path) if File.exist?(preinitializer_path) - end - - def preinitializer_path - "#{RAILS_ROOT}/config/preinitializer.rb" - end - end - - class Boot - def run - load_initializer - Rails::Initializer.run(:set_load_path) - end - end - - class VendorBoot < Boot - def load_initializer - require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" - Rails::Initializer.run(:install_gem_spec_stubs) - end - end - - class GemBoot < Boot - def load_initializer - self.class.load_rubygems - load_rails_gem - require 'initializer' - end - - def load_rails_gem - if version = self.class.gem_version - gem 'rails', version - else - gem 'rails' - end - rescue Gem::LoadError => load_error - $stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.) - exit 1 - end - - class << self - def rubygems_version - Gem::RubyGemsVersion if defined? Gem::RubyGemsVersion - end - - def gem_version - if defined? RAILS_GEM_VERSION - RAILS_GEM_VERSION - elsif ENV.include?('RAILS_GEM_VERSION') - ENV['RAILS_GEM_VERSION'] - else - parse_gem_version(read_environment_rb) - end - end - - def load_rubygems - require 'rubygems' - min_version = '1.1.1' - unless rubygems_version >= min_version - $stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.) - exit 1 - end - - rescue LoadError - $stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org) - exit 1 - end - - def parse_gem_version(text) - $1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/ - end - - private - def read_environment_rb - File.read("#{RAILS_ROOT}/config/environment.rb") - end - end - end -end - -class Rails::Boot - def run - load_initializer - - Rails::Initializer.class_eval do - def load_gems - @bundler_loaded ||= Bundler.require :default, Rails.env - end - end - - Rails::Initializer.run(:set_load_path) - end -end - -# All that for this: -Rails.boot! +require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) diff --git a/config/database.yml.sample b/config/database.yml.sample index b00ae505e..9378af0c0 100644 --- a/config/database.yml.sample +++ b/config/database.yml.sample @@ -1,36 +1,39 @@ -# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# MySQL. 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: -# gem install mysql -# Choose the win32 build. -# Install MySQL and put its /bin directory on your path. +# gem install mysql2 # # 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: webistrano_development - username: root - password: - socket: /tmp/mysql.sock + adapter: mysql2 + encoding: utf8 + reconnect: false + database: webistrano_dev + pool: 5 + username: webistrano + password: master + socket: /var/run/mysqld/mysqld.sock -# Warning: The database defined as 'test' will be erased and -# re-generated from your development database when you run 'rake'. +# 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 + adapter: mysql2 + encoding: utf8 + reconnect: false database: webistrano_test - username: root - password: - socket: /tmp/mysql.sock + pool: 5 + username: webistrano + password: master + socket: /var/run/mysqld/mysqld.sock production: - adapter: mysql - database: webistrano_production - username: root - password: - socket: /tmp/mysql.sock + adapter: mysql2 + encoding: utf8 + reconnect: false + database: webistrano_prod + pool: 5 + username: webistrano + password: master + socket: /var/run/mysqld/mysqld.sock diff --git a/config/environment.rb b/config/environment.rb index 5f95832a0..ab06d2105 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,49 +1,4 @@ -# Be sure to restart your 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 = '2.3.11' unless defined? RAILS_GEM_VERSION - -# Bootstrap the Rails environment, frameworks, and default configuration -require File.join(File.dirname(__FILE__), 'boot') - -# load Webistrano configuration -require "#{RAILS_ROOT}/config/webistrano_config" - -Rails::Initializer.run do |config| - - # Your secret key for verifying cookie session data integrity. - # If you change this key, all old sessions will become invalid! - # Make sure the secret is at least 30 characters and all random, - # no regular words or you'll be exposed to dictionary attacks. - config.action_controller.session = { - :key => '_webistrano_session', - :secret => WebistranoConfig[:session_secret] - } - - # Make Active Record use UTC-base instead of local time - config.time_zone = 'UTC' - - config.gem 'net-ssh', :version => '2.0.15', :lib => 'net/ssh' - config.gem 'net-scp', :version => '1.0.2', :lib => 'net/scp' - config.gem 'net-sftp', :version => '2.0.2', :lib => 'net/sftp' - config.gem 'net-ssh-gateway', :version => '1.0.1', :lib => 'net/ssh/gateway' - config.gem 'capistrano', :version => '2.5.9' - config.gem 'highline', :version => '1.5.1' - config.gem 'open4', :version => '0.9.3' - config.gem 'syntax', :version => '1.0.0' -end - -require 'open4' -require 'capistrano/cli' -require 'syntax/convertors/html' - -# delete cached stylesheet on boot in order to delete stale versions -File.delete("#{RAILS_ROOT}/public/stylesheets/application.css") if File.exists?("#{RAILS_ROOT}/public/stylesheets/application.css") - -# set default time_zone to UTC -ENV['TZ'] = 'UTC' -Time.zone = 'UTC' \ No newline at end of file +# Load the rails application +require File.expand_path('../application', __FILE__) +# Initialize the rails application +Www::Application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index d81d692ba..64f183ae8 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,17 +1,31 @@ -# Settings specified here will take precedence over those in config/environment.rb +Www::Application.configure do + # Settings specified here will take precedence over those in config/application.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 + # 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 + # 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 -config.action_view.debug_rjs = true + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_view.debug_rjs = true + config.action_controller.perform_caching = false -# Don't care if the mailer can't send -config.action_mailer.raise_delivery_errors = false + # Don't care if the mailer can't send + config.action_mailer.raise_delivery_errors = false + + # Print deprecation notices to the Rails logger + config.active_support.deprecation = :log + + # Only use best-standards-support built into browsers + config.action_dispatch.best_standards_support = :builtin + +# config.middleware.use ExceptionNotifier, +# :email_prefix => "[Whatever] ", +# :sender_address => %{"notifier" }, +# :exception_recipients => %w{exceptions@example.com} + +end diff --git a/config/environments/production.rb b/config/environments/production.rb index 0b2132c3f..0740633d6 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,21 +1,49 @@ -# Settings specified here will take precedence over those in config/environment.rb +Www::Application.configure do + # Settings specified here will take precedence over those in config/application.rb -# The production environment is meant for finished, "live" apps. -# Code is not reloaded between requests -config.cache_classes = true + # 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.consider_all_requests_local = false + config.action_controller.perform_caching = true -# Full error reports are disabled and caching is turned on -config.action_controller.consider_all_requests_local = false -config.action_controller.perform_caching = true + # Specifies the header that your server uses for sending files + config.action_dispatch.x_sendfile_header = "X-Sendfile" -# Enable serving of images, stylesheets, and javascripts from an asset server -# config.action_controller.asset_host = "http://assets.example.com" + # For nginx: + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' -# Disable delivery errors, bad email addresses will be ignored -# config.action_mailer.raise_delivery_errors = false + # If you have no front-end server that supports something like X-Sendfile, + # just comment this out and Rails will serve the files -# long only errors -config.log_level = :error + # See everything in the log (default is :info) + config.log_level = :error + + # Use a different logger for distributed setups + # config.logger = SyslogLogger.new + + # Use a different cache store in production + # config.cache_store = :mem_cache_store + + # Disable Rails's static asset server + # In production, Apache or nginx will already do this + config.serve_static_assets = false + + # Enable serving of images, stylesheets, and javascripts from an asset server + # config.action_controller.asset_host = "http://assets.example.com" + + # Disable delivery errors, bad email addresses will be ignored + # config.action_mailer.raise_delivery_errors = false + + # Enable threaded mode + # config.threadsafe! + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation can not be found) + config.i18n.fallbacks = true + + # Send deprecation notices to registered listeners + config.active_support.deprecation = :notify +end diff --git a/config/environments/test.rb b/config/environments/test.rb index 01d00198f..ca842fff4 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,25 +1,35 @@ -# Settings specified here will take precedence over those in config/environment.rb +Www::Application.configure do + # Settings specified here will take precedence over those in config/application.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 + # 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 + # 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 + # Show full error reports and disable caching + config.consider_all_requests_local = true + config.action_controller.perform_caching = false -# Disable request forgery protection in test environment -config.action_controller.allow_forgery_protection = false + # Raise exceptions instead of rendering exception templates + config.action_dispatch.show_exceptions = 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 + # Disable request forgery protection in test environment + config.action_controller.allow_forgery_protection = false -require 'test/unit' -config.gem 'mocha', :version => '0.9.8' + # Tell Action Mailer 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 + + # 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 + + # Print deprecation notices to the stderr + config.active_support.deprecation = :stderr +end diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb new file mode 100644 index 000000000..59385cdf3 --- /dev/null +++ b/config/initializers/backtrace_silencers.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. +# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } + +# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. +# Rails.backtrace_cleaner.remove_silencers! diff --git a/config/initializers/capistrano_namespace_rake_fix.rb b/config/initializers/capistrano_namespace_rake_fix.rb deleted file mode 100644 index 5f72b1599..000000000 --- a/config/initializers/capistrano_namespace_rake_fix.rb +++ /dev/null @@ -1,2 +0,0 @@ -# see http://groups.google.com/group/capistrano/browse_thread/thread/b5e11c0ebf37a8be -Capistrano::Configuration::Namespaces::Namespace.class_eval { undef :symlink } \ No newline at end of file diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 09158b865..9e8b0131f 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,8 +1,8 @@ # Be sure to restart your server when you modify this file. -# Add new inflection rules using the following format +# Add new inflection rules using the following format # (all these examples are active by default): -# Inflector.inflections do |inflect| +# ActiveSupport::Inflector.inflections do |inflect| # inflect.plural /^(ox)$/i, '\1en' # inflect.singular /^(ox)en/i, '\1' # inflect.irregular 'person', 'people' diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb new file mode 100644 index 000000000..c78f63ba2 --- /dev/null +++ b/config/initializers/secret_token.rb @@ -0,0 +1,7 @@ +# Be sure to restart your server when you modify this file. + +# Your secret key for verifying the integrity of signed cookies. +# If you change this key, all old signed cookies will become invalid! +# Make sure the secret is at least 30 characters and all random, +# no regular words or you'll be exposed to dictionary attacks. +Www::Application.config.secret_token = 'f729b177755bce74cd7b099f2dc7bb9c2e8f56046611a907be294bbdd5cf96fdc0b2a976eb9d74e93203b69c7aa1ff303a2d8591d40643323348470e653a31b4' diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb new file mode 100644 index 000000000..92b77e0e4 --- /dev/null +++ b/config/initializers/session_store.rb @@ -0,0 +1,15 @@ +# Be sure to restart your server when you modify this file. + +Www::Application.config.session_store :cookie_store, :key => '_www_session' + +# Use the database for sessions instead of the cookie-based default, +# which shouldn't be used to store highly confidential information +# (create the session table with "rails generate session_migration") +# Www::Application.config.session_store :active_record_store + + +# from environment.rb +#config.action_controller.session = { +# :key => '_webistrano_session', +# :secret => WebistranoConfig[:session_secret] +#} diff --git a/config/initializers/time_formatting.rb b/config/initializers/time_formatting.rb index e98153a3d..8625077f6 100644 --- a/config/initializers/time_formatting.rb +++ b/config/initializers/time_formatting.rb @@ -1,2 +1,2 @@ -ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(:log => '%Y-%m-%d %H:%M') -ActiveSupport::CoreExtensions::Time::Conversions::DATE_FORMATS.update(:date_with_day => '%Y-%m-%d') \ No newline at end of file +Time::DATE_FORMATS.update(:log => '%Y-%m-%d %H:%M') +Time::DATE_FORMATS.update(:date_with_day => '%Y-%m-%d') diff --git a/config/initializers/webistrano_configuration.rb b/config/initializers/webistrano_configuration.rb index 4c21b9a4e..d3bce0ea9 100644 --- a/config/initializers/webistrano_configuration.rb +++ b/config/initializers/webistrano_configuration.rb @@ -1,14 +1,11 @@ if WebistranoConfig[:authentication_method] == :cas - cas_options = YAML::load_file(RAILS_ROOT+'/config/cas.yml') - CASClient::Frameworks::Rails::Filter.configure(cas_options[RAILS_ENV]) + cas_options = YAML::load_file(Rails.root.to_s+'/config/cas.yml') + CASClient::Frameworks::Rails::Filter.configure(cas_options[Rails.env]) end -WEBISTRANO_VERSION = '1.5' +WEBISTRANO_VERSION = '1.5 - JS 1.0' ActionMailer::Base.delivery_method = WebistranoConfig[:smtp_delivery_method] ActionMailer::Base.smtp_settings = WebistranoConfig[:smtp_settings] -Notification.webistrano_sender_address = WebistranoConfig[:webistrano_sender_address] - -ExceptionNotification::Notifier.exception_recipients = WebistranoConfig[:exception_recipients] -ExceptionNotification::Notifier.sender_address = WebistranoConfig[:exception_sender_address] +Notification.webistrano_sender_address = WebistranoConfig[:webistrano_sender_address] \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml new file mode 100644 index 000000000..a747bfa69 --- /dev/null +++ b/config/locales/en.yml @@ -0,0 +1,5 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + hello: "Hello world" diff --git a/config/preinitializer.rb b/config/preinitializer.rb deleted file mode 100644 index 85399f7c1..000000000 --- a/config/preinitializer.rb +++ /dev/null @@ -1,20 +0,0 @@ -begin - require "rubygems" - require "bundler" -rescue LoadError - raise "Could not load the bundler gem. Install it with `gem install bundler`." -end - -if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.24") - raise RuntimeError, "Your bundler version is too old for Rails 2.3." + - "Run `gem install bundler` to upgrade." -end - -begin - # Set up load paths for all bundled gems - ENV["BUNDLE_GEMFILE"] = File.expand_path("../../Gemfile", __FILE__) - Bundler.setup -rescue Bundler::GemNotFound - raise RuntimeError, "Bundler couldn't find some gems." + - "Did you run `bundle install`?" -end \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index db6ce4658..0874ac307 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,45 +1,64 @@ -ActionController::Routing::Routes.draw do |map| - # The priority is based upon order of creation: first created -> highest priority. +Www::Application.routes.draw do - # Sample of regular route: - # map.connect 'products/:id', :controller => 'catalog', :action => 'view' - # Keep in mind you can assign values other than :controller and :action + resources :auth_sources + resources :auth_sources_ldap - # Sample of named route: - # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' - # This route can be invoked with purchase_url(:id => product.id) + match '/' => 'projects#dashboard', :as => :home + match ':controller/service.wsdl' => '#wsdl' - # You can have the root of your site routed by hooking up '' - # -- just remember to delete public/index.html. - map.home '', :controller => "projects", :action => 'dashboard' + resources :hosts + resources :recipes do + collection do + get :preview + end + end - # Allow downloading Web Service WSDL as a file with an extension - # instead of a file named 'wsdl' - map.connect ':controller/service.wsdl', :action => 'wsdl' - - map.resources :hosts - map.resources :recipes, :collection => {:preview => :get} - map.resources :projects, :member => {:dashboard => :get} do |projects| - projects.resources :project_configurations - - projects.resources :stages, :member => {:capfile => :get, :recipes => :any, :tasks => :get} do |stages| - stages.resources :stage_configurations - stages.resources :roles - stages.resources :deployments, :collection => {:latest => :get}, :member => {:cancel => :post} + resources :projects do + member do + get :dashboard + end + + resources :project_configurations + resources :stages do + match :recipes, :on => :member + member do + get :capfile + get :tasks + end + + resources :stage_configurations + resources :roles + resources :deployments do + collection do + get :latest + end + + member do + post :cancel + end + end + end + end + + resources :users do + + member do + get :deployments + post :enable + end + end + + resources :sessions do + collection do + get :version end end - # RESTful auth - map.resources :users,:member => {:deployments => :get, :enable => :post} - map.resources :sessions, :collection => {:version => :get} - map.signup '/signup', :controller => 'users', :action => 'new' - map.login '/login', :controller => 'sessions', :action => 'new' - map.logout '/logout', :controller => 'sessions', :action => 'destroy' - - # stylesheet - map.stylesheet '/stylesheets/application.css', :controller => 'stylesheets', :action => 'application' - # Install the default route as the lowest priority. - map.connect ':controller/:action/:id.:format' - map.connect ':controller/:action/:id' -end + + match '/signup' => 'users#new', :as => :signup + match '/login' => 'sessions#new', :as => :login + match '/logout' => 'sessions#destroy', :as => :logout + match '/stylesheets/application.css' => 'stylesheets#application', :as => :stylesheet + match '/:controller(/:action(/:id))' +end \ No newline at end of file diff --git a/db/migrate/20101109184848_add_auth_source_id_to_users.rb b/db/migrate/20101109184848_add_auth_source_id_to_users.rb new file mode 100644 index 000000000..49fef78a5 --- /dev/null +++ b/db/migrate/20101109184848_add_auth_source_id_to_users.rb @@ -0,0 +1,9 @@ +class AddAuthSourceIdToUsers < ActiveRecord::Migration + def self.up + add_column :users, :auth_source_id, :integer + end + + def self.down + remove_column :users, :auth_source_id + end +end diff --git a/db/migrate/20101109185024_create_auth_sources.rb b/db/migrate/20101109185024_create_auth_sources.rb new file mode 100644 index 000000000..9cc20a949 --- /dev/null +++ b/db/migrate/20101109185024_create_auth_sources.rb @@ -0,0 +1,24 @@ +class CreateAuthSources < ActiveRecord::Migration + def self.up + create_table "auth_sources", :force => true do |t| + t.column "type", :string, :limit => 30, :default => "", :null => false + t.column "name", :string, :limit => 60, :default => "", :null => false + t.column "host", :string, :limit => 60 + t.column "port", :integer + t.column "account", :string, :limit => nil + t.column "account_password", :string, :limit => 60 + t.column "base_dn", :string, :limit => 255 + t.column "attr_login", :string, :limit => 30 + t.column "attr_firstname", :string, :limit => 30 + t.column "attr_lastname", :string, :limit => 30 + t.column "attr_mail", :string, :limit => 30 + t.column "onthefly_register", :boolean, :default => false, :null => false + t.column 'tls', :boolean, :default => false, :null => false + t.timestamps + end + end + + def self.down + drop_table :auth_sources + end +end diff --git a/db/migrate/20101110193943_add_can_manage_fields_to_users.rb b/db/migrate/20101110193943_add_can_manage_fields_to_users.rb new file mode 100644 index 000000000..34189fe2d --- /dev/null +++ b/db/migrate/20101110193943_add_can_manage_fields_to_users.rb @@ -0,0 +1,17 @@ +class AddCanManageFieldsToUsers < ActiveRecord::Migration + def self.up + add_column :users, :manage_hosts, :boolean, :default => false + add_column :users, :manage_recipes, :boolean, :default => false + add_column :users, :manage_users, :boolean, :default => false + add_column :users, :manage_stages, :boolean, :default => false + add_column :users, :manage_projects, :boolean, :default => false + end + + def self.down + remove_column :users, :manage_hosts + remove_column :users, :manage_recipes + remove_column :users, :manage_users + remove_column :users, :manage_stages + remove_column :users, :manage_projects + end +end diff --git a/db/migrate/20101110200852_create_user_project_links.rb b/db/migrate/20101110200852_create_user_project_links.rb new file mode 100644 index 000000000..2925cd94e --- /dev/null +++ b/db/migrate/20101110200852_create_user_project_links.rb @@ -0,0 +1,14 @@ +class CreateUserProjectLinks < ActiveRecord::Migration + def self.up + create_table :user_project_links do |t| + t.integer :user_id + t.integer :project_id + + t.timestamps + end + end + + def self.down + drop_table :user_project_links + end +end diff --git a/db/migrate/20101201002226_add_archived_flag_to_projects.rb b/db/migrate/20101201002226_add_archived_flag_to_projects.rb new file mode 100644 index 000000000..5a7a2d782 --- /dev/null +++ b/db/migrate/20101201002226_add_archived_flag_to_projects.rb @@ -0,0 +1,9 @@ +class AddArchivedFlagToProjects < ActiveRecord::Migration + def self.up + add_column :projects, :archived, :boolean, :default => false + end + + def self.down + remove_column :projects, :archived + end +end \ No newline at end of file diff --git a/db/migrate/017_add_default_user.rb b/db/migrate/20131220141503_add_default_user.rb similarity index 91% rename from db/migrate/017_add_default_user.rb rename to db/migrate/20131220141503_add_default_user.rb index 25a89099b..22720a6d1 100644 --- a/db/migrate/017_add_default_user.rb +++ b/db/migrate/20131220141503_add_default_user.rb @@ -1,4 +1,4 @@ -require "#{RAILS_ROOT}/app/models/user" +require "#{Rails.root.to_s}/app/models/user" class AddDefaultUser < ActiveRecord::Migration def self.up diff --git a/db/schema.rb b/db/schema.rb index 8ba3d7003..2808641e6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1,16 +1,36 @@ -# This file is auto-generated from the current state of the database. Instead of editing this file, -# please use the migrations feature of Active Record to incrementally modify your database, and -# then regenerate this schema definition. +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. # -# Note that this schema.rb definition is the authoritative source for your database schema. If you need -# to create the application database on another system, you should be using db:schema:load, not running -# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # # It's strongly recommended to check this file into your version control system. ActiveRecord::Schema.define(:version => 20110512144542) do + create_table "auth_sources", :force => true do |t| + t.string "type", :limit => 30, :default => "", :null => false + t.string "name", :limit => 60, :default => "", :null => false + t.string "host", :limit => 60 + t.integer "port" + t.string "account" + t.string "account_password", :limit => 60 + t.string "base_dn" + t.string "attr_login", :limit => 30 + t.string "attr_firstname", :limit => 30 + t.string "attr_lastname", :limit => 30 + t.string "attr_mail", :limit => 30 + t.boolean "onthefly_register", :default => false, :null => false + t.boolean "tls", :default => false, :null => false + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "configuration_parameters", :force => true do |t| t.string "name" t.string "value" @@ -60,6 +80,7 @@ t.string "template" t.datetime "created_at" t.datetime "updated_at" + t.boolean "archived", :default => false end create_table "recipe_versions", :force => true do |t| @@ -116,6 +137,13 @@ add_index "stages", ["project_id"], :name => "index_stages_on_project_id" + create_table "user_project_links", :force => true do |t| + t.integer "user_id" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "users", :force => true do |t| t.string "login" t.string "email" @@ -128,6 +156,12 @@ t.integer "admin", :default => 0 t.string "time_zone", :default => "UTC" t.datetime "disabled" + t.integer "auth_source_id" + t.boolean "manage_hosts", :default => false + t.boolean "manage_recipes", :default => false + t.boolean "manage_users", :default => false + t.boolean "manage_stages", :default => false + t.boolean "manage_projects", :default => false end add_index "users", ["disabled"], :name => "index_users_on_disabled" diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 000000000..664d8c74c --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,7 @@ +# This file should contain all the record creation needed to seed the database with its default values. +# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). +# +# Examples: +# +# cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }]) +# Mayor.create(:name => 'Daley', :city => cities.first) diff --git a/doc/README_FOR_APP b/doc/README_FOR_APP index ac6c14912..fe41f5cc2 100644 --- a/doc/README_FOR_APP +++ b/doc/README_FOR_APP @@ -1,2 +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 +Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. diff --git a/doc/passenger.rst b/doc/passenger.rst new file mode 100644 index 000000000..d3456bc0d --- /dev/null +++ b/doc/passenger.rst @@ -0,0 +1,26 @@ +Install and server through passenger with apache + +- enable libapache2-mod-passenger (aptitude install libapache2-mod-passenger) +- enable rewrite (a2enmod rewrite) + +/etc/apache2/sites-available/webistrano + + ServerName server.example.com + DocumentRoot /path/to/rails/app/public + + # set passenger root for ubuntu installations + PassengerRoot /usr/lib/phusion_passenger + + # path to ruby executable + PassengerRuby /home/vagrant/.rvm/rubies/ruby-1.8.7-p374/bin/ruby + + # endpoint + RailsBaseURI / + + # rails environment to load + RailsEnv development + + +- enable site (a2ensite webistrano) +- restart apache (/etc/init.d apache2 restart) + diff --git a/vendor/gems/open4-0.9.3/open4-0.9.3.gem b/lib/tasks/.gitkeep similarity index 100% rename from vendor/gems/open4-0.9.3/open4-0.9.3.gem rename to lib/tasks/.gitkeep diff --git a/lib/webistrano/deployer.rb b/lib/webistrano/deployer.rb index 327ef6945..bb9e28f17 100644 --- a/lib/webistrano/deployer.rb +++ b/lib/webistrano/deployer.rb @@ -22,7 +22,7 @@ def initialize(deployment) @deployment = deployment - if(@deployment.task && !@deployment.new_record?) + if(@deployment.send(:task) && !@deployment.new_record?) # a read deployment @logger = Webistrano::Logger.new(deployment) @logger.level = Webistrano::Logger::TRACE @@ -63,8 +63,12 @@ def execute! set_webistrano_logger(config) set_up_config(config) - - exchange_real_revision(config) unless (config.fetch(:scm).to_s == 'git') # git cannot do a local query by default + + # git and mercurial cannot do a local query by default + unless %w(git mercurial).include? config.fetch(:scm).to_s + exchange_real_revision(config) + end + save_revision(config) save_pid diff --git a/public/404.html b/public/404.html index eff660b90..9a48320a5 100644 --- a/public/404.html +++ b/public/404.html @@ -1,23 +1,19 @@ - - - - + + - The page you were looking for doesn't exist (404) - + @@ -27,4 +23,4 @@

The page you were looking for doesn't exist.

You may have mistyped the address or the page may have moved.

- \ No newline at end of file + diff --git a/public/422.html b/public/422.html index b54e4a3ca..83660ab18 100644 --- a/public/422.html +++ b/public/422.html @@ -1,23 +1,19 @@ - - - - + + - The change you wanted was rejected (422) - + @@ -27,4 +23,4 @@

The change you wanted was rejected.

Maybe you tried to change something you didn't have access to.

- \ No newline at end of file + diff --git a/public/500.html b/public/500.html index f0aee0e9f..b80307fc1 100644 --- a/public/500.html +++ b/public/500.html @@ -1,23 +1,19 @@ - - - - + + - - We're sorry, but something went wrong - + We're sorry, but something went wrong (500) + @@ -27,4 +23,4 @@

We're sorry, but something went wrong.

We've been notified about this issue and we'll take a look at it shortly.

- \ No newline at end of file + diff --git a/public/dispatch.cgi b/public/dispatch.cgi index a76782ae0..9b5ae760f 100755 --- a/public/dispatch.cgi +++ b/public/dispatch.cgi @@ -1,4 +1,4 @@ -#!/opt/local/bin/ruby +#!/usr/local/bin/ruby require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) diff --git a/public/dispatch.fcgi b/public/dispatch.fcgi index a5267664e..65188f380 100755 --- a/public/dispatch.fcgi +++ b/public/dispatch.fcgi @@ -1,4 +1,4 @@ -#!/opt/local/bin/ruby +#!/usr/local/bin/ruby # # You may specify the path to the FastCGI crash log (a log of unhandled # exceptions which forced the FastCGI instance to exit, great for debugging) diff --git a/public/dispatch.rb b/public/dispatch.rb index a76782ae0..4e7790d9a 100755 --- a/public/dispatch.rb +++ b/public/dispatch.rb @@ -1,6 +1,6 @@ -#!/opt/local/bin/ruby +#!/usr/local/bin/ruby -require File.dirname(__FILE__) + "/../config/environment" unless defined?(RAILS_ROOT) +require File.dirname(__FILE__) + "/../config/environment" unless defined?(Rails.root) # If you're using RubyGems and mod_ruby, this require should be changed to an absolute path one, like: # "/usr/local/lib/ruby/gems/1.8/gems/rails-0.8.0/lib/dispatcher" -- otherwise performance is severely impaired diff --git a/public/javascripts/application.js b/public/javascripts/application.js index 2ee02d19e..b801ae15a 100644 --- a/public/javascripts/application.js +++ b/public/javascripts/application.js @@ -1,49 +1,49 @@ // open a new window function loadWindow(url) { - popupWin = window.open(url, 'new_window', 'scrollbars=no,status=no,toolbar=no,location=no,directories=no,menubar=no,width=960, height=700, top=50, left=50, resizable=1, scrollbars=yes'); - popupWin.focus(); + popupWin = window.open(url, 'new_window', 'scrollbars=no,status=no,toolbar=no,location=no,directories=no,menubar=no,width=960, height=700, top=50, left=50, resizable=1, scrollbars=yes'); + popupWin.focus(); } /* Create Menu Links */ function open_menu(dom_id){ - // arrow images - $(dom_id + "_arrow_right").hide(); - $(dom_id + "_arrow_down").show(); - - // stages - $(dom_id + "_stages").show(); + // arrow images + $(dom_id + "_arrow_right").hide(); + $(dom_id + "_arrow_down").show(); + + // stages + $(dom_id + "_stages").show(); } function close_menu(dom_id){ - // arrow images - $(dom_id + "_arrow_right").show(); - $(dom_id + "_arrow_down").hide(); - - // stages - $(dom_id + "_stages").hide(); + // arrow images + $(dom_id + "_arrow_right").show(); + $(dom_id + "_arrow_down").hide(); + + // stages + $(dom_id + "_stages").hide(); } function open_menu_box(dom_id){ - // arrow images - $(dom_id + "_arrow_right").hide(); - $(dom_id + "_arrow_down").show(); - - // show box body - $(dom_id + "_open_content").show(); - - // hide closed info - $(dom_id + "_closed_content").hide(); + // arrow images + $(dom_id + "_arrow_right").hide(); + $(dom_id + "_arrow_down").show(); + + // show box body + $(dom_id + "_open_content").show(); + + // hide closed info + $(dom_id + "_closed_content").hide(); } function close_menu_box(dom_id){ - // arrow images - $(dom_id + "_arrow_right").show(); - $(dom_id + "_arrow_down").hide(); - - // show box body - $(dom_id + "_open_content").hide(); - - // hide closed info - $(dom_id + "_closed_content").show(); -} \ No newline at end of file + // arrow images + $(dom_id + "_arrow_right").show(); + $(dom_id + "_arrow_down").hide(); + + // show box body + $(dom_id + "_open_content").hide(); + + // hide closed info + $(dom_id + "_closed_content").show(); +} diff --git a/public/javascripts/controls.js b/public/javascripts/controls.js index 5aaf0bb2b..7392fb664 100644 --- a/public/javascripts/controls.js +++ b/public/javascripts/controls.js @@ -1,22 +1,24 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Ivan Krstic (http://blogs.law.harvard.edu/ivan) -// (c) 2005-2007 Jon Tirsen (http://www.tirsen.com) +// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2009 Jon Tirsen (http://www.tirsen.com) // Contributors: // Richard Livsey // Rahul Bhargava // Rob Wills -// +// // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ -// Autocompleter.Base handles all the autocompletion functionality +// Autocompleter.Base handles all the autocompletion functionality // that's independent of the data source for autocompletion. This // includes drawing the autocompletion menu, observing keyboard // and mouse events, and similar. // -// Specific autocompleters need to provide, at the very least, +// Specific autocompleters need to provide, at the very least, // a getUpdatedChoices function that will be invoked every time -// the text inside the monitored textbox changes. This method +// the text inside the monitored textbox changes. This method // should get the text for which to provide autocompletion by // invoking this.getToken(), NOT by directly accessing // this.element.value. This is to allow incremental tokenized @@ -30,23 +32,23 @@ // will incrementally autocomplete with a comma as the token. // Additionally, ',' in the above example can be replaced with // a token array, e.g. { tokens: [',', '\n'] } which -// enables autocompletion on multiple tokens. This is most -// useful when one of the tokens is \n (a newline), as it +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it // allows smart autocompletion after linebreaks. if(typeof Effect == 'undefined') throw("controls.js requires including script.aculo.us' effects.js library"); -var Autocompleter = { } +var Autocompleter = { }; Autocompleter.Base = Class.create({ baseInitialize: function(element, update, options) { - element = $(element) - this.element = element; - this.update = $(update); - this.hasFocus = false; - this.changed = false; - this.active = false; - this.index = 0; + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; this.entryCount = 0; this.oldElementValue = this.element.value; @@ -59,28 +61,28 @@ Autocompleter.Base = Class.create({ this.options.tokens = this.options.tokens || []; this.options.frequency = this.options.frequency || 0.4; this.options.minChars = this.options.minChars || 1; - this.options.onShow = this.options.onShow || - function(element, update){ + this.options.onShow = this.options.onShow || + function(element, update){ if(!update.style.position || update.style.position=='absolute') { update.style.position = 'absolute'; Position.clone(element, update, { - setHeight: false, + setHeight: false, offsetTop: element.offsetHeight }); } Effect.Appear(update,{duration:0.15}); }; - this.options.onHide = this.options.onHide || + this.options.onHide = this.options.onHide || function(element, update){ new Effect.Fade(update,{duration:0.15}) }; - if(typeof(this.options.tokens) == 'string') + if(typeof(this.options.tokens) == 'string') this.options.tokens = new Array(this.options.tokens); // Force carriage returns as token delimiters anyway if (!this.options.tokens.include('\n')) this.options.tokens.push('\n'); this.observer = null; - + this.element.setAttribute('autocomplete','off'); Element.hide(this.update); @@ -91,10 +93,10 @@ Autocompleter.Base = Class.create({ show: function() { if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); - if(!this.iefix && + if(!this.iefix && (Prototype.Browser.IE) && (Element.getStyle(this.update, 'position')=='absolute')) { - new Insertion.After(this.update, + new Insertion.After(this.update, ''); @@ -102,7 +104,7 @@ Autocompleter.Base = Class.create({ } if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); }, - + fixIEOverlapping: function() { Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); this.iefix.style.zIndex = 1; @@ -150,15 +152,15 @@ Autocompleter.Base = Class.create({ Event.stop(event); return; } - else - if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; this.changed = true; this.hasFocus = true; if(this.observer) clearTimeout(this.observer); - this.observer = + this.observer = setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); }, @@ -170,35 +172,35 @@ Autocompleter.Base = Class.create({ onHover: function(event) { var element = Event.findElement(event, 'LI'); - if(this.index != element.autocompleteIndex) + if(this.index != element.autocompleteIndex) { this.index = element.autocompleteIndex; this.render(); } Event.stop(event); }, - + onClick: function(event) { var element = Event.findElement(event, 'LI'); this.index = element.autocompleteIndex; this.selectEntry(); this.hide(); }, - + onBlur: function(event) { // needed to make click events working setTimeout(this.hide.bind(this), 250); this.hasFocus = false; - this.active = false; - }, - + this.active = false; + }, + render: function() { if(this.entryCount > 0) { for (var i = 0; i < this.entryCount; i++) - this.index==i ? - Element.addClassName(this.getEntry(i),"selected") : + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : Element.removeClassName(this.getEntry(i),"selected"); - if(this.hasFocus) { + if(this.hasFocus) { this.show(); this.active = true; } @@ -207,27 +209,27 @@ Autocompleter.Base = Class.create({ this.hide(); } }, - + markPrevious: function() { - if(this.index > 0) this.index-- + if(this.index > 0) this.index--; else this.index = this.entryCount-1; this.getEntry(this.index).scrollIntoView(true); }, - + markNext: function() { - if(this.index < this.entryCount-1) this.index++ + if(this.index < this.entryCount-1) this.index++; else this.index = 0; this.getEntry(this.index).scrollIntoView(false); }, - + getEntry: function(index) { return this.update.firstChild.childNodes[index]; }, - + getCurrentEntry: function() { return this.getEntry(this.index); }, - + selectEntry: function() { this.active = false; this.updateElement(this.getCurrentEntry()); @@ -244,7 +246,7 @@ Autocompleter.Base = Class.create({ if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); } else value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); - + var bounds = this.getTokenBounds(); if (bounds[0] != -1) { var newValue = this.element.value.substr(0, bounds[0]); @@ -257,7 +259,7 @@ Autocompleter.Base = Class.create({ } this.oldElementValue = this.element.value; this.element.focus(); - + if (this.options.afterUpdateElement) this.options.afterUpdateElement(this.element, selectedElement); }, @@ -269,20 +271,20 @@ Autocompleter.Base = Class.create({ Element.cleanWhitespace(this.update.down()); if(this.update.firstChild && this.update.down().childNodes) { - this.entryCount = + this.entryCount = this.update.down().childNodes.length; for (var i = 0; i < this.entryCount; i++) { var entry = this.getEntry(i); entry.autocompleteIndex = i; this.addObservers(entry); } - } else { + } else { this.entryCount = 0; } this.stopIndicator(); this.index = 0; - + if(this.entryCount==1 && this.options.autoSelect) { this.selectEntry(); this.hide(); @@ -298,7 +300,7 @@ Autocompleter.Base = Class.create({ }, onObserverEvent: function() { - this.changed = false; + this.changed = false; this.tokenBounds = null; if(this.getToken().length>=this.options.minChars) { this.getUpdatedChoices(); @@ -351,16 +353,16 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { getUpdatedChoices: function() { this.startIndicator(); - - var entry = encodeURIComponent(this.options.paramName) + '=' + + + var entry = encodeURIComponent(this.options.paramName) + '=' + encodeURIComponent(this.getToken()); this.options.parameters = this.options.callback ? this.options.callback(this.element, entry) : entry; - if(this.options.defaultParams) + if(this.options.defaultParams) this.options.parameters += '&' + this.options.defaultParams; - + new Ajax.Request(this.url, this.options); }, @@ -382,7 +384,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { // - choices - How many autocompletion choices to offer // // - partialSearch - If false, the autocompleter will match entered -// text only at the beginning of strings in the +// text only at the beginning of strings in the // autocomplete array. Defaults to true, which will // match text at the beginning of any *word* in the // strings in the autocomplete array. If you want to @@ -399,7 +401,7 @@ Ajax.Autocompleter = Class.create(Autocompleter.Base, { // - ignoreCase - Whether to ignore case when autocompleting. // Defaults to true. // -// It's possible to pass in a custom function as the 'selector' +// It's possible to pass in a custom function as the 'selector' // option, if you prefer to write your own autocompletion logic. // In that case, the other options above will not apply unless // you support them. @@ -427,20 +429,20 @@ Autocompleter.Local = Class.create(Autocompleter.Base, { var entry = instance.getToken(); var count = 0; - for (var i = 0; i < instance.options.array.length && - ret.length < instance.options.choices ; i++) { + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { var elem = instance.options.array[i]; - var foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase()) : + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : elem.indexOf(entry); while (foundPos != -1) { - if (foundPos == 0 && elem.length != entry.length) { - ret.push("
  • " + elem.substr(0, entry.length) + "" + + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + elem.substr(entry.length) + "
  • "); break; - } else if (entry.length >= instance.options.partialChars && + } else if (entry.length >= instance.options.partialChars && instance.options.partialSearch && foundPos != -1) { if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { partial.push("
  • " + elem.substr(0, foundPos) + "" + @@ -450,14 +452,14 @@ Autocompleter.Local = Class.create(Autocompleter.Base, { } } - foundPos = instance.options.ignoreCase ? - elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : elem.indexOf(entry, foundPos + 1); } } if (partial.length) - ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); return "
      " + ret.join('') + "
    "; } }, options || { }); @@ -474,7 +476,7 @@ Field.scrollFreeActivate = function(field) { setTimeout(function() { Field.activate(field); }, 1); -} +}; Ajax.InPlaceEditor = Class.create({ initialize: function(element, url, options) { @@ -604,7 +606,7 @@ Ajax.InPlaceEditor = Class.create({ this.triggerCallback('onEnterHover'); }, getText: function() { - return this.element.innerHTML; + return this.element.innerHTML.unescapeHTML(); }, handleAJAXFailure: function(transport) { this.triggerCallback('onFailure', transport); @@ -780,7 +782,7 @@ Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { onSuccess: function(transport) { var js = transport.responseText.strip(); if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check - throw 'Server returned an invalid collection representation.'; + throw('Server returned an invalid collection representation.'); this._collection = eval(js); this.checkForExternalText(); }.bind(this), @@ -937,7 +939,7 @@ Ajax.InPlaceCollectionEditor.DefaultOptions = { loadingCollectionText: 'Loading options...' }; -// Delayed observer, like Form.Element.Observer, +// Delayed observer, like Form.Element.Observer, // but waits for delay after last key input // Ideal for live-search fields @@ -947,7 +949,7 @@ Form.Element.DelayedObserver = Class.create({ this.element = $(element); this.callback = callback; this.timer = null; - this.lastValue = $F(this.element); + this.lastValue = $F(this.element); Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); }, delayedListener: function(event) { @@ -960,4 +962,4 @@ Form.Element.DelayedObserver = Class.create({ this.timer = null; this.callback(this.element, $F(this.element)); } -}); +}); \ No newline at end of file diff --git a/public/javascripts/dragdrop.js b/public/javascripts/dragdrop.js index bf5cfea66..15c6dbca6 100644 --- a/public/javascripts/dragdrop.js +++ b/public/javascripts/dragdrop.js @@ -1,6 +1,7 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) -// (c) 2005-2007 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) -// +// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// // script.aculo.us is freely distributable under the terms of an MIT-style license. // For details, see the script.aculo.us web site: http://script.aculo.us/ @@ -32,7 +33,7 @@ var Droppables = { options._containers.push($(containment)); } } - + if(options.accept) options.accept = [options.accept].flatten(); Element.makePositioned(element); // fix IE @@ -40,34 +41,34 @@ var Droppables = { this.drops.push(options); }, - + findDeepestChild: function(drops) { deepest = drops[0]; - + for (i = 1; i < drops.length; ++i) if (Element.isParent(drops[i].element, deepest.element)) deepest = drops[i]; - + return deepest; }, isContained: function(element, drop) { var containmentNode; if(drop.tree) { - containmentNode = element.treeNode; + containmentNode = element.treeNode; } else { containmentNode = element.parentNode; } return drop._containers.detect(function(c) { return containmentNode == c }); }, - + isAffected: function(point, element, drop) { return ( (drop.element!=element) && ((!drop._containers) || this.isContained(element, drop)) && ((!drop.accept) || - (Element.classNames(element).detect( + (Element.classNames(element).detect( function(v) { return drop.accept.include(v) } ) )) && Position.within(drop.element, point[0], point[1]) ); }, @@ -87,12 +88,12 @@ var Droppables = { show: function(point, element) { if(!this.drops.length) return; var drop, affected = []; - + this.drops.each( function(drop) { if(Droppables.isAffected(point, element, drop)) affected.push(drop); }); - + if(affected.length>0) drop = Droppables.findDeepestChild(affected); @@ -101,7 +102,7 @@ var Droppables = { Position.within(drop.element, point[0], point[1]); if(drop.onHover) drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); - + if (drop != this.last_active) Droppables.activate(drop); } }, @@ -112,8 +113,8 @@ var Droppables = { if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) if (this.last_active.onDrop) { - this.last_active.onDrop(element, this.last_active.element, event); - return true; + this.last_active.onDrop(element, this.last_active.element, event); + return true; } }, @@ -121,25 +122,25 @@ var Droppables = { if(this.last_active) this.deactivate(this.last_active); } -} +}; var Draggables = { drags: [], observers: [], - + register: function(draggable) { if(this.drags.length == 0) { this.eventMouseUp = this.endDrag.bindAsEventListener(this); this.eventMouseMove = this.updateDrag.bindAsEventListener(this); this.eventKeypress = this.keyPress.bindAsEventListener(this); - + Event.observe(document, "mouseup", this.eventMouseUp); Event.observe(document, "mousemove", this.eventMouseMove); Event.observe(document, "keypress", this.eventKeypress); } this.drags.push(draggable); }, - + unregister: function(draggable) { this.drags = this.drags.reject(function(d) { return d==draggable }); if(this.drags.length == 0) { @@ -148,24 +149,24 @@ var Draggables = { Event.stopObserving(document, "keypress", this.eventKeypress); } }, - + activate: function(draggable) { - if(draggable.options.delay) { - this._timeout = setTimeout(function() { - Draggables._timeout = null; - window.focus(); - Draggables.activeDraggable = draggable; - }.bind(this), draggable.options.delay); + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); } else { window.focus(); // allows keypress events if window isn't currently focused, fails for Safari this.activeDraggable = draggable; } }, - + deactivate: function() { this.activeDraggable = null; }, - + updateDrag: function(event) { if(!this.activeDraggable) return; var pointer = [Event.pointerX(event), Event.pointerY(event)]; @@ -173,36 +174,36 @@ var Draggables = { // the same coordinates, prevent needless redrawing (moz bug?) if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; this._lastPointer = pointer; - + this.activeDraggable.updateDrag(event, pointer); }, - + endDrag: function(event) { - if(this._timeout) { - clearTimeout(this._timeout); - this._timeout = null; + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; } if(!this.activeDraggable) return; this._lastPointer = null; this.activeDraggable.endDrag(event); this.activeDraggable = null; }, - + keyPress: function(event) { if(this.activeDraggable) this.activeDraggable.keyPress(event); }, - + addObserver: function(observer) { this.observers.push(observer); this._cacheObserverCallbacks(); }, - + removeObserver: function(element) { // element instead of observer fixes mem leaks this.observers = this.observers.reject( function(o) { return o.element==element }); this._cacheObserverCallbacks(); }, - + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' if(this[eventName+'Count'] > 0) this.observers.each( function(o) { @@ -210,7 +211,7 @@ var Draggables = { }); if(draggable.options[eventName]) draggable.options[eventName](draggable, event); }, - + _cacheObserverCallbacks: function() { ['onStart','onEnd','onDrag'].each( function(eventName) { Draggables[eventName+'Count'] = Draggables.observers.select( @@ -218,7 +219,7 @@ var Draggables = { ).length; }); } -} +}; /*--------------------------------------------------------------------------*/ @@ -234,12 +235,12 @@ var Draggable = Class.create({ }, endeffect: function(element) { var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; - new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, queue: {scope:'_draggable', position:'end'}, - afterFinish: function(){ - Draggable._dragging[element] = false + afterFinish: function(){ + Draggable._dragging[element] = false } - }); + }); }, zindex: 1000, revert: false, @@ -250,57 +251,57 @@ var Draggable = Class.create({ snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } delay: 0 }; - + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) Object.extend(defaults, { starteffect: function(element) { element._opacity = Element.getOpacity(element); Draggable._dragging[element] = true; - new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); } }); - + var options = Object.extend(defaults, arguments[1] || { }); this.element = $(element); - + if(options.handle && Object.isString(options.handle)) this.handle = this.element.down('.'+options.handle, 0); - + if(!this.handle) this.handle = $(options.handle); if(!this.handle) this.handle = this.element; - + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { options.scroll = $(options.scroll); this._isScrollChild = Element.childOf(this.element, options.scroll); } - Element.makePositioned(this.element); // fix IE + Element.makePositioned(this.element); // fix IE this.options = options; - this.dragging = false; + this.dragging = false; this.eventMouseDown = this.initDrag.bindAsEventListener(this); Event.observe(this.handle, "mousedown", this.eventMouseDown); - + Draggables.register(this); }, - + destroy: function() { Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); Draggables.unregister(this); }, - + currentDelta: function() { return([ parseInt(Element.getStyle(this.element,'left') || '0'), parseInt(Element.getStyle(this.element,'top') || '0')]); }, - + initDrag: function(event) { if(!Object.isUndefined(Draggable._dragging[this.element]) && Draggable._dragging[this.element]) return; - if(Event.isLeftClick(event)) { + if(Event.isLeftClick(event)) { // abort on form elements, fixes a Firefox issue var src = Event.element(event); if((tag_name = src.tagName.toUpperCase()) && ( @@ -309,34 +310,34 @@ var Draggable = Class.create({ tag_name=='OPTION' || tag_name=='BUTTON' || tag_name=='TEXTAREA')) return; - + var pointer = [Event.pointerX(event), Event.pointerY(event)]; - var pos = Position.cumulativeOffset(this.element); + var pos = this.element.cumulativeOffset(); this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); - + Draggables.activate(this); Event.stop(event); } }, - + startDrag: function(event) { this.dragging = true; if(!this.delta) this.delta = this.currentDelta(); - + if(this.options.zindex) { this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); this.element.style.zIndex = this.options.zindex; } - + if(this.options.ghosting) { this._clone = this.element.cloneNode(true); - this.element._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); - if (!this.element._originallyAbsolute) + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) Position.absolutize(this.element); this.element.parentNode.insertBefore(this._clone, this.element); } - + if(this.options.scroll) { if (this.options.scroll == window) { var where = this._getWindowScroll(this.options.scroll); @@ -347,28 +348,28 @@ var Draggable = Class.create({ this.originalScrollTop = this.options.scroll.scrollTop; } } - + Draggables.notify('onStart', this, event); - + if(this.options.starteffect) this.options.starteffect(this.element); }, - + updateDrag: function(event, pointer) { if(!this.dragging) this.startDrag(event); - + if(!this.options.quiet){ Position.prepare(); Droppables.show(pointer, this.element); } - + Draggables.notify('onDrag', this, event); - + this.draw(pointer); if(this.options.change) this.options.change(this); - + if(this.options.scroll) { this.stopScrolling(); - + var p; if (this.options.scroll == window) { with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } @@ -386,16 +387,16 @@ var Draggable = Class.create({ if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); this.startScrolling(speed); } - + // fix AppleWebKit rendering if(Prototype.Browser.WebKit) window.scrollBy(0,0); - + Event.stop(event); }, - + finishDrag: function(event, success) { this.dragging = false; - + if(this.options.quiet){ Position.prepare(); var pointer = [Event.pointerX(event), Event.pointerY(event)]; @@ -403,24 +404,24 @@ var Draggable = Class.create({ } if(this.options.ghosting) { - if (!this.element._originallyAbsolute) + if (!this._originallyAbsolute) Position.relativize(this.element); - delete this.element._originallyAbsolute; + delete this._originallyAbsolute; Element.remove(this._clone); this._clone = null; } - var dropped = false; - if(success) { - dropped = Droppables.fire(event, this.element); - if (!dropped) dropped = false; + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; } if(dropped && this.options.onDropped) this.options.onDropped(this.element); Draggables.notify('onEnd', this, event); var revert = this.options.revert; if(revert && Object.isFunction(revert)) revert = revert(this.element); - + var d = this.currentDelta(); if(revert && this.options.reverteffect) { if (dropped == 0 || revert != 'failure') @@ -433,67 +434,67 @@ var Draggable = Class.create({ if(this.options.zindex) this.element.style.zIndex = this.originalZ; - if(this.options.endeffect) + if(this.options.endeffect) this.options.endeffect(this.element); - + Draggables.deactivate(this); Droppables.reset(); }, - + keyPress: function(event) { if(event.keyCode!=Event.KEY_ESC) return; this.finishDrag(event, false); Event.stop(event); }, - + endDrag: function(event) { if(!this.dragging) return; this.stopScrolling(); this.finishDrag(event, true); Event.stop(event); }, - + draw: function(point) { - var pos = Position.cumulativeOffset(this.element); + var pos = this.element.cumulativeOffset(); if(this.options.ghosting) { var r = Position.realOffset(this.element); pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; } - + var d = this.currentDelta(); pos[0] -= d[0]; pos[1] -= d[1]; - + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; } - - var p = [0,1].map(function(i){ - return (point[i]-pos[i]-this.offset[i]) + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) }.bind(this)); - + if(this.options.snap) { if(Object.isFunction(this.options.snap)) { p = this.options.snap(p[0],p[1],this); } else { if(Object.isArray(this.options.snap)) { p = p.map( function(v, i) { - return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)) + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); } else { p = p.map( function(v) { - return (v/this.options.snap).round()*this.options.snap }.bind(this)) + return (v/this.options.snap).round()*this.options.snap }.bind(this)); } }} - + var style = this.element.style; if((!this.options.constraint) || (this.options.constraint=='horizontal')) style.left = p[0] + "px"; if((!this.options.constraint) || (this.options.constraint=='vertical')) style.top = p[1] + "px"; - + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering }, - + stopScrolling: function() { if(this.scrollInterval) { clearInterval(this.scrollInterval); @@ -501,14 +502,14 @@ var Draggable = Class.create({ Draggables._lastScrollPointer = null; } }, - + startScrolling: function(speed) { if(!(speed[0] || speed[1])) return; this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; this.lastScrolled = new Date(); this.scrollInterval = setInterval(this.scroll.bind(this), 10); }, - + scroll: function() { var current = new Date(); var delta = current - this.lastScrolled; @@ -524,7 +525,7 @@ var Draggable = Class.create({ this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; } - + Position.prepare(); Droppables.show(Draggables._lastPointer, this.element); Draggables.notify('onDrag', this); @@ -538,10 +539,10 @@ var Draggable = Class.create({ Draggables._lastScrollPointer[1] = 0; this.draw(Draggables._lastScrollPointer); } - + if(this.options.change) this.options.change(this); }, - + _getWindowScroll: function(w) { var T, L, W, H; with (w.document) { @@ -560,7 +561,7 @@ var Draggable = Class.create({ H = documentElement.clientHeight; } else { W = body.offsetWidth; - H = body.offsetHeight + H = body.offsetHeight; } } return { top: T, left: L, width: W, height: H }; @@ -577,11 +578,11 @@ var SortableObserver = Class.create({ this.observer = observer; this.lastValue = Sortable.serialize(this.element); }, - + onStart: function() { this.lastValue = Sortable.serialize(this.element); }, - + onEnd: function() { Sortable.unmark(); if(this.lastValue != Sortable.serialize(this.element)) @@ -591,11 +592,11 @@ var SortableObserver = Class.create({ var Sortable = { SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, - + sortables: { }, - + _findRootElement: function(element) { - while (element.tagName.toUpperCase() != "BODY") { + while (element.tagName.toUpperCase() != "BODY") { if(element.id && Sortable.sortables[element.id]) return element; element = element.parentNode; } @@ -606,22 +607,23 @@ var Sortable = { if(!element) return; return Sortable.sortables[element.id]; }, - + destroy: function(element){ - var s = Sortable.options(element); - + element = $(element); + var s = Sortable.sortables[element.id]; + if(s) { Draggables.removeObserver(s.element); s.droppables.each(function(d){ Droppables.remove(d) }); s.draggables.invoke('destroy'); - + delete Sortable.sortables[s.element.id]; } }, create: function(element) { element = $(element); - var options = Object.extend({ + var options = Object.extend({ element: element, tag: 'li', // assumes li children, override with tag: 'tagname' dropOnEmpty: false, @@ -635,17 +637,17 @@ var Sortable = { delay: 0, hoverclass: null, ghosting: false, - quiet: false, + quiet: false, scroll: false, scrollSensitivity: 20, scrollSpeed: 15, format: this.SERIALIZE_RULE, - - // these take arrays of elements or ids and can be + + // these take arrays of elements or ids and can be // used for better initialization performance elements: false, handles: false, - + onChange: Prototype.emptyFunction, onUpdate: Prototype.emptyFunction }, arguments[1] || { }); @@ -682,24 +684,24 @@ var Sortable = { if(options.zindex) options_for_draggable.zindex = options.zindex; - // build options for the droppables + // build options for the droppables var options_for_droppable = { overlap: options.overlap, containment: options.containment, tree: options.tree, hoverclass: options.hoverclass, onHover: Sortable.onHover - } - + }; + var options_for_tree = { onHover: Sortable.onEmptyHover, overlap: options.overlap, containment: options.containment, hoverclass: options.hoverclass - } + }; // fix for gecko engine - Element.cleanWhitespace(element); + Element.cleanWhitespace(element); options.draggables = []; options.droppables = []; @@ -712,14 +714,14 @@ var Sortable = { (options.elements || this.findElements(element, options) || []).each( function(e,i) { var handle = options.handles ? $(options.handles[i]) : - (options.handle ? $(e).select('.' + options.handle)[0] : e); + (options.handle ? $(e).select('.' + options.handle)[0] : e); options.draggables.push( new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); Droppables.add(e, options_for_droppable); if(options.tree) e.treeNode = element; - options.droppables.push(e); + options.droppables.push(e); }); - + if(options.tree) { (Sortable.findTreeElements(element, options) || []).each( function(e) { Droppables.add(e, options_for_tree); @@ -729,7 +731,7 @@ var Sortable = { } // keep reference - this.sortables[element.id] = options; + this.sortables[element.identify()] = options; // for onupdate Draggables.addObserver(new SortableObserver(element, options.onUpdate)); @@ -741,7 +743,7 @@ var Sortable = { return Element.findChildren( element, options.only, options.tree ? true : false, options.tag); }, - + findTreeElements: function(element, options) { return Element.findChildren( element, options.only, options.tree ? true : false, options.treeTag); @@ -758,7 +760,7 @@ var Sortable = { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, dropon); - if(dropon.parentNode!=oldParentNode) + if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } @@ -769,26 +771,26 @@ var Sortable = { var oldParentNode = element.parentNode; element.style.visibility = "hidden"; // fix gecko rendering dropon.parentNode.insertBefore(element, nextElement); - if(dropon.parentNode!=oldParentNode) + if(dropon.parentNode!=oldParentNode) Sortable.options(oldParentNode).onChange(element); Sortable.options(dropon.parentNode).onChange(element); } } }, - + onEmptyHover: function(element, dropon, overlap) { var oldParentNode = element.parentNode; var droponOptions = Sortable.options(dropon); - + if(!Element.isParent(dropon, element)) { var index; - + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); var child = null; - + if(children) { var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); - + for (index = 0; index < children.length; index += 1) { if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { offset -= Element.offsetSize (children[index], droponOptions.overlap); @@ -801,9 +803,9 @@ var Sortable = { } } } - + dropon.insertBefore(element, child); - + Sortable.options(oldParentNode).onChange(element); droponOptions.onChange(element); } @@ -816,34 +818,34 @@ var Sortable = { mark: function(dropon, position) { // mark on ghosting only var sortable = Sortable.options(dropon.parentNode); - if(sortable && !sortable.ghosting) return; + if(sortable && !sortable.ghosting) return; if(!Sortable._marker) { - Sortable._marker = + Sortable._marker = ($('dropmarker') || Element.extend(document.createElement('DIV'))). hide().addClassName('dropmarker').setStyle({position:'absolute'}); document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); - } - var offsets = Position.cumulativeOffset(dropon); + } + var offsets = dropon.cumulativeOffset(); Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); - + if(position=='after') - if(sortable.overlap == 'horizontal') + if(sortable.overlap == 'horizontal') Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); else Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); - + Sortable._marker.show(); }, - + _tree: function(element, options, parent) { var children = Sortable.findElements(element, options) || []; - + for (var i = 0; i < children.length; ++i) { var match = children[i].id.match(options.format); if (!match) continue; - + var child = { id: encodeURIComponent(match ? match[1] : null), element: element, @@ -851,16 +853,16 @@ var Sortable = { children: [], position: parent.children.length, container: $(children[i]).down(options.treeTag) - } - + }; + /* Get the element containing the children and recurse over it */ if (child.container) - this._tree(child.container, options, child) - + this._tree(child.container, options, child); + parent.children.push (child); } - return parent; + return parent; }, tree: function(element) { @@ -873,15 +875,15 @@ var Sortable = { name: element.id, format: sortableOptions.format }, arguments[1] || { }); - + var root = { id: null, parent: null, children: [], container: element, position: 0 - } - + }; + return Sortable._tree(element, options, root); }, @@ -897,7 +899,7 @@ var Sortable = { sequence: function(element) { element = $(element); var options = Object.extend(this.options(element), arguments[1] || { }); - + return $(this.findElements(element, options) || []).map( function(item) { return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; }); @@ -906,14 +908,14 @@ var Sortable = { setSequence: function(element, new_sequence) { element = $(element); var options = Object.extend(this.options(element), arguments[2] || { }); - + var nodeMap = { }; this.findElements(element, options).each( function(n) { if (n.id.match(options.format)) nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; n.parentNode.removeChild(n); }); - + new_sequence.each(function(ident) { var n = nodeMap[ident]; if (n) { @@ -922,16 +924,16 @@ var Sortable = { } }); }, - + serialize: function(element) { element = $(element); var options = Object.extend(Sortable.options(element), arguments[1] || { }); var name = encodeURIComponent( (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); - + if (options.tree) { return Sortable.tree(element, arguments[1]).children.map( function (item) { - return [name + Sortable._constructIndex(item) + "[id]=" + + return [name + Sortable._constructIndex(item) + "[id]=" + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); }).flatten().join('&'); } else { @@ -940,16 +942,16 @@ var Sortable = { }).join('&'); } } -} +}; // Returns true if child is contained within element Element.isParent = function(child, element) { if (!child.parentNode || child == element) return false; if (child.parentNode == element) return true; return Element.isParent(child.parentNode, element); -} +}; -Element.findChildren = function(element, only, recursive, tagName) { +Element.findChildren = function(element, only, recursive, tagName) { if(!element.hasChildNodes()) return null; tagName = tagName.toUpperCase(); if(only) only = [only].flatten(); @@ -965,8 +967,8 @@ Element.findChildren = function(element, only, recursive, tagName) { }); return (elements.length>0 ? elements.flatten() : []); -} +}; Element.offsetSize = function (element, type) { return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; -} +}; \ No newline at end of file diff --git a/public/javascripts/effects.js b/public/javascripts/effects.js index f030b5dbe..c81e6c7d5 100644 --- a/public/javascripts/effects.js +++ b/public/javascripts/effects.js @@ -1,48 +1,50 @@ -// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009 + +// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) // Contributors: // Justin Palmer (http://encytemedia.com/) // Mark Pilgrim (http://diveintomark.org/) // Martin Bialasinki -// +// // script.aculo.us is freely distributable under the terms of an MIT-style license. -// For details, see the script.aculo.us web site: http://script.aculo.us/ +// For details, see the script.aculo.us web site: http://script.aculo.us/ -// converts rgb() and #xxx to #xxxxxx format, -// returns self (or first argument) if not convertable -String.prototype.parseColor = function() { +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { var color = '#'; - if (this.slice(0,4) == 'rgb(') { - var cols = this.slice(4,this.length-1).split(','); - var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); - } else { - if (this.slice(0,1) == '#') { - if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); - if (this.length==7) color = this.toLowerCase(); - } - } - return (color.length==7 ? color : (arguments[0] || this)); + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); }; /*--------------------------------------------------------------------------*/ -Element.collectTextNodes = function(element) { +Element.collectTextNodes = function(element) { return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : + return (node.nodeType==3 ? node.nodeValue : (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); }).flatten().join(''); }; -Element.collectTextNodesIgnoreClass = function(element, className) { +Element.collectTextNodesIgnoreClass = function(element, className) { return $A($(element).childNodes).collect( function(node) { - return (node.nodeType==3 ? node.nodeValue : - ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? Element.collectTextNodesIgnoreClass(node, className) : '')); }).flatten().join(''); }; Element.setContentZoom = function(element, percent) { - element = $(element); - element.setStyle({fontSize: (percent/100) + 'em'}); + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); if (Prototype.Browser.WebKit) window.scrollBy(0,0); return element; }; @@ -70,28 +72,23 @@ var Effect = { Transitions: { linear: Prototype.K, sinoidal: function(pos) { - return (-Math.cos(pos*Math.PI)/2) + 0.5; + return (-Math.cos(pos*Math.PI)/2) + .5; }, reverse: function(pos) { return 1-pos; }, flicker: function(pos) { - var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; return pos > 1 ? 1 : pos; }, wobble: function(pos) { - return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; }, - pulse: function(pos, pulses) { - pulses = pulses || 5; - return ( - ((pos % (1/pulses)) * pulses).round() == 0 ? - ((pos * pulses * 2) - (pos * pulses * 2).floor()) : - 1 - ((pos * pulses * 2) - (pos * pulses * 2).floor()) - ); + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; }, - spring: function(pos) { - return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); }, none: function(pos) { return 0; @@ -112,14 +109,14 @@ var Effect = { tagifyText: function(element) { var tagifyStyle = 'position:relative'; if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; - + element = $(element); $A(element.childNodes).each( function(child) { if (child.nodeType==3) { child.nodeValue.toArray().each( function(character) { element.insertBefore( new Element('span', {style: tagifyStyle}).update( - character == ' ' ? String.fromCharCode(160) : character), + character == ' ' ? String.fromCharCode(160) : character), child); }); Element.remove(child); @@ -128,13 +125,13 @@ var Effect = { }, multiple: function(element, effect) { var elements; - if (((typeof element == 'object') || - Object.isFunction(element)) && + if (((typeof element == 'object') || + Object.isFunction(element)) && (element.length)) elements = element; else elements = $(element).childNodes; - + var options = Object.extend({ speed: 0.1, delay: 0.0 @@ -150,14 +147,13 @@ var Effect = { 'blind': ['BlindDown','BlindUp'], 'appear': ['Appear','Fade'] }, - toggle: function(element, effect) { + toggle: function(element, effect, options) { element = $(element); - effect = (effect || 'appear').toLowerCase(); - var options = Object.extend({ + effect = (effect || 'appear').toLowerCase(); + + return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({ queue: { position:'end', scope:(element.id || 'global'), limit: 1 } - }, arguments[2] || { }); - Effect[element.visible() ? - Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + }, options || {})); } }; @@ -168,20 +164,20 @@ Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; Effect.ScopedQueue = Class.create(Enumerable, { initialize: function() { this.effects = []; - this.interval = null; + this.interval = null; }, _each: function(iterator) { this.effects._each(iterator); }, add: function(effect) { var timestamp = new Date().getTime(); - - var position = Object.isString(effect.options.queue) ? + + var position = Object.isString(effect.options.queue) ? effect.options.queue : effect.options.queue.position; - + switch(position) { case 'front': - // move unstarted effects after this effect + // move unstarted effects after this effect this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { e.startOn += effect.finishOn; e.finishOn += effect.finishOn; @@ -195,13 +191,13 @@ Effect.ScopedQueue = Class.create(Enumerable, { timestamp = this.effects.pluck('finishOn').max() || timestamp; break; } - + effect.startOn += timestamp; effect.finishOn += timestamp; if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) this.effects.push(effect); - + if (!this.interval) this.interval = setInterval(this.loop.bind(this), 15); }, @@ -214,7 +210,7 @@ Effect.ScopedQueue = Class.create(Enumerable, { }, loop: function() { var timePos = new Date().getTime(); - for(var i=0, len=this.effects.length;i0) { @@ -430,9 +432,9 @@ Effect.Scale = Class.create(Effect.Base, { this.fontSizeType = fontSizeType; } }.bind(this)); - + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; - + this.dims = null; if (this.options.scaleMode=='box') this.dims = [this.element.offsetHeight, this.element.offsetWidth]; @@ -507,17 +509,16 @@ Effect.Highlight = Class.create(Effect.Base, { Effect.ScrollTo = function(element) { var options = arguments[1] || { }, - scrollOffsets = document.viewport.getScrollOffsets(), - elementOffsets = $(element).cumulativeOffset(), - max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); if (options.offset) elementOffsets[1] += options.offset; return new Effect.Tween(null, scrollOffsets.top, - elementOffsets[1] > max ? max : elementOffsets[1], + elementOffsets[1], options, - function(p){ scrollTo(scrollOffsets.left, p.round()) } + function(p){ scrollTo(scrollOffsets.left, p.round()); } ); }; @@ -529,9 +530,9 @@ Effect.Fade = function(element) { var options = Object.extend({ from: element.getOpacity() || 1.0, to: 0.0, - afterFinishInternal: function(effect) { + afterFinishInternal: function(effect) { if (effect.options.to!=0) return; - effect.element.hide().setStyle({opacity: oldOpacity}); + effect.element.hide().setStyle({opacity: oldOpacity}); } }, arguments[1] || { }); return new Effect.Opacity(element,options); @@ -547,15 +548,15 @@ Effect.Appear = function(element) { effect.element.forceRerendering(); }, beforeSetup: function(effect) { - effect.element.setOpacity(effect.options.from).show(); + effect.element.setOpacity(effect.options.from).show(); }}, arguments[1] || { }); return new Effect.Opacity(element,options); }; Effect.Puff = function(element) { element = $(element); - var oldStyle = { - opacity: element.getInlineOpacity(), + var oldStyle = { + opacity: element.getInlineOpacity(), position: element.getStyle('position'), top: element.style.top, left: element.style.left, @@ -563,12 +564,12 @@ Effect.Puff = function(element) { height: element.style.height }; return new Effect.Parallel( - [ new Effect.Scale(element, 200, - { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), - new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], - Object.extend({ duration: 1.0, + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, beforeSetupInternal: function(effect) { - Position.absolutize(effect.effects[0].element) + Position.absolutize(effect.effects[0].element); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().setStyle(oldStyle); } @@ -580,12 +581,12 @@ Effect.BlindUp = function(element) { element = $(element); element.makeClipping(); return new Effect.Scale(element, 0, - Object.extend({ scaleContent: false, - scaleX: false, + Object.extend({ scaleContent: false, + scaleX: false, restoreAfterFinish: true, afterFinishInternal: function(effect) { effect.element.hide().undoClipping(); - } + } }, arguments[1] || { }) ); }; @@ -593,15 +594,15 @@ Effect.BlindUp = function(element) { Effect.BlindDown = function(element) { element = $(element); var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, scaleX: false, scaleFrom: 0, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, afterSetup: function(effect) { - effect.element.makeClipping().setStyle({height: '0px'}).show(); - }, + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, afterFinishInternal: function(effect) { effect.element.undoClipping(); } @@ -616,16 +617,16 @@ Effect.SwitchOff = function(element) { from: 0, transition: Effect.Transitions.flicker, afterFinishInternal: function(effect) { - new Effect.Scale(effect.element, 1, { + new Effect.Scale(effect.element, 1, { duration: 0.3, scaleFromCenter: true, scaleX: false, scaleContent: false, restoreAfterFinish: true, - beforeSetup: function(effect) { + beforeSetup: function(effect) { effect.element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); } - }) + }); } }, arguments[1] || { })); }; @@ -637,16 +638,16 @@ Effect.DropOut = function(element) { left: element.getStyle('left'), opacity: element.getInlineOpacity() }; return new Effect.Parallel( - [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), new Effect.Opacity(element, { sync: true, to: 0.0 }) ], Object.extend( { duration: 0.5, beforeSetup: function(effect) { - effect.effects[0].element.makePositioned(); + effect.effects[0].element.makePositioned(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); - } + } }, arguments[1] || { })); }; @@ -674,7 +675,7 @@ Effect.Shake = function(element) { new Effect.Move(effect.element, { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { effect.element.undoPositioned().setStyle(oldStyle); - }}) }}) }}) }}) }}) }}); + }}); }}); }}); }}); }}); }}); }; Effect.SlideDown = function(element) { @@ -682,9 +683,9 @@ Effect.SlideDown = function(element) { // SlideDown need to have the content of the element wrapped in a container element with fixed height! var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); - return new Effect.Scale(element, 100, Object.extend({ - scaleContent: false, - scaleX: false, + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, scaleFrom: window.opera ? 0 : 1, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, restoreAfterFinish: true, @@ -692,11 +693,11 @@ Effect.SlideDown = function(element) { effect.element.makePositioned(); effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); - effect.element.makeClipping().setStyle({height: '0px'}).show(); + effect.element.makeClipping().setStyle({height: '0px'}).show(); }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: - (effect.dims[0] - effect.element.clientHeight) + 'px' }); + (effect.dims[0] - effect.element.clientHeight) + 'px' }); }, afterFinishInternal: function(effect) { effect.element.undoClipping().undoPositioned(); @@ -710,8 +711,8 @@ Effect.SlideUp = function(element) { var oldInnerBottom = element.down().getStyle('bottom'); var elementDimensions = element.getDimensions(); return new Effect.Scale(element, window.opera ? 0 : 1, - Object.extend({ scaleContent: false, - scaleX: false, + Object.extend({ scaleContent: false, + scaleX: false, scaleMode: 'box', scaleFrom: 100, scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, @@ -721,7 +722,7 @@ Effect.SlideUp = function(element) { effect.element.down().makePositioned(); if (window.opera) effect.element.setStyle({top: ''}); effect.element.makeClipping().show(); - }, + }, afterUpdateInternal: function(effect) { effect.element.down().setStyle({bottom: (effect.dims[0] - effect.element.clientHeight) + 'px' }); @@ -734,15 +735,15 @@ Effect.SlideUp = function(element) { ); }; -// Bug in opera makes the TD containing this element expand for a instance after finish +// Bug in opera makes the TD containing this element expand for a instance after finish Effect.Squish = function(element) { - return new Effect.Scale(element, window.opera ? 1 : 0, { + return new Effect.Scale(element, window.opera ? 1 : 0, { restoreAfterFinish: true, beforeSetup: function(effect) { - effect.element.makeClipping(); - }, + effect.element.makeClipping(); + }, afterFinishInternal: function(effect) { - effect.element.hide().undoClipping(); + effect.element.hide().undoClipping(); } }); }; @@ -762,13 +763,13 @@ Effect.Grow = function(element) { width: element.style.width, opacity: element.getInlineOpacity() }; - var dims = element.getDimensions(); + var dims = element.getDimensions(); var initialMoveX, initialMoveY; var moveX, moveY; - + switch (options.direction) { case 'top-left': - initialMoveX = initialMoveY = moveX = moveY = 0; + initialMoveX = initialMoveY = moveX = moveY = 0; break; case 'top-right': initialMoveX = dims.width; @@ -793,11 +794,11 @@ Effect.Grow = function(element) { moveY = -dims.height / 2; break; } - + return new Effect.Move(element, { x: initialMoveX, y: initialMoveY, - duration: 0.01, + duration: 0.01, beforeSetup: function(effect) { effect.element.hide().makeClipping().makePositioned(); }, @@ -806,17 +807,17 @@ Effect.Grow = function(element) { [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), new Effect.Scale(effect.element, 100, { - scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) ], Object.extend({ beforeSetup: function(effect) { - effect.effects[0].element.setStyle({height: '0px'}).show(); + effect.effects[0].element.setStyle({height: '0px'}).show(); }, afterFinishInternal: function(effect) { - effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); } }, options) - ) + ); } }); }; @@ -838,7 +839,7 @@ Effect.Shrink = function(element) { var dims = element.getDimensions(); var moveX, moveY; - + switch (options.direction) { case 'top-left': moveX = moveY = 0; @@ -855,19 +856,19 @@ Effect.Shrink = function(element) { moveX = dims.width; moveY = dims.height; break; - case 'center': + case 'center': moveX = dims.width / 2; moveY = dims.height / 2; break; } - + return new Effect.Parallel( [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) - ], Object.extend({ + ], Object.extend({ beforeStartInternal: function(effect) { - effect.effects[0].element.makePositioned().makeClipping(); + effect.effects[0].element.makePositioned().makeClipping(); }, afterFinishInternal: function(effect) { effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } @@ -877,12 +878,14 @@ Effect.Shrink = function(element) { Effect.Pulsate = function(element) { element = $(element); - var options = arguments[1] || { }; - var oldOpacity = element.getInlineOpacity(); - var transition = options.transition || Effect.Transitions.sinoidal; - var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos, options.pulses)) }; - reverser.bind(transition); - return new Effect.Opacity(element, + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + + return new Effect.Opacity(element, Object.extend(Object.extend({ duration: 2.0, from: 0, afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } }, options), {transition: reverser})); @@ -896,12 +899,12 @@ Effect.Fold = function(element) { width: element.style.width, height: element.style.height }; element.makeClipping(); - return new Effect.Scale(element, 5, Object.extend({ + return new Effect.Scale(element, 5, Object.extend({ scaleContent: false, scaleX: false, afterFinishInternal: function(effect) { - new Effect.Scale(element, 1, { - scaleContent: false, + new Effect.Scale(element, 1, { + scaleContent: false, scaleY: false, afterFinishInternal: function(effect) { effect.element.hide().undoClipping().setStyle(oldStyle); @@ -916,7 +919,7 @@ Effect.Morph = Class.create(Effect.Base, { var options = Object.extend({ style: { } }, arguments[1] || { }); - + if (!Object.isString(options.style)) this.style = $H(options.style); else { if (options.style.include(':')) @@ -934,18 +937,18 @@ Effect.Morph = Class.create(Effect.Base, { effect.transforms.each(function(transform) { effect.element.style[transform.style] = ''; }); - } + }; } } this.start(options); }, - + setup: function(){ function parseColor(color){ if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; color = color.parseColor(); return $R(0,2).map(function(i){ - return parseInt( color.slice(i*2+1,i*2+3), 16 ) + return parseInt( color.slice(i*2+1,i*2+3), 16 ); }); } this.transforms = this.style.map(function(pair){ @@ -965,9 +968,9 @@ Effect.Morph = Class.create(Effect.Base, { } var originalValue = this.element.getStyle(property); - return { - style: property.camelize(), - originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), targetValue: unit=='color' ? parseColor(value) : value, unit: unit }; @@ -978,13 +981,13 @@ Effect.Morph = Class.create(Effect.Base, { transform.unit != 'color' && (isNaN(transform.originalValue) || isNaN(transform.targetValue)) ) - ) + ); }); }, update: function(position) { var style = { }, transform, i = this.transforms.length; while(i--) - style[(transform = this.transforms[i]).style] = + style[(transform = this.transforms[i]).style] = transform.unit=='color' ? '#'+ (Math.round(transform.originalValue[0]+ (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + @@ -993,7 +996,7 @@ Effect.Morph = Class.create(Effect.Base, { (Math.round(transform.originalValue[2]+ (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : (transform.originalValue + - (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + (transform.unit === null ? '' : transform.unit); this.element.setStyle(style, true); } @@ -1030,7 +1033,7 @@ Effect.Transform = Class.create({ }); Element.CSS_PROPERTIES = $w( - 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + @@ -1039,7 +1042,7 @@ Element.CSS_PROPERTIES = $w( 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + 'right textIndent top width wordSpacing zIndex'); - + Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; String.__parseStyleElement = document.createElement('div'); @@ -1051,11 +1054,11 @@ String.prototype.parseStyle = function(){ String.__parseStyleElement.innerHTML = '
    '; style = String.__parseStyleElement.childNodes[0].style; } - + Element.CSS_PROPERTIES.each(function(property){ - if (style[property]) styleRules.set(property, style[property]); + if (style[property]) styleRules.set(property, style[property]); }); - + if (Prototype.Browser.IE && this.include('opacity')) styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); @@ -1074,14 +1077,14 @@ if (document.defaultView && document.defaultView.getComputedStyle) { Element.getStyles = function(element) { element = $(element); var css = element.currentStyle, styles; - styles = Element.CSS_PROPERTIES.inject({ }, function(hash, property) { - hash.set(property, css[property]); - return hash; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; }); - if (!styles.opacity) styles.set('opacity', element.getOpacity()); + if (!styles.opacity) styles.opacity = element.getOpacity(); return styles; }; -}; +} Effect.Methods = { morph: function(element, style) { @@ -1090,7 +1093,7 @@ Effect.Methods = { return element; }, visualEffect: function(element, effect, options) { - element = $(element) + element = $(element); var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); new Effect[klass](element, options); return element; @@ -1104,17 +1107,17 @@ Effect.Methods = { $w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ 'pulsate shake puff squish switchOff dropOut').each( - function(effect) { + function(effect) { Effect.Methods[effect] = function(element, options){ element = $(element); Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); return element; - } + }; } ); -$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( function(f) { Effect.Methods[f] = Element[f]; } ); -Element.addMethods(Effect.Methods); +Element.addMethods(Effect.Methods); \ No newline at end of file diff --git a/public/javascripts/prototype.js b/public/javascripts/prototype.js index 546f9fe44..06249a6ae 100644 --- a/public/javascripts/prototype.js +++ b/public/javascripts/prototype.js @@ -1,5 +1,5 @@ -/* Prototype JavaScript framework, version 1.6.0.1 - * (c) 2005-2007 Sam Stephenson +/* Prototype JavaScript framework, version 1.7_rc2 + * (c) 2005-2010 Sam Stephenson * * Prototype is freely distributable under the terms of an MIT-style license. * For details, see the Prototype web site: http://www.prototypejs.org/ @@ -7,29 +7,53 @@ *--------------------------------------------------------------------------*/ var Prototype = { - Version: '1.6.0.1', - Browser: { - IE: !!(window.attachEvent && !window.opera), - Opera: !!window.opera, - WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, - Gecko: navigator.userAgent.indexOf('Gecko') > -1 && navigator.userAgent.indexOf('KHTML') == -1, - MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) - }, + Version: '1.7_rc2', + + Browser: (function(){ + var ua = navigator.userAgent; + var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]'; + return { + IE: !!window.attachEvent && !isOpera, + Opera: isOpera, + WebKit: ua.indexOf('AppleWebKit/') > -1, + Gecko: ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1, + MobileSafari: /Apple.*Mobile/.test(ua) + } + })(), BrowserFeatures: { XPath: !!document.evaluate, - ElementExtensions: !!window.HTMLElement, - SpecificElementExtensions: - document.createElement('div').__proto__ && - document.createElement('div').__proto__ !== - document.createElement('form').__proto__ + + SelectorsAPI: !!document.querySelector, + + ElementExtensions: (function() { + var constructor = window.Element || window.HTMLElement; + return !!(constructor && constructor.prototype); + })(), + SpecificElementExtensions: (function() { + if (typeof window.HTMLDivElement !== 'undefined') + return true; + + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; + + if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { + isSupported = true; + } + + div = form = null; + + return isSupported; + })() }, ScriptFragment: ']*>([\\S\\s]*?)<\/script>', JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, emptyFunction: function() { }, + K: function(x) { return x } }; @@ -37,9 +61,38 @@ if (Prototype.Browser.MobileSafari) Prototype.BrowserFeatures.SpecificElementExtensions = false; +var Abstract = { }; + + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + /* Based on Alex Arnell's inheritance implementation. */ -var Class = { - create: function() { + +var Class = (function() { + + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + if (p === 'toString') return false; + } + return true; + })(); + + function subclass() {}; + function create() { var parent = null, properties = $A(arguments); if (Object.isFunction(properties[0])) parent = properties.shift(); @@ -53,235 +106,374 @@ var Class = { klass.subclasses = []; if (parent) { - var subclass = function() { }; subclass.prototype = parent.prototype; klass.prototype = new subclass; parent.subclasses.push(klass); } - for (var i = 0; i < properties.length; i++) + for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) klass.prototype.initialize = Prototype.emptyFunction; klass.prototype.constructor = klass; - return klass; } -}; -Class.Methods = { - addMethods: function(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); + function addMethods(source) { + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); - if (!Object.keys({ toString: true }).length) - properties.push("toString", "valueOf"); + if (IS_DONTENUM_BUGGY) { + if (source.toString != Object.prototype.toString) + properties.push("toString"); + if (source.valueOf != Object.prototype.valueOf) + properties.push("valueOf"); + } for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { - var method = value, value = Object.extend((function(m) { - return function() { return ancestor[m].apply(this, arguments) }; - })(property).wrap(method), { - valueOf: function() { return method }, - toString: function() { return method.toString() } - }); + value.argumentNames()[0] == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments); }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); } this.prototype[property] = value; } return this; } -}; -var Abstract = { }; + return { + create: create, + Methods: { + addMethods: addMethods + } + }; +})(); +(function() { -Object.extend = function(destination, source) { - for (var property in source) - destination[property] = source[property]; - return destination; -}; + var _toString = Object.prototype.toString, + NULL_TYPE = 'Null', + UNDEFINED_TYPE = 'Undefined', + BOOLEAN_TYPE = 'Boolean', + NUMBER_TYPE = 'Number', + STRING_TYPE = 'String', + OBJECT_TYPE = 'Object', + BOOLEAN_CLASS = '[object Boolean]', + NUMBER_CLASS = '[object Number]', + STRING_CLASS = '[object String]', + ARRAY_CLASS = '[object Array]', + NATIVE_JSON_STRINGIFY_SUPPORT = window.JSON && + typeof JSON.stringify === 'function' && + JSON.stringify(0) === '0' && + typeof JSON.stringify(Prototype.K) === 'undefined'; + + function Type(o) { + switch(o) { + case null: return NULL_TYPE; + case (void 0): return UNDEFINED_TYPE; + } + var type = typeof o; + switch(type) { + case 'boolean': return BOOLEAN_TYPE; + case 'number': return NUMBER_TYPE; + case 'string': return STRING_TYPE; + } + return OBJECT_TYPE; + } -Object.extend(Object, { - inspect: function(object) { + function extend(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; + } + + function inspect(object) { try { - if (Object.isUndefined(object)) return 'undefined'; + if (isUndefined(object)) return 'undefined'; if (object === null) return 'null'; - return object.inspect ? object.inspect() : object.toString(); + return object.inspect ? object.inspect() : String(object); } catch (e) { if (e instanceof RangeError) return '...'; throw e; } - }, + } - toJSON: function(object) { - var type = typeof object; - switch (type) { - case 'undefined': - case 'function': - case 'unknown': return; - case 'boolean': return object.toString(); + function toJSON(value) { + return Str('', { '': value }, []); + } + + function Str(key, holder, stack) { + var value = holder[key], + type = typeof value; + + if (Type(value) === OBJECT_TYPE && typeof value.toJSON === 'function') { + value = value.toJSON(key); } - if (object === null) return 'null'; - if (object.toJSON) return object.toJSON(); - if (Object.isElement(object)) return; + var _class = _toString.call(value); - var results = []; - for (var property in object) { - var value = Object.toJSON(object[property]); - if (!Object.isUndefined(value)) - results.push(property.toJSON() + ': ' + value); + switch (_class) { + case NUMBER_CLASS: + case BOOLEAN_CLASS: + case STRING_CLASS: + value = value.valueOf(); } - return '{' + results.join(', ') + '}'; - }, + switch (value) { + case null: return 'null'; + case true: return 'true'; + case false: return 'false'; + } + + type = typeof value; + switch (type) { + case 'string': + return value.inspect(true); + case 'number': + return isFinite(value) ? String(value) : 'null'; + case 'object': + + for (var i = 0, length = stack.length; i < length; i++) { + if (stack[i] === value) { throw new TypeError(); } + } + stack.push(value); + + var partial = []; + if (_class === ARRAY_CLASS) { + for (var i = 0, length = value.length; i < length; i++) { + var str = Str(i, value, stack); + partial.push(typeof str === 'undefined' ? 'null' : str); + } + partial = '[' + partial.join(',') + ']'; + } else { + var keys = Object.keys(value); + for (var i = 0, length = keys.length; i < length; i++) { + var key = keys[i], str = Str(key, value, stack); + if (typeof str !== "undefined") { + partial.push(key.inspect(true)+ ':' + str); + } + } + partial = '{' + partial.join(',') + '}'; + } + stack.pop(); + return partial; + } + } + + function stringify(object) { + return JSON.stringify(object); + } - toQueryString: function(object) { + function toQueryString(object) { return $H(object).toQueryString(); - }, + } - toHTML: function(object) { + function toHTML(object) { return object && object.toHTML ? object.toHTML() : String.interpret(object); - }, + } - keys: function(object) { - var keys = []; - for (var property in object) - keys.push(property); - return keys; - }, + function keys(object) { + if (Type(object) !== OBJECT_TYPE) { throw new TypeError(); } + var results = []; + for (var property in object) { + if (object.hasOwnProperty(property)) { + results.push(property); + } + } + return results; + } - values: function(object) { - var values = []; + function values(object) { + var results = []; for (var property in object) - values.push(object[property]); - return values; - }, + results.push(object[property]); + return results; + } - clone: function(object) { - return Object.extend({ }, object); - }, + function clone(object) { + return extend({ }, object); + } - isElement: function(object) { - return object && object.nodeType == 1; - }, + function isElement(object) { + return !!(object && object.nodeType == 1); + } - isArray: function(object) { - return object && object.constructor === Array; - }, + function isArray(object) { + return _toString.call(object) === ARRAY_CLASS; + } + + var hasNativeIsArray = (typeof Array.isArray == 'function') + && Array.isArray([]) && !Array.isArray({}); + + if (hasNativeIsArray) { + isArray = Array.isArray; + } - isHash: function(object) { + function isHash(object) { return object instanceof Hash; - }, + } - isFunction: function(object) { - return typeof object == "function"; - }, + function isFunction(object) { + return typeof object === "function"; + } - isString: function(object) { - return typeof object == "string"; - }, + function isString(object) { + return _toString.call(object) === STRING_CLASS; + } - isNumber: function(object) { - return typeof object == "number"; - }, + function isNumber(object) { + return _toString.call(object) === NUMBER_CLASS; + } - isUndefined: function(object) { - return typeof object == "undefined"; + function isUndefined(object) { + return typeof object === "undefined"; + } + + extend(Object, { + extend: extend, + inspect: inspect, + toJSON: NATIVE_JSON_STRINGIFY_SUPPORT ? stringify : toJSON, + toQueryString: toQueryString, + toHTML: toHTML, + keys: Object.keys || keys, + values: values, + clone: clone, + isElement: isElement, + isArray: isArray, + isHash: isHash, + isFunction: isFunction, + isString: isString, + isNumber: isNumber, + isUndefined: isUndefined + }); +})(); +Object.extend(Function.prototype, (function() { + var slice = Array.prototype.slice; + + function update(array, args) { + var arrayLength = array.length, length = args.length; + while (length--) array[arrayLength + length] = args[length]; + return array; } -}); -Object.extend(Function.prototype, { - argumentNames: function() { - var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + function merge(array, args) { + array = slice.call(array, 0); + return update(array, args); + } + + function argumentNames() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1] + .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '') + .replace(/\s+/g, '').split(','); return names.length == 1 && !names[0] ? [] : names; - }, + } - bind: function() { + function bind(context) { if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; - var __method = this, args = $A(arguments), object = args.shift(); + var __method = this, args = slice.call(arguments, 1); return function() { - return __method.apply(object, args.concat($A(arguments))); + var a = merge(args, arguments); + return __method.apply(context, a); } - }, + } - bindAsEventListener: function() { - var __method = this, args = $A(arguments), object = args.shift(); + function bindAsEventListener(context) { + var __method = this, args = slice.call(arguments, 1); return function(event) { - return __method.apply(object, [event || window.event].concat(args)); + var a = update([event || window.event], args); + return __method.apply(context, a); } - }, + } - curry: function() { + function curry() { if (!arguments.length) return this; - var __method = this, args = $A(arguments); + var __method = this, args = slice.call(arguments, 0); return function() { - return __method.apply(this, args.concat($A(arguments))); + var a = merge(args, arguments); + return __method.apply(this, a); } - }, + } - delay: function() { - var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + function delay(timeout) { + var __method = this, args = slice.call(arguments, 1); + timeout = timeout * 1000; return window.setTimeout(function() { return __method.apply(__method, args); }, timeout); - }, + } - wrap: function(wrapper) { + function defer() { + var args = update([0.01], arguments); + return this.delay.apply(this, args); + } + + function wrap(wrapper) { var __method = this; return function() { - return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + var a = update([__method.bind(this)], arguments); + return wrapper.apply(this, a); } - }, + } - methodize: function() { + function methodize() { if (this._methodized) return this._methodized; var __method = this; return this._methodized = function() { - return __method.apply(null, [this].concat($A(arguments))); + var a = update([this], arguments); + return __method.apply(null, a); }; } -}); -Function.prototype.defer = Function.prototype.delay.curry(0.01); + return { + argumentNames: argumentNames, + bind: bind, + bindAsEventListener: bindAsEventListener, + curry: curry, + delay: delay, + defer: defer, + wrap: wrap, + methodize: methodize + } +})()); -Date.prototype.toJSON = function() { - return '"' + this.getUTCFullYear() + '-' + - (this.getUTCMonth() + 1).toPaddedString(2) + '-' + - this.getUTCDate().toPaddedString(2) + 'T' + - this.getUTCHours().toPaddedString(2) + ':' + - this.getUTCMinutes().toPaddedString(2) + ':' + - this.getUTCSeconds().toPaddedString(2) + 'Z"'; -}; -var Try = { - these: function() { - var returnValue; - for (var i = 0, length = arguments.length; i < length; i++) { - var lambda = arguments[i]; - try { - returnValue = lambda(); - break; - } catch (e) { } - } +(function(proto) { - return returnValue; + + function toISOString() { + return this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z'; } -}; + + + function toJSON() { + return this.toISOString(); + } + + if (!proto.toISOString) proto.toISOString = toISOString; + if (!proto.toJSON) proto.toJSON = toJSON; + +})(Date.prototype); + RegExp.prototype.match = RegExp.prototype.test; RegExp.escape = function(str) { return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); }; - -/*--------------------------------------------------------------------------*/ - var PeriodicalExecuter = Class.create({ initialize: function(callback, frequency) { this.callback = callback; @@ -310,8 +502,10 @@ var PeriodicalExecuter = Class.create({ try { this.currentlyExecuting = true; this.execute(); - } finally { this.currentlyExecuting = false; + } catch(e) { + this.currentlyExecuting = false; + throw e; } } } @@ -330,10 +524,28 @@ Object.extend(String, { } }); -Object.extend(String.prototype, { - gsub: function(pattern, replacement) { +Object.extend(String.prototype, (function() { + var NATIVE_JSON_PARSE_SUPPORT = window.JSON && + typeof JSON.parse === 'function' && + JSON.parse('{"test": true}').test; + + function prepareReplacement(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; + } + + function gsub(pattern, replacement) { var result = '', source = this, match; - replacement = arguments.callee.prepareReplacement(replacement); + replacement = prepareReplacement(replacement); + + if (Object.isString(pattern)) + pattern = RegExp.escape(pattern); + + if (!(pattern.length || pattern.source)) { + replacement = replacement(''); + return replacement + source.split('').join(replacement) + replacement; + } while (source.length > 0) { if (match = source.match(pattern)) { @@ -345,76 +557,72 @@ Object.extend(String.prototype, { } } return result; - }, + } - sub: function(pattern, replacement, count) { - replacement = this.gsub.prepareReplacement(replacement); + function sub(pattern, replacement, count) { + replacement = prepareReplacement(replacement); count = Object.isUndefined(count) ? 1 : count; return this.gsub(pattern, function(match) { if (--count < 0) return match[0]; return replacement(match); }); - }, + } - scan: function(pattern, iterator) { + function scan(pattern, iterator) { this.gsub(pattern, iterator); return String(this); - }, + } - truncate: function(length, truncation) { + function truncate(length, truncation) { length = length || 30; truncation = Object.isUndefined(truncation) ? '...' : truncation; return this.length > length ? this.slice(0, length - truncation.length) + truncation : String(this); - }, + } - strip: function() { + function strip() { return this.replace(/^\s+/, '').replace(/\s+$/, ''); - }, + } - stripTags: function() { - return this.replace(/<\/?[^>]+>/gi, ''); - }, + function stripTags() { + return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, ''); + } - stripScripts: function() { + function stripScripts() { return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); - }, + } - extractScripts: function() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + function extractScripts() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); - }, + } - evalScripts: function() { + function evalScripts() { return this.extractScripts().map(function(script) { return eval(script) }); - }, + } - escapeHTML: function() { - var self = arguments.callee; - self.text.data = this; - return self.div.innerHTML; - }, + function escapeHTML() { + return this.replace(/&/g,'&').replace(//g,'>'); + } + + function unescapeHTML() { + return this.stripTags().replace(/</g,'<').replace(/>/g,'>').replace(/&/g,'&'); + } - unescapeHTML: function() { - var div = new Element('div'); - div.innerHTML = this.stripTags(); - return div.childNodes[0] ? (div.childNodes.length > 1 ? - $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : - div.childNodes[0].nodeValue) : ''; - }, - toQueryParams: function(separator) { + function toQueryParams(separator) { var match = this.strip().match(/([^?#]*)(#.*)?$/); if (!match) return { }; return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); if (key in hash) { @@ -425,128 +633,144 @@ Object.extend(String.prototype, { } return hash; }); - }, + } - toArray: function() { + function toArray() { return this.split(''); - }, + } - succ: function() { + function succ() { return this.slice(0, this.length - 1) + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); - }, + } - times: function(count) { + function times(count) { return count < 1 ? '' : new Array(count + 1).join(this); - }, - - camelize: function() { - var parts = this.split('-'), len = parts.length; - if (len == 1) return parts[0]; - - var camelized = this.charAt(0) == '-' - ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) - : parts[0]; - - for (var i = 1; i < len; i++) - camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + } - return camelized; - }, + function camelize() { + return this.replace(/-+(.)?/g, function(match, chr) { + return chr ? chr.toUpperCase() : ''; + }); + } - capitalize: function() { + function capitalize() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); - }, + } - underscore: function() { - return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); - }, + function underscore() { + return this.replace(/::/g, '/') + .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2') + .replace(/([a-z\d])([A-Z])/g, '$1_$2') + .replace(/-/g, '_') + .toLowerCase(); + } - dasherize: function() { - return this.gsub(/_/,'-'); - }, + function dasherize() { + return this.replace(/_/g, '-'); + } - inspect: function(useDoubleQuotes) { - var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { - var character = String.specialChar[match[0]]; - return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + function inspect(useDoubleQuotes) { + var escapedString = this.replace(/[\x00-\x1f\\]/g, function(character) { + if (character in String.specialChar) { + return String.specialChar[character]; + } + return '\\u00' + character.charCodeAt().toPaddedString(2, 16); }); if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; return "'" + escapedString.replace(/'/g, '\\\'') + "'"; - }, - - toJSON: function() { - return this.inspect(true); - }, + } - unfilterJSON: function(filter) { - return this.sub(filter || Prototype.JSONFilter, '#{1}'); - }, + function unfilterJSON(filter) { + return this.replace(filter || Prototype.JSONFilter, '$1'); + } - isJSON: function() { + function isJSON() { var str = this; if (str.blank()) return false; - str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); - return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); - }, + str = str.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'); + str = str.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'); + str = str.replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + return (/^[\],:{}\s]*$/).test(str); + } - evalJSON: function(sanitize) { - var json = this.unfilterJSON(); + function evalJSON(sanitize) { + var json = this.unfilterJSON(), + cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g; + if (cx.test(json)) { + json = json.replace(cx, function (a) { + return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4); + }); + } try { if (!sanitize || json.isJSON()) return eval('(' + json + ')'); } catch (e) { } throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); - }, + } + + function parseJSON() { + var json = this.unfilterJSON(); + return JSON.parse(json); + } - include: function(pattern) { + function include(pattern) { return this.indexOf(pattern) > -1; - }, + } - startsWith: function(pattern) { - return this.indexOf(pattern) === 0; - }, + function startsWith(pattern) { + return this.lastIndexOf(pattern, 0) === 0; + } - endsWith: function(pattern) { + function endsWith(pattern) { var d = this.length - pattern.length; - return d >= 0 && this.lastIndexOf(pattern) === d; - }, + return d >= 0 && this.indexOf(pattern, d) === d; + } - empty: function() { + function empty() { return this == ''; - }, + } - blank: function() { + function blank() { return /^\s*$/.test(this); - }, - - interpolate: function(object, pattern) { - return new Template(this, pattern).evaluate(object); } -}); -if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { - escapeHTML: function() { - return this.replace(/&/g,'&').replace(//g,'>'); - }, - unescapeHTML: function() { - return this.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + function interpolate(object, pattern) { + return new Template(this, pattern).evaluate(object); } -}); -String.prototype.gsub.prepareReplacement = function(replacement) { - if (Object.isFunction(replacement)) return replacement; - var template = new Template(replacement); - return function(match) { return template.evaluate(match) }; -}; - -String.prototype.parseQuery = String.prototype.toQueryParams; - -Object.extend(String.prototype.escapeHTML, { - div: document.createElement('div'), - text: document.createTextNode('') -}); - -with (String.prototype.escapeHTML) div.appendChild(text); + return { + gsub: gsub, + sub: sub, + scan: scan, + truncate: truncate, + strip: String.prototype.trim || strip, + stripTags: stripTags, + stripScripts: stripScripts, + extractScripts: extractScripts, + evalScripts: evalScripts, + escapeHTML: escapeHTML, + unescapeHTML: unescapeHTML, + toQueryParams: toQueryParams, + parseQuery: toQueryParams, + toArray: toArray, + succ: succ, + times: times, + camelize: camelize, + capitalize: capitalize, + underscore: underscore, + dasherize: dasherize, + inspect: inspect, + unfilterJSON: unfilterJSON, + isJSON: isJSON, + evalJSON: NATIVE_JSON_PARSE_SUPPORT ? parseJSON : evalJSON, + include: include, + startsWith: startsWith, + endsWith: endsWith, + empty: empty, + blank: blank, + interpolate: interpolate + }; +})()); var Template = Class.create({ initialize: function(template, pattern) { @@ -555,22 +779,23 @@ var Template = Class.create({ }, evaluate: function(object) { - if (Object.isFunction(object.toTemplateReplacements)) + if (object && Object.isFunction(object.toTemplateReplacements)) object = object.toTemplateReplacements(); return this.template.gsub(this.pattern, function(match) { - if (object == null) return ''; + if (object == null) return (match[1] + ''); var before = match[1] || ''; if (before == '\\') return match[2]; - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); if (match == null) return before; while (match != null) { - var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1]; ctx = ctx[comp]; if (null == ctx || '' == match[3]) break; expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); @@ -578,101 +803,98 @@ var Template = Class.create({ } return before + String.interpret(ctx); - }.bind(this)); + }); } }); Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; var $break = { }; -var Enumerable = { - each: function(iterator, context) { +var Enumerable = (function() { + function each(iterator, context) { var index = 0; - iterator = iterator.bind(context); try { this._each(function(value) { - iterator(value, index++); + iterator.call(context, value, index++); }); } catch (e) { if (e != $break) throw e; } return this; - }, + } - eachSlice: function(number, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + function eachSlice(number, iterator, context) { var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; while ((index += number) < array.length) slices.push(array.slice(index, index+number)); return slices.collect(iterator, context); - }, + } - all: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + function all(iterator, context) { + iterator = iterator || Prototype.K; var result = true; this.each(function(value, index) { - result = result && !!iterator(value, index); + result = result && !!iterator.call(context, value, index); if (!result) throw $break; }); return result; - }, + } - any: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + function any(iterator, context) { + iterator = iterator || Prototype.K; var result = false; this.each(function(value, index) { - if (result = !!iterator(value, index)) + if (result = !!iterator.call(context, value, index)) throw $break; }); return result; - }, + } - collect: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + function collect(iterator, context) { + iterator = iterator || Prototype.K; var results = []; this.each(function(value, index) { - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; - }, + } - detect: function(iterator, context) { - iterator = iterator.bind(context); + function detect(iterator, context) { var result; this.each(function(value, index) { - if (iterator(value, index)) { + if (iterator.call(context, value, index)) { result = value; throw $break; } }); return result; - }, + } - findAll: function(iterator, context) { - iterator = iterator.bind(context); + function findAll(iterator, context) { var results = []; this.each(function(value, index) { - if (iterator(value, index)) + if (iterator.call(context, value, index)) results.push(value); }); return results; - }, + } - grep: function(filter, iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + function grep(filter, iterator, context) { + iterator = iterator || Prototype.K; var results = []; if (Object.isString(filter)) - filter = new RegExp(filter); + filter = new RegExp(RegExp.escape(filter)); this.each(function(value, index) { if (filter.match(value)) - results.push(iterator(value, index)); + results.push(iterator.call(context, value, index)); }); return results; - }, + } - include: function(object) { + function include(object) { if (Object.isFunction(this.indexOf)) if (this.indexOf(object) != -1) return true; @@ -684,96 +906,96 @@ var Enumerable = { } }); return found; - }, + } - inGroupsOf: function(number, fillWith) { + function inGroupsOf(number, fillWith) { fillWith = Object.isUndefined(fillWith) ? null : fillWith; return this.eachSlice(number, function(slice) { while(slice.length < number) slice.push(fillWith); return slice; }); - }, + } - inject: function(memo, iterator, context) { - iterator = iterator.bind(context); + function inject(memo, iterator, context) { this.each(function(value, index) { - memo = iterator(memo, value, index); + memo = iterator.call(context, memo, value, index); }); return memo; - }, + } - invoke: function(method) { + function invoke(method) { var args = $A(arguments).slice(1); return this.map(function(value) { return value[method].apply(value, args); }); - }, + } - max: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + function max(iterator, context) { + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value >= result) result = value; }); return result; - }, + } - min: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + function min(iterator, context) { + iterator = iterator || Prototype.K; var result; this.each(function(value, index) { - value = iterator(value, index); + value = iterator.call(context, value, index); if (result == null || value < result) result = value; }); return result; - }, + } - partition: function(iterator, context) { - iterator = iterator ? iterator.bind(context) : Prototype.K; + function partition(iterator, context) { + iterator = iterator || Prototype.K; var trues = [], falses = []; this.each(function(value, index) { - (iterator(value, index) ? + (iterator.call(context, value, index) ? trues : falses).push(value); }); return [trues, falses]; - }, + } - pluck: function(property) { + function pluck(property) { var results = []; this.each(function(value) { results.push(value[property]); }); return results; - }, + } - reject: function(iterator, context) { - iterator = iterator.bind(context); + function reject(iterator, context) { var results = []; this.each(function(value, index) { - if (!iterator(value, index)) + if (!iterator.call(context, value, index)) results.push(value); }); return results; - }, + } - sortBy: function(iterator, context) { - iterator = iterator.bind(context); + function sortBy(iterator, context) { return this.map(function(value, index) { - return {value: value, criteria: iterator(value, index)}; + return { + value: value, + criteria: iterator.call(context, value, index) + }; }).sort(function(left, right) { var a = left.criteria, b = right.criteria; return a < b ? -1 : a > b ? 1 : 0; }).pluck('value'); - }, + } - toArray: function() { + function toArray() { return this.map(); - }, + } - zip: function() { + function zip() { var iterator = Prototype.K, args = $A(arguments); if (Object.isFunction(args.last())) iterator = args.pop(); @@ -782,330 +1004,409 @@ var Enumerable = { return this.map(function(value, index) { return iterator(collections.pluck(index)); }); - }, + } - size: function() { + function size() { return this.toArray().length; - }, + } - inspect: function() { + function inspect() { return '#'; } -}; -Object.extend(Enumerable, { - map: Enumerable.collect, - find: Enumerable.detect, - select: Enumerable.findAll, - filter: Enumerable.findAll, - member: Enumerable.include, - entries: Enumerable.toArray, - every: Enumerable.all, - some: Enumerable.any -}); + + + + + + + + + return { + each: each, + eachSlice: eachSlice, + all: all, + every: all, + any: any, + some: any, + collect: collect, + map: collect, + detect: detect, + findAll: findAll, + select: findAll, + filter: findAll, + grep: grep, + include: include, + member: include, + inGroupsOf: inGroupsOf, + inject: inject, + invoke: invoke, + max: max, + min: min, + partition: partition, + pluck: pluck, + reject: reject, + sortBy: sortBy, + toArray: toArray, + entries: toArray, + zip: zip, + size: size, + inspect: inspect, + find: detect + }; +})(); + function $A(iterable) { if (!iterable) return []; - if (iterable.toArray) return iterable.toArray(); - var length = iterable.length, results = new Array(length); + if ('toArray' in Object(iterable)) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); while (length--) results[length] = iterable[length]; return results; } -if (Prototype.Browser.WebKit) { - function $A(iterable) { - if (!iterable) return []; - if (!(Object.isFunction(iterable) && iterable == '[object NodeList]') && - iterable.toArray) return iterable.toArray(); - var length = iterable.length, results = new Array(length); - while (length--) results[length] = iterable[length]; - return results; - } + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; } Array.from = $A; -Object.extend(Array.prototype, Enumerable); -if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; +(function() { + var arrayProto = Array.prototype, + slice = arrayProto.slice, + _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available -Object.extend(Array.prototype, { - _each: function(iterator) { + function each(iterator) { for (var i = 0, length = this.length; i < length; i++) iterator(this[i]); - }, + } + if (!_each) _each = each; - clear: function() { + function clear() { this.length = 0; return this; - }, + } - first: function() { + function first() { return this[0]; - }, + } - last: function() { + function last() { return this[this.length - 1]; - }, + } - compact: function() { + function compact() { return this.select(function(value) { return value != null; }); - }, + } - flatten: function() { + function flatten() { return this.inject([], function(array, value) { - return array.concat(Object.isArray(value) ? - value.flatten() : [value]); + if (Object.isArray(value)) + return array.concat(value.flatten()); + array.push(value); + return array; }); - }, + } - without: function() { - var values = $A(arguments); + function without() { + var values = slice.call(arguments, 0); return this.select(function(value) { return !values.include(value); }); - }, - - reverse: function(inline) { - return (inline !== false ? this : this.toArray())._reverse(); - }, + } - reduce: function() { - return this.length > 1 ? this : this[0]; - }, + function reverse(inline) { + return (inline === false ? this.toArray() : this)._reverse(); + } - uniq: function(sorted) { + function uniq(sorted) { return this.inject([], function(array, value, index) { if (0 == index || (sorted ? array.last() != value : !array.include(value))) array.push(value); return array; }); - }, + } - intersect: function(array) { + function intersect(array) { return this.uniq().findAll(function(item) { return array.detect(function(value) { return item === value }); }); - }, + } - clone: function() { - return [].concat(this); - }, - size: function() { + function clone() { + return slice.call(this, 0); + } + + function size() { return this.length; - }, + } - inspect: function() { + function inspect() { return '[' + this.map(Object.inspect).join(', ') + ']'; - }, - - toJSON: function() { - var results = []; - this.each(function(object) { - var value = Object.toJSON(object); - if (!Object.isUndefined(value)) results.push(value); - }); - return '[' + results.join(', ') + ']'; } -}); - -// use native browser JS 1.6 implementation if available -if (Object.isFunction(Array.prototype.forEach)) - Array.prototype._each = Array.prototype.forEach; - -if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { - i || (i = 0); - var length = this.length; - if (i < 0) i = length + i; - for (; i < length; i++) - if (this[i] === item) return i; - return -1; -}; -if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { - i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; - var n = this.slice(0, i).reverse().indexOf(item); - return (n < 0) ? n : i - n - 1; -}; - -Array.prototype.toArray = Array.prototype.clone; + function indexOf(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; + } -function $w(string) { - if (!Object.isString(string)) return []; - string = string.strip(); - return string ? string.split(/\s+/) : []; -} + function lastIndexOf(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; + } -if (Prototype.Browser.Opera){ - Array.prototype.concat = function() { - var array = []; - for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + function concat() { + var array = slice.call(this, 0), item; for (var i = 0, length = arguments.length; i < length; i++) { - if (Object.isArray(arguments[i])) { - for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) - array.push(arguments[i][j]); + item = arguments[i]; + if (Object.isArray(item) && !('callee' in item)) { + for (var j = 0, arrayLength = item.length; j < arrayLength; j++) + array.push(item[j]); } else { - array.push(arguments[i]); + array.push(item); } } return array; - }; -} -Object.extend(Number.prototype, { - toColorPart: function() { - return this.toPaddedString(2, 16); - }, - - succ: function() { - return this + 1; - }, + } - times: function(iterator) { - $R(0, this, true).each(iterator); - return this; - }, + Object.extend(arrayProto, Enumerable); + + if (!arrayProto._reverse) + arrayProto._reverse = arrayProto.reverse; + + Object.extend(arrayProto, { + _each: _each, + clear: clear, + first: first, + last: last, + compact: compact, + flatten: flatten, + without: without, + reverse: reverse, + uniq: uniq, + intersect: intersect, + clone: clone, + toArray: clone, + size: size, + inspect: inspect + }); - toPaddedString: function(length, radix) { - var string = this.toString(radix || 10); - return '0'.times(length - string.length) + string; - }, + var CONCAT_ARGUMENTS_BUGGY = (function() { + return [].concat(arguments)[0][0] !== 1; + })(1,2) - toJSON: function() { - return isFinite(this) ? this.toString() : 'null'; - } -}); + if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat; -$w('abs round ceil floor').each(function(method){ - Number.prototype[method] = Math[method].methodize(); -}); + if (!arrayProto.indexOf) arrayProto.indexOf = indexOf; + if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf; +})(); function $H(object) { return new Hash(object); }; var Hash = Class.create(Enumerable, (function() { - - function toQueryPair(key, value) { - if (Object.isUndefined(value)) return key; - return key + '=' + encodeURIComponent(String.interpret(value)); + function initialize(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); } - return { - initialize: function(object) { - this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); - }, - _each: function(iterator) { - for (var key in this._object) { - var value = this._object[key], pair = [key, value]; - pair.key = key; - pair.value = value; - iterator(pair); - } - }, + function _each(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + } - set: function(key, value) { - return this._object[key] = value; - }, + function set(key, value) { + return this._object[key] = value; + } - get: function(key) { + function get(key) { + if (this._object[key] !== Object.prototype[key]) return this._object[key]; - }, + } - unset: function(key) { - var value = this._object[key]; - delete this._object[key]; - return value; - }, + function unset(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + } - toObject: function() { - return Object.clone(this._object); - }, + function toObject() { + return Object.clone(this._object); + } - keys: function() { - return this.pluck('key'); - }, - values: function() { - return this.pluck('value'); - }, - index: function(value) { - var match = this.detect(function(pair) { - return pair.value === value; - }); - return match && match.key; - }, + function keys() { + return this.pluck('key'); + } - merge: function(object) { - return this.clone().update(object); - }, + function values() { + return this.pluck('value'); + } - update: function(object) { - return new Hash(object).inject(this, function(result, pair) { - result.set(pair.key, pair.value); - return result; - }); - }, + function index(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + } - toQueryString: function() { - return this.map(function(pair) { - var key = encodeURIComponent(pair.key), values = pair.value; + function merge(object) { + return this.clone().update(object); + } - if (values && typeof values == 'object') { - if (Object.isArray(values)) - return values.map(toQueryPair.curry(key)).join('&'); - } - return toQueryPair(key, values); - }).join('&'); - }, + function update(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + } - inspect: function() { - return '#'; - }, + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } - toJSON: function() { - return Object.toJSON(this.toObject()); - }, + function toQueryString() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; - clone: function() { - return new Hash(this); - } + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + } + + function inspect() { + return '#'; } + + function clone() { + return new Hash(this); + } + + return { + initialize: initialize, + _each: _each, + set: set, + get: get, + unset: unset, + toObject: toObject, + toTemplateReplacements: toObject, + keys: keys, + values: values, + index: index, + merge: merge, + update: update, + toQueryString: toQueryString, + inspect: inspect, + toJSON: toObject, + clone: clone + }; })()); -Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; Hash.from = $H; -var ObjectRange = Class.create(Enumerable, { - initialize: function(start, end, exclusive) { +Object.extend(Number.prototype, (function() { + function toColorPart() { + return this.toPaddedString(2, 16); + } + + function succ() { + return this + 1; + } + + function times(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + } + + function toPaddedString(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + } + + function abs() { + return Math.abs(this); + } + + function round() { + return Math.round(this); + } + + function ceil() { + return Math.ceil(this); + } + + function floor() { + return Math.floor(this); + } + + return { + toColorPart: toColorPart, + succ: succ, + times: times, + toPaddedString: toPaddedString, + abs: abs, + round: round, + ceil: ceil, + floor: floor + }; +})()); + +function $R(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var ObjectRange = Class.create(Enumerable, (function() { + function initialize(start, end, exclusive) { this.start = start; this.end = end; this.exclusive = exclusive; - }, + } - _each: function(iterator) { + function _each(iterator) { var value = this.start; while (this.include(value)) { iterator(value); value = value.succ(); } - }, + } - include: function(value) { + function include(value) { if (value < this.start) return false; if (this.exclusive) return value < this.end; return value <= this.end; } -}); -var $R = function(start, end, exclusive) { - return new ObjectRange(start, end, exclusive); -}; + return { + initialize: initialize, + _each: _each, + include: include + }; +})()); + + var Ajax = { getTransport: function() { @@ -1152,7 +1453,6 @@ Ajax.Responders.register({ onCreate: function() { Ajax.activeRequestCount++ }, onComplete: function() { Ajax.activeRequestCount-- } }); - Ajax.Base = Class.create({ initialize: function(options) { this.options = { @@ -1174,7 +1474,6 @@ Ajax.Base = Class.create({ this.options.parameters = this.options.parameters.toObject(); } }); - Ajax.Request = Class.create(Ajax.Base, { _complete: false, @@ -1190,7 +1489,6 @@ Ajax.Request = Class.create(Ajax.Base, { var params = Object.clone(this.options.parameters); if (!['get', 'post'].include(this.method)) { - // simulate other verbs over post params['_method'] = this.method; this.method = 'post'; } @@ -1198,7 +1496,6 @@ Ajax.Request = Class.create(Ajax.Base, { this.parameters = params; if (params = Object.toQueryString(params)) { - // when GET, append parameters to URL if (this.method == 'get') this.url += (this.url.include('?') ? '&' : '?') + params; else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) @@ -1257,7 +1554,6 @@ Ajax.Request = Class.create(Ajax.Base, { headers['Connection'] = 'close'; } - // user-defined headers if (typeof this.options.requestHeaders == 'object') { var extras = this.options.requestHeaders; @@ -1298,7 +1594,7 @@ Ajax.Request = Class.create(Ajax.Base, { var contentType = response.getHeader('Content-type'); if (this.options.evalJS == 'force' - || (this.options.evalJS && contentType + || (this.options.evalJS && this.isSameOrigin() && contentType && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) this.evalResponse(); } @@ -1311,15 +1607,23 @@ Ajax.Request = Class.create(Ajax.Base, { } if (state == 'Complete') { - // avoid memory leak in MSIE: clean up this.transport.onreadystatechange = Prototype.emptyFunction; } }, + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + getHeader: function(name) { try { - return this.transport.getResponseHeader(name); - } catch (e) { return null } + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null; } }, evalResponse: function() { @@ -1339,20 +1643,27 @@ Ajax.Request = Class.create(Ajax.Base, { Ajax.Request.Events = ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + + + + + + + Ajax.Response = Class.create({ initialize: function(request){ this.request = request; var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } - if(readyState == 4) { + if (readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); @@ -1360,6 +1671,7 @@ Ajax.Response = Class.create({ }, status: 0, + statusText: '', getStatus: Ajax.Request.prototype.getStatus, @@ -1391,7 +1703,8 @@ Ajax.Response = Class.create({ if (!json) return null; json = decodeURIComponent(escape(json)); try { - return json.evalJSON(this.request.options.sanitizeJSON); + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -1404,7 +1717,8 @@ Ajax.Response = Class.create({ this.responseText.blank()) return null; try { - return this.responseText.evalJSON(options.sanitizeJSON); + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); } catch (e) { this.request.dispatchException(e); } @@ -1487,6 +1801,8 @@ Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { this.updater = new Ajax.Updater(this.container, this.url, this.options); } }); + + function $(element) { if (arguments.length > 1) { for (var i = 0, elements = [], length = arguments.length; i < length; i++) @@ -1511,10 +1827,9 @@ if (Prototype.BrowserFeatures.XPath) { /*--------------------------------------------------------------------------*/ -if (!window.Node) var Node = { }; +if (!Node) var Node = { }; if (!Node.ELEMENT_NODE) { - // DOM level 2 ECMAScript Language Binding Object.extend(Node, { ELEMENT_NODE: 1, ATTRIBUTE_NODE: 2, @@ -1531,13 +1846,27 @@ if (!Node.ELEMENT_NODE) { }); } -(function() { - var element = this.Element; - this.Element = function(tagName, attributes) { + + +(function(global) { + + var HAS_EXTENDED_CREATE_ELEMENT_SYNTAX = (function(){ + try { + var el = document.createElement(''); + return el.tagName.toLowerCase() === 'input' && el.name === 'x'; + } + catch(err) { + return false; + } + })(); + + var element = global.Element; + + global.Element = function(tagName, attributes) { attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; - if (Prototype.Browser.IE && attributes.name) { + if (HAS_EXTENDED_CREATE_ELEMENT_SYNTAX && attributes.name) { tagName = '<' + tagName + ' name="' + attributes.name + '">'; delete attributes.name; return Element.writeAttribute(document.createElement(tagName), attributes); @@ -1545,11 +1874,24 @@ if (!Node.ELEMENT_NODE) { if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; - Object.extend(this.Element, element || { }); -}).call(window); + Object.extend(global.Element, element || { }); + if (element) global.Element.prototype = element.prototype; + +})(this); + +Element.idCounter = 1; Element.cache = { }; +function purgeElement(element) { + var uid = element._prototypeUID; + if (uid) { + Element.stopObserving(element); + element._prototypeUID = void 0; + delete Element.Storage[uid]; + } +} + Element.Methods = { visible: function(element) { return $(element).style.display != 'none'; @@ -1562,12 +1904,14 @@ Element.Methods = { }, hide: function(element) { - $(element).style.display = 'none'; + element = $(element); + element.style.display = 'none'; return element; }, show: function(element) { - $(element).style.display = ''; + element = $(element); + element.style.display = ''; return element; }, @@ -1577,15 +1921,93 @@ Element.Methods = { return element; }, - update: function(element, content) { - element = $(element); - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - content = Object.toHTML(content); - element.innerHTML = content.stripScripts(); - content.evalScripts.bind(content).defer(); - return element; - }, + update: (function(){ + + var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){ + var el = document.createElement("select"), + isBuggy = true; + el.innerHTML = ""; + if (el.options && el.options[0]) { + isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION"; + } + el = null; + return isBuggy; + })(); + + var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){ + try { + var el = document.createElement("table"); + if (el && el.tBodies) { + el.innerHTML = "
  • "; + var isBuggy = typeof el.tBodies[0] == "undefined"; + el = null; + return isBuggy; + } + } catch (e) { + return true; + } + })(); + + var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () { + var s = document.createElement("script"), + isBuggy = false; + try { + s.appendChild(document.createTextNode("")); + isBuggy = !s.firstChild || + s.firstChild && s.firstChild.nodeType !== 3; + } catch (e) { + isBuggy = true; + } + s = null; + return isBuggy; + })(); + + function update(element, content) { + element = $(element); + + var descendants = element.getElementsByTagName('*'), + i = descendants.length; + while (i--) purgeElement(descendants[i]); + + if (content && content.toElement) + content = content.toElement(); + + if (Object.isElement(content)) + return element.update().insert(content); + + content = Object.toHTML(content); + + var tagName = element.tagName.toUpperCase(); + + if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) { + element.text = content; + return element; + } + + if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) { + if (tagName in Element._insertionTranslations.tags) { + while (element.firstChild) { + element.removeChild(element.firstChild); + } + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { + element.appendChild(node) + }); + } + else { + element.innerHTML = content.stripScripts(); + } + } + else { + element.innerHTML = content.stripScripts(); + } + + content.evalScripts.bind(content).defer(); + return element; + } + + return update; + })(), replace: function(element, content) { element = $(element); @@ -1608,24 +2030,28 @@ Element.Methods = { Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) insertions = {bottom:insertions}; - var content, t, range; + var content, insert, tagName, childNodes; - for (position in insertions) { + for (var position in insertions) { content = insertions[position]; position = position.toLowerCase(); - t = Element._insertionTranslations[position]; + insert = Element._insertionTranslations[position]; if (content && content.toElement) content = content.toElement(); if (Object.isElement(content)) { - t.insert(element, content); + insert(element, content); continue; } content = Object.toHTML(content); - range = element.ownerDocument.createRange(); - t.initializeRange(element, range); - t.insert(element, range.createContextualFragment(content.stripScripts())); + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); content.evalScripts.bind(content).defer(); } @@ -1649,28 +2075,35 @@ Element.Methods = { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; }, - recursivelyCollect: function(element, property) { + recursivelyCollect: function(element, property, maximumLength) { element = $(element); + maximumLength = maximumLength || -1; var elements = []; - while (element = element[property]) + + while (element = element[property]) { if (element.nodeType == 1) elements.push(Element.extend(element)); + if (elements.length == maximumLength) + break; + } + return elements; }, ancestors: function(element) { - return $(element).recursivelyCollect('parentNode'); + return Element.recursivelyCollect(element, 'parentNode'); }, descendants: function(element) { - return $(element).getElementsBySelector("*"); + return Element.select(element, "*"); }, firstDescendant: function(element) { @@ -1680,79 +2113,96 @@ Element.Methods = { }, immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; }, - previousSiblings: function(element) { - return $(element).recursivelyCollect('previousSibling'); + previousSiblings: function(element, maximumLength) { + return Element.recursivelyCollect(element, 'previousSibling'); }, nextSiblings: function(element) { - return $(element).recursivelyCollect('nextSibling'); + return Element.recursivelyCollect(element, 'nextSibling'); }, siblings: function(element) { element = $(element); - return element.previousSiblings().reverse().concat(element.nextSiblings()); + return Element.previousSiblings(element).reverse() + .concat(Element.nextSiblings(element)); }, match: function(element, selector) { + element = $(element); if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); + return Prototype.Selector.match(element, selector); + return selector.match(element); }, up: function(element, expression, index) { element = $(element); if (arguments.length == 1) return $(element.parentNode); - var ancestors = element.ancestors(); - return expression ? Selector.findElement(ancestors, expression, index) : - ancestors[index || 0]; + var ancestors = Element.ancestors(element); + return Object.isNumber(expression) ? ancestors[expression] : + Prototype.Selector.find(ancestors, expression, index); }, down: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return element.firstDescendant(); - var descendants = element.descendants(); - return expression ? Selector.findElement(descendants, expression, index) : - descendants[index || 0]; + if (arguments.length == 1) return Element.firstDescendant(element); + return Object.isNumber(expression) ? Element.descendants(element)[expression] : + Element.select(element, expression)[index || 0]; }, previous: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = element.previousSiblings(); - return expression ? Selector.findElement(previousSiblings, expression, index) : - previousSiblings[index || 0]; + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } }, next: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = element.nextSiblings(); - return expression ? Selector.findElement(nextSiblings, expression, index) : - nextSiblings[index || 0]; + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Prototype.Selector.find(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } }, - select: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element, args); + + select: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); }, - adjacent: function() { - var args = $A(arguments), element = $(args.shift()); - return Selector.findChildElements(element.parentNode, args).without(element); + adjacent: function(element) { + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); }, identify: function(element) { element = $(element); - var id = element.readAttribute('id'), self = arguments.callee; + var id = Element.readAttribute(element, 'id'); if (id) return id; - do { id = 'anonymous_element_' + self.counter++ } while ($(id)); - element.writeAttribute('id', id); + do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id)); + Element.writeAttribute(element, 'id', id); return id; }, @@ -1791,11 +2241,11 @@ Element.Methods = { }, getHeight: function(element) { - return $(element).getDimensions().height; + return Element.getDimensions(element).height; }, getWidth: function(element) { - return $(element).getDimensions().width; + return Element.getDimensions(element).width; }, classNames: function(element) { @@ -1811,7 +2261,7 @@ Element.Methods = { addClassName: function(element, className) { if (!(element = $(element))) return; - if (!element.hasClassName(className)) + if (!Element.hasClassName(element, className)) element.className += (element.className ? ' ' : '') + className; return element; }, @@ -1825,11 +2275,10 @@ Element.Methods = { toggleClassName: function(element, className) { if (!(element = $(element))) return; - return element[element.hasClassName(className) ? - 'removeClassName' : 'addClassName'](className); + return Element[Element.hasClassName(element, className) ? + 'removeClassName' : 'addClassName'](element, className); }, - // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); var node = element.firstChild; @@ -1848,29 +2297,22 @@ Element.Methods = { descendantOf: function(element, ancestor) { element = $(element), ancestor = $(ancestor); - var originalAncestor = ancestor; if (element.compareDocumentPosition) return (element.compareDocumentPosition(ancestor) & 8) === 8; - if (element.sourceIndex && !Prototype.Browser.Opera) { - var e = element.sourceIndex, a = ancestor.sourceIndex, - nextAncestor = ancestor.nextSibling; - if (!nextAncestor) { - do { ancestor = ancestor.parentNode; } - while (!(nextAncestor = ancestor.nextSibling) && ancestor.parentNode); - } - if (nextAncestor) return (e > a && e < nextAncestor.sourceIndex); - } + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; while (element = element.parentNode) - if (element == originalAncestor) return true; + if (element == ancestor) return true; + return false; }, scrollTo: function(element) { element = $(element); - var pos = element.cumulativeOffset(); + var pos = Element.cumulativeOffset(element); window.scrollTo(pos[0], pos[1]); return element; }, @@ -1879,7 +2321,7 @@ Element.Methods = { element = $(element); style = style == 'float' ? 'cssFloat' : style.camelize(); var value = element.style[style]; - if (!value) { + if (!value || value == 'auto') { var css = document.defaultView.getComputedStyle(element, null); value = css ? css[style] : null; } @@ -1916,38 +2358,13 @@ Element.Methods = { return element; }, - getDimensions: function(element) { - element = $(element); - var display = $(element).getStyle('display'); - if (display != 'none' && display != null) // Safari bug - return {width: element.offsetWidth, height: element.offsetHeight}; - - // All *Width and *Height properties give 0 on elements with display none, - // so enable the element temporarily - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; - els.visibility = 'hidden'; - els.position = 'absolute'; - els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; - els.display = originalDisplay; - els.position = originalPosition; - els.visibility = originalVisibility; - return {width: originalWidth, height: originalHeight}; - }, - makePositioned: function(element) { element = $(element); var pos = Element.getStyle(element, 'position'); if (pos == 'static' || !pos) { element._madePositioned = true; element.style.position = 'relative'; - // Opera returns the offset relative to the positioning context, when an - // element is position relative but top and left have not been defined - if (window.opera) { + if (Prototype.Browser.Opera) { element.style.top = 0; element.style.left = 0; } @@ -1987,11 +2404,13 @@ Element.Methods = { cumulativeOffset: function(element) { var valueT = 0, valueL = 0; - do { - valueT += element.offsetTop || 0; - valueL += element.offsetLeft || 0; - element = element.offsetParent; - } while (element); + if (element.parentNode) { + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + } return Element._returnOffset(valueL, valueT); }, @@ -2002,9 +2421,9 @@ Element.Methods = { valueL += element.offsetLeft || 0; element = element.offsetParent; if (element) { - if (element.tagName == 'BODY') break; + if (element.tagName.toUpperCase() == 'BODY') break; var p = Element.getStyle(element, 'position'); - if (p == 'relative' || p == 'absolute') break; + if (p !== 'static') break; } } while (element); return Element._returnOffset(valueL, valueT); @@ -2012,14 +2431,13 @@ Element.Methods = { absolutize: function(element) { element = $(element); - if (element.getStyle('position') == 'absolute') return; - // Position.prepare(); // To be done manually by Scripty when it needs it. + if (Element.getStyle(element, 'position') == 'absolute') return element; - var offsets = element.positionedOffset(); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; + var offsets = Element.positionedOffset(element), + top = offsets[1], + left = offsets[0], + width = element.clientWidth, + height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); @@ -2036,12 +2454,11 @@ Element.Methods = { relativize: function(element) { element = $(element); - if (element.getStyle('position') == 'relative') return; - // Position.prepare(); // To be done manually by Scripty when it needs it. + if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), + left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; @@ -2072,14 +2489,14 @@ Element.Methods = { }, viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; + var valueT = 0, + valueL = 0, + element = forElement; - var element = forElement; do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; - // Safari fix if (element.offsetParent == document.body && Element.getStyle(element, 'position') == 'absolute') break; @@ -2087,7 +2504,7 @@ Element.Methods = { element = forElement; do { - if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { valueT -= element.scrollTop || 0; valueL -= element.scrollLeft || 0; } @@ -2106,28 +2523,21 @@ Element.Methods = { offsetLeft: 0 }, arguments[2] || { }); - // find page position of source source = $(source); - var p = source.viewportOffset(); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; - // find coordinate system to use element = $(element); - var delta = [0, 0]; - var parent = null; - // delta [0,0] will do fine with position: fixed elements, - // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { - parent = element.getOffsetParent(); - delta = parent.viewportOffset(); + parent = Element.getOffsetParent(element); + delta = Element.viewportOffset(parent); } - // correct by body offsets (fixes Safari) if (parent == document.body) { delta[0] -= document.body.offsetLeft; delta[1] -= document.body.offsetTop; } - // set position if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; if (options.setWidth) element.style.width = source.offsetWidth + 'px'; @@ -2136,10 +2546,9 @@ Element.Methods = { } }; -Element.Methods.identify.counter = 1; - Object.extend(Element.Methods, { getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants }); @@ -2153,46 +2562,6 @@ Element._attributeTranslations = { } }; - -if (!document.createRange || Prototype.Browser.Opera) { - Element.Methods.insert = function(element, insertions) { - element = $(element); - - if (Object.isString(insertions) || Object.isNumber(insertions) || - Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) - insertions = { bottom: insertions }; - - var t = Element._insertionTranslations, content, position, pos, tagName; - - for (position in insertions) { - content = insertions[position]; - position = position.toLowerCase(); - pos = t[position]; - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) { - pos.insert(element, content); - continue; - } - - content = Object.toHTML(content); - tagName = ((position == 'before' || position == 'after') - ? element.parentNode : element).tagName.toUpperCase(); - - if (t.tags[tagName]) { - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); - if (position == 'top' || position == 'after') fragments.reverse(); - fragments.each(pos.insert.curry(element)); - } - else element.insertAdjacentHTML(pos.adjacency, content.stripScripts()); - - content.evalScripts.bind(content).defer(); - } - - return element; - }; -} - if (Prototype.Browser.Opera) { Element.Methods.getStyle = Element.Methods.getStyle.wrap( function(proceed, element, style) { @@ -2200,11 +2569,8 @@ if (Prototype.Browser.Opera) { case 'left': case 'top': case 'right': case 'bottom': if (proceed(element, 'position') === 'static') return null; case 'height': case 'width': - // returns '0px' for hidden elements; we want it to return null if (!Element.visible(element)) return null; - // returns the border-box dimensions rather than the content-box - // dimensions, so we subtract padding and borders from the value var dim = parseInt(proceed(element, style), 10); if (dim !== element['offset' + style.capitalize()]) @@ -2237,12 +2603,29 @@ if (Prototype.Browser.Opera) { } else if (Prototype.Browser.IE) { - $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + if (!element.parentNode) return $(document.body); + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { Element.Methods[method] = Element.Methods[method].wrap( function(proceed, element) { element = $(element); + if (!element.parentNode) return Element._returnOffset(0, 0); var position = element.getStyle('position'); - if (position != 'static') return proceed(element); + if (position !== 'static') return proceed(element); + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); element.setStyle({ position: 'relative' }); var value = proceed(element); element.setStyle({ position: position }); @@ -2292,39 +2675,96 @@ else if (Prototype.Browser.IE) { return element; }; - Element._attributeTranslations = { - read: { - names: { - 'class': 'className', - 'for': 'htmlFor' - }, - values: { - _getAttr: function(element, attribute) { - return element.getAttribute(attribute, 2); - }, - _getAttrNode: function(element, attribute) { - var node = element.getAttributeNode(attribute); - return node ? node.value : ""; - }, - _getEv: function(element, attribute) { - attribute = element.getAttribute(attribute); - return attribute ? attribute.toString().slice(23, -2) : null; - }, - _flag: function(element, attribute) { - return $(element).hasAttribute(attribute) ? attribute : null; - }, - style: function(element) { - return element.style.cssText.toLowerCase(); + Element._attributeTranslations = (function(){ + + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); + + el.setAttribute(classProp, 'x'); + + if (el.className !== 'x') { + el.setAttribute('class', 'x'); + if (el.className === 'x') { + classProp = 'class'; + } + } + el = null; + + el = document.createElement('label'); + el.setAttribute(forProp, 'x'); + if (el.htmlFor !== 'x') { + el.setAttribute('htmlFor', 'x'); + if (el.htmlFor === 'x') { + forProp = 'htmlFor'; + } + } + el = null; + + return { + read: { + names: { + 'class': classProp, + 'className': classProp, + 'for': forProp, + 'htmlFor': forProp }, - title: function(element) { - return element.title; + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute); + }, + _getAttr2: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: (function(){ + + var el = document.createElement('div'), f; + el.onclick = Prototype.emptyFunction; + var value = el.getAttribute('onclick'); + + if (String(value).indexOf('{') > -1) { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + attribute = attribute.toString(); + attribute = attribute.split('{')[1]; + attribute = attribute.split('}')[0]; + return attribute.strip(); + }; + } + else if (value === '') { + f = function(element, attribute) { + attribute = element.getAttribute(attribute); + if (!attribute) return null; + return attribute.strip(); + }; + } + el = null; + return f; + })(), + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } } } } - }; + })(); Element._attributeTranslations.write = { - names: Object.clone(Element._attributeTranslations.read.names), + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), values: { checked: function(element, value) { element.checked = !!value; @@ -2339,15 +2779,15 @@ else if (Prototype.Browser.IE) { Element._attributeTranslations.has = {}; $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + - 'encType maxLength readOnly longDesc').each(function(attr) { + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; Element._attributeTranslations.has[attr.toLowerCase()] = attr; }); (function(v) { Object.extend(v, { - href: v._getAttr, - src: v._getAttr, + href: v._getAttr2, + src: v._getAttr2, type: v._getAttr, action: v._getAttrNode, disabled: v._flag, @@ -2374,6 +2814,26 @@ else if (Prototype.Browser.IE) { onchange: v._getEv }); })(Element._attributeTranslations.read.values); + + if (Prototype.BrowserFeatures.ElementExtensions) { + (function() { + function _descendants(element) { + var nodes = element.getElementsByTagName('*'), results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName !== "!") // Filter out comment nodes. + results.push(node); + return results; + } + + Element.Methods.down = function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? _descendants(element)[expression] : + Element.select(element, expression)[index || 0]; + } + })(); + } + } else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { @@ -2392,7 +2852,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName == 'IMG' && element.width) { + if (element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); @@ -2403,9 +2863,6 @@ else if (Prototype.Browser.WebKit) { return element; }; - // Safari returns margins on body which is incorrect if the child is absolutely - // positioned. For performance reasons, redefine Element#cumulativeOffset for - // KHTML/WebKit only. Element.Methods.cumulativeOffset = function(element) { var valueT = 0, valueL = 0; do { @@ -2421,30 +2878,7 @@ else if (Prototype.Browser.WebKit) { }; } -if (Prototype.Browser.IE || Prototype.Browser.Opera) { - // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements - Element.Methods.update = function(element, content) { - element = $(element); - - if (content && content.toElement) content = content.toElement(); - if (Object.isElement(content)) return element.update().insert(content); - - content = Object.toHTML(content); - var tagName = element.tagName.toUpperCase(); - - if (tagName in Element._insertionTranslations.tags) { - $A(element.childNodes).each(function(node) { element.removeChild(node) }); - Element._getContentFromAnonymousElement(tagName, content.stripScripts()) - .each(function(node) { element.appendChild(node) }); - } - else element.innerHTML = content.stripScripts(); - - content.evalScripts.bind(content).defer(); - return element; - }; -} - -if (document.createElement('div').outerHTML) { +if ('outerHTML' in document.documentElement) { Element.Methods.replace = function(element, content) { element = $(element); @@ -2458,8 +2892,8 @@ if (document.createElement('div').outerHTML) { var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); @@ -2481,46 +2915,32 @@ Element._returnOffset = function(l, t) { }; Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; - div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } return $A(div.childNodes); }; Element._insertionTranslations = { - before: { - adjacency: 'beforeBegin', - insert: function(element, node) { - element.parentNode.insertBefore(node, element); - }, - initializeRange: function(element, range) { - range.setStartBefore(element); - } + before: function(element, node) { + element.parentNode.insertBefore(node, element); }, - top: { - adjacency: 'afterBegin', - insert: function(element, node) { - element.insertBefore(node, element.firstChild); - }, - initializeRange: function(element, range) { - range.selectNodeContents(element); - range.collapse(true); - } + top: function(element, node) { + element.insertBefore(node, element.firstChild); }, - bottom: { - adjacency: 'beforeEnd', - insert: function(element, node) { - element.appendChild(node); - } + bottom: function(element, node) { + element.appendChild(node); }, - after: { - adjacency: 'afterEnd', - insert: function(element, node) { - element.parentNode.insertBefore(node, element.nextSibling); - }, - initializeRange: function(element, range) { - range.setStartAfter(element); - } + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); }, tags: { TABLE: ['
    Project:<%= link_to truncate(h(deployment.stage.project.name), :length => 21), project_path(deployment.stage.project), :title => h(deployment.stage.project.name) %><%= link_to truncate(deployment.stage.project.name, :length => 21), project_path(deployment.stage.project), :title => deployment.stage.project.name %> User:<%= link_to truncate(h(deployment.user.login), :length => 10) , user_path(deployment.user), :title => h(deployment.user.login) %><%= link_to truncate(deployment.user.login, :length => 10) , user_path(deployment.user), :title => deployment.user.login %> Started:<%=h deployment.created_at.to_s(:log) %><%= deployment.created_at.to_s(:log) %> <%= image_tag('peritor_theme/pixel_grey.gif', :width => '1', :height => '50') %> - + <%= image_tag("peritor_theme/status_#{deployment.status}_small.gif", :width => '53', :height => '40', :border => '0') %>
    Stage:<%= link_to truncate(h(deployment.stage.name), :length => 19), project_stage_path(deployment.stage.project, deployment.stage), :title => h(deployment.stage.name) %><%= link_to truncate(h(deployment.stage.name), :length => 19), project_stage_path(deployment.stage.project, deployment.stage), :title => deployment.stage.name %> Rev:<%= truncate(h(deployment.revision), :length => 10) rescue '' %><%= truncate(h(deployment.revision), :length => 10) rescue '' %> Duration:<%=h distance_of_time_in_words(deployment.created_at, deployment.completed_at, true) rescue "" %><%= distance_of_time_in_words(deployment.created_at, deployment.completed_at, true) rescue "" %>
    Comment:<%= truncate(h(deployment.description), :length => 30) %><%= truncate(h(deployment.description), :length => 30) %> Task:<%=h truncate(deployment.task, :length => 20) %><%= truncate(deployment.task, :length => 20) %> <%= link_to 'Show', project_stage_deployment_path(deployment.stage.project, deployment.stage, deployment) %> <%= image_tag('peritor_theme/status_' + @deployment.status + '_small.gif', :title => @deployment.status, :width => 20, :heigth => 15, :style => 'vertical-align:middle;') %> - Status: <%=h @deployment.status %> + Status: <%= @deployment.status %> <% if @deployment.completed? %>     - <%= link_to "Repeat", new_project_stage_deployment_path(current_project, current_stage) + '?repeat=' + h(@deployment.id), :class => 'arrow_link' %> + <%= link_to "Repeat", new_project_stage_deployment_path(current_project, current_stage) + "?repeat=#{@deployment.id}", :class => 'arrow_link' %> <% end %> <% if @deployment.cancelling_possible? %>     @@ -97,7 +97,7 @@ +

    + Auth Source
    + <%= select 'user', 'auth_source_id', AuthSource.all.collect {|p| [ p.name, p.id ] }, { :include_blank => true }, :style =>'width:330px;', + :onchange => "handle_auth_source_select_change(this)" %> + <%= link_to 'Edit & change auth sources', auth_sources_path %> +

    -<% unless WebistranoConfig[:authentication_method] == :cas -%> -

    - Password
    - <%= password_field 'user', 'password', :style =>'width:330px;' %> -

    +
    + <% unless WebistranoConfig[:authentication_method] == :cas -%> +

    + Password
    + <%= password_field 'user', 'password', :style =>'width:330px;' %> +

    -

    - Password confirmation
    - <%= password_field 'user', 'password_confirmation', :style =>'width:330px;' %> -

    -<% end -%> +

    + Password confirmation
    + <%= password_field 'user', 'password_confirmation', :style =>'width:330px;' %> +

    + + + <% end -%> +
    +<%- end -%>

    Timezone
    @@ -28,7 +54,26 @@ <% if logged_in? && current_user.admin? -%>

    - Admin?
    - <%= check_box 'user', 'admin', :class => 'noframe' %> + <%= check_box 'user', 'admin', :class => 'noframe' %> Admin? +

    + +

    + <%= check_box 'user', 'manage_hosts', :class => 'noframe' %> Can manage hosts? +

    + +

    + <%= check_box 'user', 'manage_recipes', :class => 'noframe' %> Can manage recipes? +

    + +

    + <%= check_box 'user', 'manage_projects', :class => 'noframe' %> Can manage projects?

    + +

    Project access

    + <%- @projects.each do |project| -%> +

    + <%= check_box_tag "user[project_ids][]", project.id, @user.projects.include?(project), :class => 'noframe' %> <%= project.name %> +

    + <%- end -%> + <% end -%> diff --git a/app/views/users/deployments.html.erb b/app/views/users/deployments.html.erb index 42d699ba0..910da3a8b 100644 --- a/app/views/users/deployments.html.erb +++ b/app/views/users/deployments.html.erb @@ -7,15 +7,13 @@ <%= link_to 'Back to user', user_path(@user), :class => 'arrow_link' %> <% content_for(:breadcrumb) do %> - <% breadcrumb_box do %> <%= link_to "Back to user", user_path(@user), :style => "float:right", :class => "arrow_link" %> <%= link_to "Users", users_path %> > User <%= link_to @user.login, user_path(@user) %> > Deployments of user <%= link_to @user.login, user_path(@user) %> - <% end %> <% end %> <% content_for(:page_title) do %> <% @page_title = "Deployments of user #{h(@user.login)}" %> -

    Deployments of user <%= link_to h(@user.login), user_path(@user) %>

    +

    Deployments of user <%= link_to @user.login, user_path(@user) %>

    <% end %> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb index bcc48da15..dca0b555d 100644 --- a/app/views/users/edit.html.erb +++ b/app/views/users/edit.html.erb @@ -1,4 +1,4 @@ -<% form_for(:user, :url => user_path(@user), :html => { :method => :put }) do |f| %> +<%= form_for(:user, :url => user_path(@user), :html => { :method => :put }) do |f| %> <%= render(:partial => 'form')%> @@ -10,12 +10,10 @@ <% end %> <% content_for(:breadcrumb) do %> - <% breadcrumb_box do %> <%= link_to "Back to user", user_path(@user), :style => "float:right", :class => "arrow_link" %> <%= link_to "Users", users_path %> > User <%= link_to @user.login, user_path(@user) %> > Edit user <%= link_to @user.login, user_path(@user) %> - <% end %> <% end %> <% content_for(:page_title) do %> diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb index 33f0b0722..e3ef663d8 100644 --- a/app/views/users/index.html.erb +++ b/app/views/users/index.html.erb @@ -1,5 +1,6 @@
    - <%=h pluralize(User.count, 'User')%> listed + <%= pluralize(User.count, 'User')%> listed + <% if @users.empty? %>
    @@ -16,15 +17,15 @@
    <%= link_to h(user.login), user_path(user) %><%=h user.email %><%=h user.admin? ? 'Admin' : '' %><%= link_to user.login, user_path(user) %><%= user.email %><%= user.admin? ? 'Admin' : '' %> <% if user.disabled? %> disabled <% end %> <%=h user.created_at.to_s(:date_with_day) %><%= user.created_at.to_s(:date_with_day) %><%= link_to 'Edit', edit_user_path(user) %>
    test
    ', '
    ', 1], @@ -2532,19 +2952,19 @@ Element._insertionTranslations = { }; (function() { - this.bottom.initializeRange = this.top.initializeRange; - Object.extend(this.tags, { - THEAD: this.tags.TBODY, - TFOOT: this.tags.TBODY, - TH: this.tags.TD + var tags = Element._insertionTranslations.tags; + Object.extend(tags, { + THEAD: tags.TBODY, + TFOOT: tags.TBODY, + TH: tags.TD }); -}).call(Element._insertionTranslations); +})(); Element.Methods.Simulated = { hasAttribute: function(element, attribute) { attribute = Element._attributeTranslations.has[attribute] || attribute; var node = $(element).getAttributeNode(attribute); - return node && node.specified; + return !!(node && node.specified); } }; @@ -2552,41 +2972,81 @@ Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); -if (!Prototype.BrowserFeatures.ElementExtensions && - document.createElement('div').__proto__) { - window.HTMLElement = { }; - window.HTMLElement.prototype = document.createElement('div').__proto__; - Prototype.BrowserFeatures.ElementExtensions = true; -} +(function(div) { + + if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = div['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; + } + + div = null; + +})(document.createElement('div')); Element.extend = (function() { - if (Prototype.BrowserFeatures.SpecificElementExtensions) + + function checkDeficiency(tagName) { + if (typeof window.Element != 'undefined') { + var proto = window.Element.prototype; + if (proto) { + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); + proto[id] = 'x'; + var isBuggy = (el[id] !== 'x'); + delete proto[id]; + el = null; + return isBuggy; + } + } + return false; + } + + function extendElementWith(element, methods) { + for (var property in methods) { + var value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + } + + var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object'); + + if (Prototype.BrowserFeatures.SpecificElementExtensions) { + if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) { + return function(element) { + if (element && typeof element._extendedByPrototype == 'undefined') { + var t = element.tagName; + if (t && (/^(?:object|applet|embed)$/i.test(t))) { + extendElementWith(element, Element.Methods); + extendElementWith(element, Element.Methods.Simulated); + extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]); + } + } + return element; + } + } return Prototype.K; + } var Methods = { }, ByTag = Element.Methods.ByTag; var extend = Object.extend(function(element) { - if (!element || element._extendedByPrototype || + if (!element || typeof element._extendedByPrototype != 'undefined' || element.nodeType != 1 || element == window) return element; var methods = Object.clone(Methods), - tagName = element.tagName, property, value; + tagName = element.tagName.toUpperCase(); - // extend methods for specific tags if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); - for (property in methods) { - value = methods[property]; - if (Object.isFunction(value) && !(property in element)) - element[property] = value.methodize(); - } + extendElementWith(element, methods); element._extendedByPrototype = Prototype.emptyFunction; return element; }, { refresh: function() { - // extend methods for all tags (Safari doesn't need this) if (!Prototype.BrowserFeatures.ElementExtensions) { Object.extend(Methods, Element.Methods); Object.extend(Methods, Element.Methods.Simulated); @@ -2598,10 +3058,14 @@ Element.extend = (function() { return extend; })(); -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} Element.addMethods = function(methods) { var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; @@ -2665,14 +3129,19 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - window[klass] = { }; - window[klass].prototype = document.createElement(tagName).__proto__; - return window[klass]; + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + + element = null; + return proto; } + var elementPrototype = window.HTMLElement ? HTMLElement.prototype : + Element.prototype; + if (F.ElementExtensions) { - copy(Element.Methods, HTMLElement.prototype); - copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Element.Methods, elementPrototype); + copy(Element.Methods.Simulated, elementPrototype, true); } if (F.SpecificElementExtensions) { @@ -2690,697 +3159,1803 @@ Element.addMethods = function(methods) { Element.cache = { }; }; -document.viewport = { - getDimensions: function() { - var dimensions = { }; - var B = Prototype.Browser; - $w('width height').each(function(d) { - var D = d.capitalize(); - dimensions[d] = (B.WebKit && !document.evaluate) ? self['inner' + D] : - (B.Opera) ? document.body['client' + D] : document.documentElement['client' + D]; - }); - return dimensions; - }, - getWidth: function() { - return this.getDimensions().width; - }, +document.viewport = { - getHeight: function() { - return this.getDimensions().height; + getDimensions: function() { + return { width: this.getWidth(), height: this.getHeight() }; }, getScrollOffsets: function() { return Element._returnOffset( window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, - window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); } }; -/* Portions of the Selector class are derived from Jack Slocum’s DomQuery, - * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style - * license. Please see http://www.yui-ext.com/ for more information. */ -var Selector = Class.create({ - initialize: function(expression) { - this.expression = expression.strip(); - this.compileMatcher(); - }, +(function(viewport) { + var B = Prototype.Browser, doc = document, element, property = {}; - shouldUseXPath: function() { - if (!Prototype.BrowserFeatures.XPath) return false; + function getRootElement() { + if (B.WebKit && !doc.evaluate) + return document; - var e = this.expression; + if (B.Opera && window.parseFloat(window.opera.version()) < 9.5) + return document.body; - // Safari 3 chokes on :*-of-type and :empty - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; + return document.documentElement; + } - // XPath can't do namespaced attributes, nor can it read - // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) - return false; + function define(D) { + if (!element) element = getRootElement(); - return true; - }, + property[D] = 'client' + D; - compileMatcher: function() { - if (this.shouldUseXPath()) - return this.compileXPathMatcher(); + viewport['get' + D] = function() { return element[property[D]] }; + return viewport['get' + D](); + } - var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m; + viewport.getWidth = define.curry('Width'); - if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; - return; - } + viewport.getHeight = define.curry('Height'); +})(document.viewport); - this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Selector.handlers, c = false, n;"]; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : - new Template(c[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } +Element.Storage = { + UID: 1 +}; + +Element.addMethods({ + getStorage: function(element) { + if (!(element = $(element))) return; + + var uid; + if (element === window) { + uid = 0; + } else { + if (typeof element._prototypeUID === "undefined") + element._prototypeUID = Element.Storage.UID++; + uid = element._prototypeUID; } - this.matcher.push("return h.unique(n);\n}"); - eval(this.matcher.join('\n')); - Selector._cache[this.expression] = this.matcher; + if (!Element.Storage[uid]) + Element.Storage[uid] = $H(); + + return Element.Storage[uid]; }, - compileXPathMatcher: function() { - var e = this.expression, ps = Selector.patterns, - x = Selector.xpath, le, m; + store: function(element, key, value) { + if (!(element = $(element))) return; - if (Selector._cache[e]) { - this.xpath = Selector._cache[e]; return; + if (arguments.length === 2) { + Element.getStorage(element).update(key); + } else { + Element.getStorage(element).set(key, value); } - this.matcher = ['.//*']; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - if (m = e.match(ps[i])) { - this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : - new Template(x[i]).evaluate(m)); - e = e.replace(m[0], ''); - break; - } - } + return element; + }, + + retrieve: function(element, key, defaultValue) { + if (!(element = $(element))) return; + var hash = Element.getStorage(element), value = hash.get(key); + + if (Object.isUndefined(value)) { + hash.set(key, defaultValue); + value = defaultValue; } - this.xpath = this.matcher.join(''); - Selector._cache[this.expression] = this.xpath; + return value; }, - findElements: function(root) { - root = root || document; - if (this.xpath) return document._getElementsByXPath(this.xpath, root); - return this.matcher(root); + clone: function(element, deep) { + if (!(element = $(element))) return; + var clone = element.cloneNode(deep); + clone._prototypeUID = void 0; + if (deep) { + var descendants = Element.select(clone, '*'), + i = descendants.length; + while (i--) { + descendants[i]._prototypeUID = void 0; + } + } + return Element.extend(clone); }, - match: function(element) { - this.tokens = []; + purge: function(element) { + if (!(element = $(element))) return; + purgeElement(element); - var e = this.expression, ps = Selector.patterns, as = Selector.assertions; - var le, p, m; + var descendants = element.getElementsByTagName('*'), + i = descendants.length; - while (e && le !== e && (/\S/).test(e)) { - le = e; - for (var i in ps) { - p = ps[i]; - if (m = e.match(p)) { - // use the Selector.assertions methods unless the selector - // is too complex. - if (as[i]) { - this.tokens.push([i, Object.clone(m)]); - e = e.replace(m[0], ''); - } else { - // reluctantly do a document-wide search - // and look for a match in the array - return this.findElements(document).include(element); - } - } - } + while (i--) purgeElement(descendants[i]); + + return null; + } +}); + +(function() { + + function toDecimal(pctString) { + var match = pctString.match(/^(\d+)%?$/i); + if (!match) return null; + return (Number(match[1]) / 100); + } + + function getPixelValue(value, property) { + if (Object.isElement(value)) { + element = value; + value = element.getStyle(property); + } + if (value === null) { + return null; } - var match = true, name, matches; - for (var i = 0, token; token = this.tokens[i]; i++) { - name = token[0], matches = token[1]; - if (!Selector.assertions[name](element, matches)) { - match = false; break; - } + if ((/^(?:-)?\d+(\.\d+)?(px)?$/i).test(value)) { + return window.parseFloat(value); } - return match; - }, + if (/\d/.test(value) && element.runtimeStyle) { + var style = element.style.left, rStyle = element.runtimeStyle.left; + element.runtimeStyle.left = element.currentStyle.left; + element.style.left = value || 0; + value = element.style.pixelLeft; + element.style.left = style; + element.runtimeStyle.left = rStyle; - toString: function() { - return this.expression; - }, + return value; + } + + if (value.include('%')) { + var decimal = toDecimal(value); + var whole; + if (property.include('left') || property.include('right') || + property.include('width')) { + whole = $(element.parentNode).measure('width'); + } else if (property.include('top') || property.include('bottom') || + property.include('height')) { + whole = $(element.parentNode).measure('height'); + } + + return whole * decimal; + } - inspect: function() { - return "#"; + return 0; } -}); -Object.extend(Selector, { - _cache: { }, - - xpath: { - descendant: "//*", - child: "/*", - adjacent: "/following-sibling::*[1]", - laterSibling: '/following-sibling::*', - tagName: function(m) { - if (m[1] == '*') return ''; - return "[local-name()='" + m[1].toLowerCase() + - "' or local-name()='" + m[1].toUpperCase() + "']"; - }, - className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", - id: "[@id='#{1}']", - attrPresence: function(m) { - m[1] = m[1].toLowerCase(); - return new Template("[@#{1}]").evaluate(m); - }, - attr: function(m) { - m[1] = m[1].toLowerCase(); - m[3] = m[5] || m[6]; - return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + function toCSSPixels(number) { + if (Object.isString(number) && number.endsWith('px')) { + return number; + } + return number + 'px'; + } + + function isDisplayed(element) { + var originalElement = element; + while (element && element.parentNode) { + var display = element.getStyle('display'); + if (display === 'none') { + return false; + } + element = $(element.parentNode); + } + return true; + } + + var hasLayout = Prototype.K; + if ('currentStyle' in document.documentElement) { + hasLayout = function(element) { + if (!element.currentStyle.hasLayout) { + element.style.zoom = 1; + } + return element; + }; + } + + function cssNameFor(key) { + if (key.include('border')) key = key + '-width'; + return key.camelize(); + } + + Element.Layout = Class.create(Hash, { + initialize: function($super, element, preCompute) { + $super(); + this.element = $(element); + + Element.Layout.PROPERTIES.each( function(property) { + this._set(property, null); + }, this); + + if (preCompute) { + this._preComputing = true; + this._begin(); + Element.Layout.PROPERTIES.each( this._compute, this ); + this._end(); + this._preComputing = false; + } }, - pseudo: function(m) { - var h = Selector.xpath.pseudos[m[1]]; - if (!h) return ''; - if (Object.isFunction(h)) return h(m); - return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + + _set: function(property, value) { + return Hash.prototype.set.call(this, property, value); }, - operators: { - '=': "[@#{1}='#{3}']", - '!=': "[@#{1}!='#{3}']", - '^=': "[starts-with(@#{1}, '#{3}')]", - '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", - '*=': "[contains(@#{1}, '#{3}')]", - '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", - '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + + set: function(property, value) { + throw "Properties of Element.Layout are read-only."; }, - pseudos: { - 'first-child': '[not(preceding-sibling::*)]', - 'last-child': '[not(following-sibling::*)]', - 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", - 'checked': "[@checked]", - 'disabled': "[@disabled]", - 'enabled': "[not(@disabled)]", - 'not': function(m) { - var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v; - - var exclusion = []; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i in p) { - if (m = e.match(p[i])) { - v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); - exclusion.push("(" + v.substring(1, v.length - 1) + ")"); - e = e.replace(m[0], ''); - break; - } - } - } - return "[not(" + exclusion.join(" and ") + ")]"; + + get: function($super, property) { + var value = $super(property); + return value === null ? this._compute(property) : value; + }, + + _begin: function() { + if (this._prepared) return; + + var element = this.element; + if (isDisplayed(element)) { + this._prepared = true; + return; + } + + var originalStyles = { + position: element.style.position || '', + width: element.style.width || '', + visibility: element.style.visibility || '', + display: element.style.display || '' + }; + + element.store('prototype_original_styles', originalStyles); + + var position = element.getStyle('position'), + width = element.getStyle('width'); + + element.setStyle({ + position: 'absolute', + visibility: 'hidden', + display: 'block' + }); + + var positionedWidth = element.getStyle('width'); + + var newWidth; + if (width && (positionedWidth === width)) { + newWidth = getPixelValue(width); + } else if (width && (position === 'absolute' || position === 'fixed')) { + newWidth = getPixelValue(width); + } else { + var parent = element.parentNode, pLayout = $(parent).getLayout(); + + newWidth = pLayout.get('width') - + this.get('margin-left') - + this.get('border-left') - + this.get('padding-left') - + this.get('padding-right') - + this.get('border-right') - + this.get('margin-right'); + } + + element.setStyle({ width: newWidth + 'px' }); + + this._prepared = true; + }, + + _end: function() { + var element = this.element; + var originalStyles = element.retrieve('prototype_original_styles'); + element.store('prototype_original_styles', null); + element.setStyle(originalStyles); + this._prepared = false; + }, + + _compute: function(property) { + var COMPUTATIONS = Element.Layout.COMPUTATIONS; + if (!(property in COMPUTATIONS)) { + throw "Property not found."; + } + return this._set(property, COMPUTATIONS[property].call(this, this.element)); + }, + + toObject: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var obj = {}; + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + var value = this.get(key); + if (value != null) obj[key] = value; + }, this); + return obj; + }, + + toHash: function() { + var obj = this.toObject.apply(this, arguments); + return new Hash(obj); + }, + + toCSS: function() { + var args = $A(arguments); + var keys = (args.length === 0) ? Element.Layout.PROPERTIES : + args.join(' ').split(' '); + var css = {}; + + keys.each( function(key) { + if (!Element.Layout.PROPERTIES.include(key)) return; + if (Element.Layout.COMPOSITE_PROPERTIES.include(key)) return; + + var value = this.get(key); + if (value != null) css[cssNameFor(key)] = value + 'px'; + }, this); + return css; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Element.Layout, { + PROPERTIES: $w('height width top left right bottom border-left border-right border-top border-bottom padding-left padding-right padding-top padding-bottom margin-top margin-bottom margin-left margin-right padding-box-width padding-box-height border-box-width border-box-height margin-box-width margin-box-height'), + + COMPOSITE_PROPERTIES: $w('padding-box-width padding-box-height margin-box-width margin-box-height border-box-width border-box-height'), + + COMPUTATIONS: { + 'height': function(element) { + if (!this._preComputing) this._begin(); + + var bHeight = this.get('border-box-height'); + if (bHeight <= 0) return 0; + + var bTop = this.get('border-top'), + bBottom = this.get('border-bottom'); + + var pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + if (!this._preComputing) this._end(); + + return bHeight - bTop - bBottom - pTop - pBottom; }, - 'nth-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + + 'width': function(element) { + if (!this._preComputing) this._begin(); + + var bWidth = this.get('border-box-width'); + if (bWidth <= 0) return 0; + + var bLeft = this.get('border-left'), + bRight = this.get('border-right'); + + var pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + if (!this._preComputing) this._end(); + + return bWidth - bLeft - bRight - pLeft - pRight; }, - 'nth-last-child': function(m) { - return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + + 'padding-box-height': function(element) { + var height = this.get('height'), + pTop = this.get('padding-top'), + pBottom = this.get('padding-bottom'); + + return height + pTop + pBottom; }, - 'nth-of-type': function(m) { - return Selector.xpath.pseudos.nth("position() ", m); + + 'padding-box-width': function(element) { + var width = this.get('width'), + pLeft = this.get('padding-left'), + pRight = this.get('padding-right'); + + return width + pLeft + pRight; }, - 'nth-last-of-type': function(m) { - return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + + 'border-box-height': function(element) { + return element.offsetHeight; }, - 'first-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + + 'border-box-width': function(element) { + return element.offsetWidth; }, - 'last-of-type': function(m) { - m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + + 'margin-box-height': function(element) { + var bHeight = this.get('border-box-height'), + mTop = this.get('margin-top'), + mBottom = this.get('margin-bottom'); + + if (bHeight <= 0) return 0; + + return bHeight + mTop + mBottom; }, - 'only-of-type': function(m) { - var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + + 'margin-box-width': function(element) { + var bWidth = this.get('border-box-width'), + mLeft = this.get('margin-left'), + mRight = this.get('margin-right'); + + if (bWidth <= 0) return 0; + + return bWidth + mLeft + mRight; }, - nth: function(fragment, m) { - var mm, formula = m[6], predicate; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - if (mm = formula.match(/^(\d+)$/)) // digit only - return '[' + fragment + "= " + mm[1] + ']'; - if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (mm[1] == "-") mm[1] = -1; - var a = mm[1] ? Number(mm[1]) : 1; - var b = mm[2] ? Number(mm[2]) : 0; - predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + - "((#{fragment} - #{b}) div #{a} >= 0)]"; - return new Template(predicate).evaluate({ - fragment: fragment, a: a, b: b }); - } + + 'top': function(element) { + var offset = element.positionedOffset(); + return offset.top; + }, + + 'bottom': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pHeight = parent.measure('height'); + + var mHeight = this.get('border-box-height'); + + return pHeight - mHeight - offset.top; + }, + + 'left': function(element) { + var offset = element.positionedOffset(); + return offset.left; + }, + + 'right': function(element) { + var offset = element.positionedOffset(), + parent = element.getOffsetParent(), + pWidth = parent.measure('width'); + + var mWidth = this.get('border-box-width'); + + return pWidth - mWidth - offset.left; + }, + + 'padding-top': function(element) { + return getPixelValue(element, 'paddingTop'); + }, + + 'padding-bottom': function(element) { + return getPixelValue(element, 'paddingBottom'); + }, + + 'padding-left': function(element) { + return getPixelValue(element, 'paddingLeft'); + }, + + 'padding-right': function(element) { + return getPixelValue(element, 'paddingRight'); + }, + + 'border-top': function(element) { + return Object.isNumber(element.clientTop) ? element.clientTop : + getPixelValue(element, 'borderTopWidth'); + }, + + 'border-bottom': function(element) { + return Object.isNumber(element.clientBottom) ? element.clientBottom : + getPixelValue(element, 'borderBottomWidth'); + }, + + 'border-left': function(element) { + return Object.isNumber(element.clientLeft) ? element.clientLeft : + getPixelValue(element, 'borderLeftWidth'); + }, + + 'border-right': function(element) { + return Object.isNumber(element.clientRight) ? element.clientRight : + getPixelValue(element, 'borderRightWidth'); + }, + + 'margin-top': function(element) { + return getPixelValue(element, 'marginTop'); + }, + + 'margin-bottom': function(element) { + return getPixelValue(element, 'marginBottom'); + }, + + 'margin-left': function(element) { + return getPixelValue(element, 'marginLeft'); + }, + + 'margin-right': function(element) { + return getPixelValue(element, 'marginRight'); } } - }, + }); - criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', - attr: function(m) { - m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); - }, - pseudo: function(m) { - if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); - return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); - }, - descendant: 'c = "descendant";', - child: 'c = "child";', - adjacent: 'c = "adjacent";', - laterSibling: 'c = "laterSibling";' - }, - - patterns: { - // combinators must be listed first - // (and descendant needs to be last combinator) - laterSibling: /^\s*~\s*/, - child: /^\s*>\s*/, - adjacent: /^\s*\+\s*/, - descendant: /^\s/, - - // selectors follow - tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, - id: /^#([\w\-\*]+)(\b|$)/, - className: /^\.([\w\-\*]+)(\b|$)/, - pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s)|(?=:))/, - attrPresence: /^\[([\w]+)\]/, - attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ - }, - - // for Selector.match and Element#match - assertions: { - tagName: function(element, matches) { - return matches[1].toUpperCase() == element.tagName.toUpperCase(); + if ('getBoundingClientRect' in document.documentElement) { + Object.extend(Element.Layout.COMPUTATIONS, { + 'right': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.right - rect.right).round(); + }, + + 'bottom': function(element) { + var parent = hasLayout(element.getOffsetParent()); + var rect = element.getBoundingClientRect(), + pRect = parent.getBoundingClientRect(); + + return (pRect.bottom - rect.bottom).round(); + } + }); + } + + Element.Offset = Class.create({ + initialize: function(left, top) { + this.left = left.round(); + this.top = top.round(); + + this[0] = this.left; + this[1] = this.top; }, - className: function(element, matches) { - return Element.hasClassName(element, matches[1]); + relativeTo: function(offset) { + return new Element.Offset( + this.left - offset.left, + this.top - offset.top + ); }, - id: function(element, matches) { - return element.id === matches[1]; + inspect: function() { + return "#".interpolate(this); }, - attrPresence: function(element, matches) { - return Element.hasAttribute(element, matches[1]); + toString: function() { + return "[#{left}, #{top}]".interpolate(this); }, - attr: function(element, matches) { - var nodeValue = Element.readAttribute(element, matches[1]); - return Selector.operators[matches[2]](nodeValue, matches[3]); + toArray: function() { + return [this.left, this.top]; } - }, + }); - handlers: { - // UTILITY FUNCTIONS - // joins two collections - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - a.push(node); - return a; - }, + function getLayout(element, preCompute) { + return new Element.Layout(element, preCompute); + } - // marks an array of nodes for counting - mark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._counted = true; - return nodes; - }, + function measure(element, property) { + return $(element).getLayout().get(property); + } - unmark: function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._counted = undefined; - return nodes; - }, + function getDimensions(element) { + var layout = $(element).getLayout(); + return { + width: layout.get('width'), + height: layout.get('height') + }; + } - // mark each child node with its position (for nth calls) - // "ofType" flag indicates whether we're indexing for nth-of-type - // rather than nth-child - index: function(parentNode, reverse, ofType) { - parentNode._counted = true; - if (reverse) { - for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; - } - } else { - for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; + function getOffsetParent(element) { + if (isDetached(element)) return $(document.body); + + var isInline = (Element.getStyle(element, 'display') === 'inline'); + if (!isInline && element.offsetParent) return $(element.offsetParent); + if (element === document.body) return $(element); + + while ((element = element.parentNode) && element !== document.body) { + if (Element.getStyle(element, 'position') !== 'static') { + return (element.nodeName === 'HTML') ? $(document.body) : $(element); } - }, + } - // filters out duplicates and extends all nodes - unique: function(nodes) { - if (nodes.length == 0) return nodes; - var results = [], n; - for (var i = 0, l = nodes.length; i < l; i++) - if (!(n = nodes[i])._counted) { - n._counted = true; - results.push(Element.extend(n)); - } - return Selector.handlers.unmark(results); - }, + return $(document.body); + } - // COMBINATOR FUNCTIONS - descendant: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName('*')); - return results; - }, - child: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, child; child = node.childNodes[j]; j++) - if (child.nodeType == 1 && child.tagName != '!') results.push(child); + function cumulativeOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function positionedOffset(element) { + var layout = element.getLayout(); + + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (isBody(element)) break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; } - return results; - }, + } while (element); + + valueL -= layout.get('margin-top'); + valueT -= layout.get('margin-left'); + + return new Element.Offset(valueL, valueT); + } + + function cumulativeScrollOffset(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return new Element.Offset(valueL, valueT); + } + + function viewportOffset(forElement) { + var valueT = 0, valueL = 0, docBody = document.body; - adjacent: function(nodes) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - var next = this.nextElementSibling(node); - if (next) results.push(next); + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == docBody && + Element.getStyle(element, 'position') == 'absolute') break; + } while (element = element.offsetParent); + + element = forElement; + do { + if (element != docBody) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; } - return results; - }, + } while (element = element.parentNode); + return new Element.Offset(valueL, valueT); + } - laterSibling: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, Element.nextSiblings(node)); - return results; - }, + function absolutize(element) { + element = $(element); - nextElementSibling: function(node) { - while (node = node.nextSibling) - if (node.nodeType == 1) return node; - return null; - }, + if (Element.getStyle(element, 'position') === 'absolute') { + return element; + } - previousElementSibling: function(node) { - while (node = node.previousSibling) - if (node.nodeType == 1) return node; - return null; - }, + var offsetParent = getOffsetParent(element); + var eOffset = element.viewportOffset(), + pOffset = offsetParent.viewportOffset(); - // TOKEN FUNCTIONS - tagName: function(nodes, root, tagName, combinator) { - tagName = tagName.toUpperCase(); - var results = [], h = Selector.handlers; - if (nodes) { - if (combinator) { - // fastlane for ordinary descendant combinators - if (combinator == "descendant") { - for (var i = 0, node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName(tagName)); - return results; - } else nodes = this[combinator](nodes); - if (tagName == "*") return nodes; - } - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() == tagName) results.push(node); - return results; - } else return root.getElementsByTagName(tagName); - }, + var offset = eOffset.relativeTo(pOffset); + var layout = element.getLayout(); + + element.store('prototype_absolutize_original_styles', { + left: element.getStyle('left'), + top: element.getStyle('top'), + width: element.getStyle('width'), + height: element.getStyle('height') + }); + + element.setStyle({ + position: 'absolute', + top: offset.top + 'px', + left: offset.left + 'px', + width: layout.get('width') + 'px', + height: layout.get('height') + 'px' + }); - id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Selector.handlers; - if (!targetNode) return []; - if (!nodes && root == document) return [targetNode]; - if (nodes) { - if (combinator) { - if (combinator == 'child') { - for (var i = 0, node; node = nodes[i]; i++) - if (targetNode.parentNode == node) return [targetNode]; - } else if (combinator == 'descendant') { - for (var i = 0, node; node = nodes[i]; i++) - if (Element.descendantOf(targetNode, node)) return [targetNode]; - } else if (combinator == 'adjacent') { - for (var i = 0, node; node = nodes[i]; i++) - if (Selector.handlers.previousElementSibling(targetNode) == node) - return [targetNode]; - } else nodes = h[combinator](nodes); + return element; + } + + function relativize(element) { + element = $(element); + if (Element.getStyle(element, 'position') === 'relative') { + return element; + } + + var originalStyles = + element.retrieve('prototype_absolutize_original_styles'); + + if (originalStyles) element.setStyle(originalStyles); + return element; + } + + Element.addMethods({ + getLayout: getLayout, + measure: measure, + getDimensions: getDimensions, + getOffsetParent: getOffsetParent, + cumulativeOffset: cumulativeOffset, + positionedOffset: positionedOffset, + cumulativeScrollOffset: cumulativeScrollOffset, + viewportOffset: viewportOffset, + absolutize: absolutize, + relativize: relativize + }); + + function isBody(element) { + return element.nodeName.toUpperCase() === 'BODY'; + } + + function isDetached(element) { + return element !== document.body && + !Element.descendantOf(element, document.body); + } + + if ('getBoundingClientRect' in document.documentElement) { + Element.addMethods({ + viewportOffset: function(element) { + element = $(element); + if (isDetached(element)) return new Element.Offset(0, 0); + + var rect = element.getBoundingClientRect(), + docEl = document.documentElement; + return new Element.Offset(rect.left - docEl.clientLeft, + rect.top - docEl.clientTop); + }, + + positionedOffset: function(element) { + element = $(element); + var parent = element.getOffsetParent(); + if (isDetached(element)) return new Element.Offset(0, 0); + + if (element.offsetParent && + element.offsetParent.nodeName.toUpperCase() === 'HTML') { + return positionedOffset(element); } - for (var i = 0, node; node = nodes[i]; i++) - if (node == targetNode) return [targetNode]; - return []; + + var eOffset = element.viewportOffset(), + pOffset = isBody(parent) ? viewportOffset(parent) : + parent.viewportOffset(); + var retOffset = eOffset.relativeTo(pOffset); + + var layout = element.getLayout(); + var top = retOffset.top - layout.get('margin-top'); + var left = retOffset.left - layout.get('margin-left'); + + return new Element.Offset(left, top); } - return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; - }, + }); + } +})(); +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; - className: function(nodes, root, className, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - return Selector.handlers.byClassName(nodes, root, className); - }, +Prototype.Selector = (function() { - byClassName: function(nodes, root, className) { - if (!nodes) nodes = Selector.handlers.descendant([root]); - var needle = ' ' + className + ' '; - for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { - nodeClassName = node.className; - if (nodeClassName.length == 0) continue; - if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) - results.push(node); + function select() { + throw new Error('Method "Prototype.Selector.select" must be defined.'); + } + + function match() { + throw new Error('Method "Prototype.Selector.match" must be defined.'); + } + + function find(elements, expression, index) { + index = index || 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); } - return results; - }, + } + } - attrPresence: function(nodes, root, attr) { - if (!nodes) nodes = root.getElementsByTagName("*"); - var results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (Element.hasAttribute(node, attr)) results.push(node); - return results; - }, + function extendElements(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + + var K = Prototype.K; + + return { + select: select, + match: match, + find: find, + extendElements: (Element.extend === K) ? K : extendElements, + extendElement: Element.extend + }; +})(); +Prototype._original_property = window.Sizzle; +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +(function(){ + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

    "; + + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
    "; + + if ( div.getElementsByClassName("e").length === 0 ) + return; + + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} - attr: function(nodes, root, attr, value, operator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - var handler = Selector.operators[operator], results = []; - for (var i = 0, node; node = nodes[i]; i++) { - var nodeValue = Element.readAttribute(node, attr); - if (nodeValue === null) continue; - if (handler(nodeValue, value)) results.push(node); - } - return results; - }, +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} - pseudo: function(nodes, name, value, root, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - if (!nodes) nodes = root.getElementsByTagName("*"); - return Selector.pseudos[name](nodes, value, root); - } - }, +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; - pseudos: { - 'first-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.previousElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'last-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.nextElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'only-child': function(nodes, value, root) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) - results.push(node); - return results; - }, - 'nth-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root); - }, - 'nth-last-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true); - }, - 'nth-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, false, true); - }, - 'nth-last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true, true); - }, - 'first-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, false, true); - }, - 'last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, true, true); - }, - 'only-of-type': function(nodes, formula, root) { - var p = Selector.pseudos; - return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); - }, +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; - // handles the an+b logic - getIndices: function(a, b, total) { - if (a == 0) return b > 0 ? [b] : []; - return $R(1, total).inject([], function(memo, i) { - if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); - return memo; - }); - }, +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; - // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type - nth: function(nodes, formula, root, reverse, ofType) { - if (nodes.length == 0) return []; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - var h = Selector.handlers, results = [], indexed = [], m; - h.mark(nodes); - for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._counted) { - h.index(node.parentNode, reverse, ofType); - indexed.push(node.parentNode); - } - } - if (formula.match(/^\d+$/)) { // just a number - formula = Number(formula); - for (var i = 0, node; node = nodes[i]; i++) - if (node.nodeIndex == formula) results.push(node); - } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); - for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { - for (var j = 0; j < l; j++) - if (node.nodeIndex == indices[j]) results.push(node); - } - } - h.unmark(nodes); - h.unmark(indexed); - return results; - }, + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } - 'empty': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - // IE treats comments as element nodes - if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; - results.push(node); - } - return results; - }, + selector = Expr.relative[selector] ? selector + "*" : selector; - 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); - h.mark(exclusions); - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._counted) results.push(node); - h.unmark(exclusions); - return results; - }, + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } - 'enabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled) results.push(node); - return results; - }, + return Sizzle.filter( later, tmpSet ); +}; - 'disabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.disabled) results.push(node); - return results; - }, - 'checked': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.checked) results.push(node); - return results; - } - }, +window.Sizzle = Sizzle; - operators: { - '=': function(nv, v) { return nv == v; }, - '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv.startsWith(v); }, - '$=': function(nv, v) { return nv.endsWith(v); }, - '*=': function(nv, v) { return nv.include(v); }, - '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } - }, +})(); - matchElements: function(elements, expression) { - var matches = new Selector(expression).findElements(), h = Selector.handlers; - h.mark(matches); - for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._counted) results.push(element); - h.unmark(matches); - return results; - }, +;(function(engine) { + var extendElements = Prototype.Selector.extendElements; - findElement: function(elements, expression, index) { - if (Object.isNumber(expression)) { - index = expression; expression = false; - } - return Selector.matchElements(elements, expression || '*')[index || 0]; - }, + function select(selector, scope) { + return extendElements(engine(selector, scope || document)); + } - findChildElements: function(element, expressions) { - var exprs = expressions.join(','); - expressions = []; - exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); - var results = [], h = Selector.handlers; - for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Selector(expressions[i].strip()); - h.concat(results, selector.findElements(element)); - } - return (l > 1) ? h.unique(results) : results; + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; } -}); -if (Prototype.Browser.IE) { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - Selector.handlers.concat = function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - }; -} + Prototype.Selector.engine = engine; + Prototype.Selector.select = select; + Prototype.Selector.match = match; +})(Sizzle); + +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; -function $$() { - return Selector.findChildElements(document, $A(arguments)); -} var Form = { reset: function(form) { - $(form).reset(); + form = $(form); + form.reset(); return form; }, @@ -3392,10 +4967,9 @@ var Form = { var data = elements.inject({ }, function(result, element) { if (!element.disabled && element.name) { key = element.name; value = $(element).getValue(); - if (value != null && (element.type != 'submit' || (!submitted && + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && submit !== false && (!submit || key == submit) && (submitted = true)))) { if (key in result) { - // a key is already present; construct an array of values if (!Object.isArray(result[key])) result[key] = [result[key]]; result[key].push(value); } @@ -3415,13 +4989,18 @@ Form.Methods = { }, getElements: function(form) { - return $A($(form).getElementsByTagName('*')).inject([], - function(elements, child) { - if (Form.Element.Serializers[child.tagName.toLowerCase()]) - elements.push(Element.extend(child)); - return elements; - } - ); + var elements = $(form).getElementsByTagName('*'), + element, + arr = [ ], + serializers = Form.Element.Serializers; + for (var i = 0; element = elements[i]; i++) { + arr.push(element); + } + return arr.inject([], function(elements, child) { + if (serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + }) }, getInputs: function(form, typeName, name) { @@ -3461,7 +5040,7 @@ Form.Methods = { }).sortBy(function(element) { return element.tabIndex }).first(); return firstByIndex ? firstByIndex : elements.find(function(element) { - return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + return /^(?:input|select|textarea)$/i.test(element.tagName); }); }, @@ -3492,6 +5071,7 @@ Form.Methods = { /*--------------------------------------------------------------------------*/ + Form.Element = { focus: function(element) { $(element).focus(); @@ -3505,6 +5085,7 @@ Form.Element = { }; Form.Element.Methods = { + serialize: function(element) { element = $(element); if (!element.disabled && element.name) { @@ -3545,7 +5126,7 @@ Form.Element.Methods = { try { element.focus(); if (element.select && (element.tagName.toLowerCase() != 'input' || - !['button', 'reset', 'submit'].include(element.type))) + !(/^(?:button|reset|submit)$/i.test(element.type)))) element.select(); } catch (e) { } return element; @@ -3553,7 +5134,6 @@ Form.Element.Methods = { disable: function(element) { element = $(element); - element.blur(); element.disabled = true; return element; }, @@ -3568,6 +5148,7 @@ Form.Element.Methods = { /*--------------------------------------------------------------------------*/ var Field = Form.Element; + var $F = Form.Element.Methods.getValue; /*--------------------------------------------------------------------------*/ @@ -3593,22 +5174,22 @@ Form.Element.Serializers = { else element.value = value; }, - select: function(element, index) { - if (Object.isUndefined(index)) + select: function(element, value) { + if (Object.isUndefined(value)) return this[element.type == 'select-one' ? 'selectOne' : 'selectMany'](element); else { - var opt, value, single = !Object.isArray(index); + var opt, currentValue, single = !Object.isArray(value); for (var i = 0, length = element.length; i < length; i++) { opt = element.options[i]; - value = this.optionValue(opt); + currentValue = this.optionValue(opt); if (single) { - if (value == index) { + if (currentValue == value) { opt.selected = true; return; } } - else opt.selected = index.include(value); + else opt.selected = value.include(currentValue); } } }, @@ -3630,13 +5211,13 @@ Form.Element.Serializers = { }, optionValue: function(opt) { - // extend element because hasAttribute may not be native return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; } }; /*--------------------------------------------------------------------------*/ + Abstract.TimedObserver = Class.create(PeriodicalExecuter, { initialize: function($super, element, frequency, callback) { $super(callback, frequency); @@ -3718,323 +5299,475 @@ Form.EventObserver = Class.create(Abstract.EventObserver, { return Form.serialize(this.element); } }); -if (!window.Event) var Event = { }; - -Object.extend(Event, { - KEY_BACKSPACE: 8, - KEY_TAB: 9, - KEY_RETURN: 13, - KEY_ESC: 27, - KEY_LEFT: 37, - KEY_UP: 38, - KEY_RIGHT: 39, - KEY_DOWN: 40, - KEY_DELETE: 46, - KEY_HOME: 36, - KEY_END: 35, - KEY_PAGEUP: 33, - KEY_PAGEDOWN: 34, - KEY_INSERT: 45, - - cache: { }, - - relatedTarget: function(event) { - var element; - switch(event.type) { - case 'mouseover': element = event.fromElement; break; - case 'mouseout': element = event.toElement; break; - default: return null; - } - return Element.extend(element); - } -}); +(function() { + + var Event = { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: {} + }; -Event.Methods = (function() { - var isButton; + var docEl = document.documentElement; + var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl + && 'onmouseleave' in docEl; + var _isButton; if (Prototype.Browser.IE) { var buttonMap = { 0: 1, 1: 4, 2: 2 }; - isButton = function(event, code) { - return event.button == buttonMap[code]; + _isButton = function(event, code) { + return event.button === buttonMap[code]; }; - } else if (Prototype.Browser.WebKit) { - isButton = function(event, code) { + _isButton = function(event, code) { switch (code) { case 0: return event.which == 1 && !event.metaKey; case 1: return event.which == 1 && event.metaKey; default: return false; } }; - } else { - isButton = function(event, code) { + _isButton = function(event, code) { return event.which ? (event.which === code + 1) : (event.button === code); }; } - return { - isLeftClick: function(event) { return isButton(event, 0) }, - isMiddleClick: function(event) { return isButton(event, 1) }, - isRightClick: function(event) { return isButton(event, 2) }, + function isLeftClick(event) { return _isButton(event, 0) } - element: function(event) { - var node = Event.extend(event).target; - return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); - }, + function isMiddleClick(event) { return _isButton(event, 1) } - findElement: function(event, expression) { - var element = Event.element(event); - if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); - }, + function isRightClick(event) { return _isButton(event, 2) } - pointer: function(event) { - return { - x: event.pageX || (event.clientX + - (document.documentElement.scrollLeft || document.body.scrollLeft)), - y: event.pageY || (event.clientY + - (document.documentElement.scrollTop || document.body.scrollTop)) - }; - }, + function element(event) { + event = Event.extend(event); + + var node = event.target, type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } - pointerX: function(event) { return Event.pointer(event).x }, - pointerY: function(event) { return Event.pointer(event).y }, + if (node.nodeType == Node.TEXT_NODE) + node = node.parentNode; - stop: function(event) { - Event.extend(event); - event.preventDefault(); - event.stopPropagation(); - event.stopped = true; + return Element.extend(node); + } + + function findElement(event, expression) { + var element = Event.element(event); + if (!expression) return element; + while (element) { + if (Object.isElement(element) && Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode; } + } + + function pointer(event) { + return { x: pointerX(event), y: pointerY(event) }; + } + + function pointerX(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0 }; + + return event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)); + } + + function pointerY(event) { + var docElement = document.documentElement, + body = document.body || { scrollTop: 0 }; + + return event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)); + } + + + function stop(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + + event.stopped = true; + } + + Event.Methods = { + isLeftClick: isLeftClick, + isMiddleClick: isMiddleClick, + isRightClick: isRightClick, + + element: element, + findElement: findElement, + + pointer: pointer, + pointerX: pointerX, + pointerY: pointerY, + + stop: stop }; -})(); -Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { m[name] = Event.Methods[name].methodize(); return m; }); if (Prototype.Browser.IE) { + function _relatedTarget(event) { + var element; + switch (event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } + Object.extend(methods, { stopPropagation: function() { this.cancelBubble = true }, preventDefault: function() { this.returnValue = false }, - inspect: function() { return "[object Event]" } + inspect: function() { return '[object Event]' } }); - return function(event) { + Event.extend = function(event, element) { if (!event) return false; if (event._extendedByPrototype) return event; event._extendedByPrototype = Prototype.emptyFunction; var pointer = Event.pointer(event); + Object.extend(event, { - target: event.srcElement, - relatedTarget: Event.relatedTarget(event), + target: event.srcElement || element, + relatedTarget: _relatedTarget(event), pageX: pointer.x, pageY: pointer.y }); + return Object.extend(event, methods); }; - } else { - Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__; Object.extend(Event.prototype, methods); - return Prototype.K; + Event.extend = Prototype.K; } -})(); -Object.extend(Event, (function() { - var cache = Event.cache; + function _createResponder(element, eventName, handler) { + var registry = Element.retrieve(element, 'prototype_event_registry'); - function getEventID(element) { - if (element._eventID) return element._eventID; - arguments.callee.id = arguments.callee.id || 1; - return element._eventID = ++arguments.callee.id; - } - - function getDOMEventName(eventName) { - if (eventName && eventName.include(':')) return "dataavailable"; - return eventName; - } + if (Object.isUndefined(registry)) { + CACHE.push(element); + registry = Element.retrieve(element, 'prototype_event_registry', $H()); + } - function getCacheForID(id) { - return cache[id] = cache[id] || { }; - } + var respondersForEvent = registry.get(eventName); + if (Object.isUndefined(respondersForEvent)) { + respondersForEvent = []; + registry.set(eventName, respondersForEvent); + } - function getWrappersForEventName(id, eventName) { - var c = getCacheForID(id); - return c[eventName] = c[eventName] || []; - } + if (respondersForEvent.pluck('handler').include(handler)) return false; - function createWrapper(element, eventName, handler) { - var id = getEventID(element); - var c = getWrappersForEventName(id, eventName); - if (c.pluck("handler").include(handler)) return false; + var responder; + if (eventName.include(":")) { + responder = function(event) { + if (Object.isUndefined(event.eventName)) + return false; - var wrapper = function(event) { - if (!Event || !Event.extend || - (event.eventName && event.eventName != eventName)) + if (event.eventName !== eventName) return false; - Event.extend(event); - handler.call(element, event) - }; + Event.extend(event, element); + handler.call(element, event); + }; + } else { + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED && + (eventName === "mouseenter" || eventName === "mouseleave")) { + if (eventName === "mouseenter" || eventName === "mouseleave") { + responder = function(event) { + Event.extend(event, element); + + var parent = event.relatedTarget; + while (parent && parent !== element) { + try { parent = parent.parentNode; } + catch(e) { parent = element; } + } - wrapper.handler = handler; - c.push(wrapper); - return wrapper; - } + if (parent === element) return; - function findWrapper(id, eventName, handler) { - var c = getWrappersForEventName(id, eventName); - return c.find(function(wrapper) { return wrapper.handler == handler }); - } + handler.call(element, event); + }; + } + } else { + responder = function(event) { + Event.extend(event, element); + handler.call(element, event); + }; + } + } - function destroyWrapper(id, eventName, handler) { - var c = getCacheForID(id); - if (!c[eventName]) return false; - c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + responder.handler = handler; + respondersForEvent.push(responder); + return responder; } - function destroyCache() { - for (var id in cache) - for (var eventName in cache[id]) - cache[id][eventName] = null; + function _destroyCache() { + for (var i = 0, length = CACHE.length; i < length; i++) { + Event.stopObserving(CACHE[i]); + CACHE[i] = null; + } } - if (window.attachEvent) { - window.attachEvent("onunload", destroyCache); + var CACHE = []; + + if (Prototype.Browser.IE) + window.attachEvent('onunload', _destroyCache); + + if (Prototype.Browser.WebKit) + window.addEventListener('unload', Prototype.emptyFunction, false); + + + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; + + if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { + _getDOMEventName = function(eventName) { + return (translations[eventName] || eventName); + }; } - return { - observe: function(element, eventName, handler) { - element = $(element); - var name = getDOMEventName(eventName); + function observe(element, eventName, handler) { + element = $(element); - var wrapper = createWrapper(element, eventName, handler); - if (!wrapper) return element; + var responder = _createResponder(element, eventName, handler); - if (element.addEventListener) { - element.addEventListener(name, wrapper, false); - } else { - element.attachEvent("on" + name, wrapper); + if (!responder) return element; + + if (eventName.include(':')) { + if (element.addEventListener) + element.addEventListener("dataavailable", responder, false); + else { + element.attachEvent("ondataavailable", responder); + element.attachEvent("onfilterchange", responder); } + } else { + var actualEventName = _getDOMEventName(eventName); + + if (element.addEventListener) + element.addEventListener(actualEventName, responder, false); + else + element.attachEvent("on" + actualEventName, responder); + } + + return element; + } + + function stopObserving(element, eventName, handler) { + element = $(element); + var registry = Element.retrieve(element, 'prototype_event_registry'); + if (!registry) return element; + + if (!eventName) { + registry.each( function(pair) { + var eventName = pair.key; + stopObserving(element, eventName); + }); return element; - }, + } - stopObserving: function(element, eventName, handler) { - element = $(element); - var id = getEventID(element), name = getDOMEventName(eventName); + var responders = registry.get(eventName); + if (!responders) return element; - if (!handler && eventName) { - getWrappersForEventName(id, eventName).each(function(wrapper) { - element.stopObserving(eventName, wrapper.handler); - }); - return element; + if (!handler) { + responders.each(function(r) { + stopObserving(element, eventName, r.handler); + }); + return element; + } - } else if (!eventName) { - Object.keys(getCacheForID(id)).each(function(eventName) { - element.stopObserving(eventName); - }); - return element; + var responder = responders.find( function(r) { return r.handler === handler; }); + if (!responder) return element; + + if (eventName.include(':')) { + if (element.removeEventListener) + element.removeEventListener("dataavailable", responder, false); + else { + element.detachEvent("ondataavailable", responder); + element.detachEvent("onfilterchange", responder); } + } else { + var actualEventName = _getDOMEventName(eventName); + if (element.removeEventListener) + element.removeEventListener(actualEventName, responder, false); + else + element.detachEvent('on' + actualEventName, responder); + } - var wrapper = findWrapper(id, eventName, handler); - if (!wrapper) return element; + registry.set(eventName, responders.without(responder)); - if (element.removeEventListener) { - element.removeEventListener(name, wrapper, false); - } else { - element.detachEvent("on" + name, wrapper); - } + return element; + } - destroyWrapper(id, eventName, handler); + function fire(element, eventName, memo, bubble) { + element = $(element); - return element; - }, + if (Object.isUndefined(bubble)) + bubble = true; - fire: function(element, eventName, memo) { - element = $(element); - if (element == document && document.createEvent && !element.dispatchEvent) - element = document.documentElement; + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; - if (document.createEvent) { - var event = document.createEvent("HTMLEvents"); - event.initEvent("dataavailable", true, true); - } else { - var event = document.createEventObject(); - event.eventType = "ondataavailable"; - } + var event; + if (document.createEvent) { + event = document.createEvent('HTMLEvents'); + event.initEvent('dataavailable', true, true); + } else { + event = document.createEventObject(); + event.eventType = bubble ? 'ondataavailable' : 'onfilterchange'; + } - event.eventName = eventName; - event.memo = memo || { }; + event.eventName = eventName; + event.memo = memo || { }; - if (document.createEvent) { - element.dispatchEvent(event); - } else { - element.fireEvent(event.eventType, event); - } + if (document.createEvent) + element.dispatchEvent(event); + else + element.fireEvent(event.eventType, event); + + return Event.extend(event); + } + + Event.Handler = Class.create({ + initialize: function(element, eventName, selector, callback) { + this.element = $(element); + this.eventName = eventName; + this.selector = selector; + this.callback = callback; + this.handler = this.handleEvent.bind(this); + }, + + start: function() { + Event.observe(this.element, this.eventName, this.handler); + return this; + }, + + stop: function() { + Event.stopObserving(this.element, this.eventName, this.handler); + return this; + }, - return Event.extend(event); + handleEvent: function(event) { + var element = event.findElement(this.selector); + if (element) this.callback.call(this.element, event, element); } - }; -})()); + }); -Object.extend(Event, Event.Methods); + function on(element, eventName, selector, callback) { + element = $(element); + if (Object.isFunction(selector) && Object.isUndefined(callback)) { + callback = selector, selector = null; + } -Element.addMethods({ - fire: Event.fire, - observe: Event.observe, - stopObserving: Event.stopObserving -}); + return new Event.Handler(element, eventName, selector, callback).start(); + } -Object.extend(document, { - fire: Element.Methods.fire.methodize(), - observe: Element.Methods.observe.methodize(), - stopObserving: Element.Methods.stopObserving.methodize() -}); + Object.extend(Event, Event.Methods); + + Object.extend(Event, { + fire: fire, + observe: observe, + stopObserving: stopObserving, + on: on + }); + + Element.addMethods({ + fire: fire, + + observe: observe, + + stopObserving: stopObserving, + + on: on + }); + + Object.extend(document, { + fire: fire.methodize(), + + observe: observe.methodize(), + + stopObserving: stopObserving.methodize(), + + on: on.methodize(), + + loaded: false + }); + + if (window.Event) Object.extend(window.Event, Event); + else window.Event = Event; +})(); (function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, - Matthias Miller, Dean Edwards and John Resig. */ + Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ - var timer, fired = false; + var timer; function fireContentLoadedEvent() { - if (fired) return; - if (timer) window.clearInterval(timer); - document.fire("dom:loaded"); - fired = true; + if (document.loaded) return; + if (timer) window.clearTimeout(timer); + document.loaded = true; + document.fire('dom:loaded'); } - if (document.addEventListener) { - if (Prototype.Browser.WebKit) { - timer = window.setInterval(function() { - if (/loaded|complete/.test(document.readyState)) - fireContentLoadedEvent(); - }, 0); - - Event.observe(window, "load", fireContentLoadedEvent); + function checkReadyState() { + if (document.readyState === 'complete') { + document.stopObserving('readystatechange', checkReadyState); + fireContentLoadedEvent(); + } + } - } else { - document.addEventListener("DOMContentLoaded", - fireContentLoadedEvent, false); + function pollDoScroll() { + try { document.documentElement.doScroll('left'); } + catch(e) { + timer = pollDoScroll.defer(); + return; } + fireContentLoadedEvent(); + } + if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { - document.write("