diff --git a/9781590599334.jpg b/9781590599334.jpg new file mode 100644 index 0000000..7d2dd32 Binary files /dev/null and b/9781590599334.jpg differ diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..66c52fe --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,27 @@ +Freeware License, some rights reserved + +Copyright (c) 2008 David Berube + +Permission is hereby granted, free of charge, to anyone obtaining a copy +of this software and associated documentation files (the "Software"), +to work with the Software within the limits of freeware distribution and fair use. +This includes the rights to use, copy, and modify the Software for personal use. +Users are also allowed and encouraged to submit corrections and modifications +to the Software for the benefit of other users. + +It is not allowed to reuse, modify, or redistribute the Software for +commercial use in any way, or for a user’s educational materials such as books +or blog articles without prior permission from the copyright holder. + +The above copyright notice and this permission notice need to be included +in all copies or substantial portions of the software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS OR APRESS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe778a1 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +#Apress Source Code + +This repository accompanies [*Practical Reporting with Ruby and Rails*](http://www.apress.com/9781590599334) by David Berube (Apress, 2008). + +![Cover image](9781590599334.jpg) + +Download the files as a zip using the green button, or clone the repository to your machine using Git. + +##Releases + +Release v1.0 corresponds to the code in the published book, without corrections or updates. + +##Contributions + +See the file Contributing.md for more information on how you can contribute to this repository. diff --git a/chapter01/player_salary_ratio.rb b/chapter01/player_salary_ratio.rb new file mode 100644 index 0000000..6bff982 --- /dev/null +++ b/chapter01/player_salary_ratio.rb @@ -0,0 +1,50 @@ +require 'active_record' + +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :username => 'root', # This is the default username and password + :password => '', # for MySQL, but note that if you have a + # different username and password, + # you should change it. + :database => 'players') + + +class Player < ActiveRecord::Base +end + +Player.delete_all + +Player.new do |p| + p.name = "Matthew 'm_giff' Gifford" + p.salary = 89000.00 + p.wins = 11 + p.save +end + +Player.new do |p| + p.name = "Matthew 'Iron Helix' Bouley" + p.salary = 75000.00 + p.wins = 4 + p.save +end + +Player.new do |p| + p.name = "Luke 'Cable Boy' Bouley" + p.salary = 75000.50 + p.wins = 7 + p.save +end + +salary_total = 0 +win_total=0 + +players = Player.find(:all) +players.each do |player| + puts "#{player.name}: $#{'%0.2f' % (player.salary/player.wins)} per win" + salary_total = salary_total + player.salary + win_total = win_total + player.wins +end + +puts "\nAverage Cost Per Win : $#{'%0.2f' % (salary_total / win_total )}" + diff --git a/chapter01/player_schema.sql b/chapter01/player_schema.sql new file mode 100644 index 0000000..95ab9f8 --- /dev/null +++ b/chapter01/player_schema.sql @@ -0,0 +1,10 @@ +CREATE DATABASE players; +USE players; +CREATE TABLE players ( + id int(11) NOT NULL AUTO_INCREMENT, + name TEXT, + wins int(11) NOT NULL, + salary DECIMAL(9,2), + PRIMARY KEY (id) +) + diff --git a/chapter01/player_schema_2.sql b/chapter01/player_schema_2.sql new file mode 100644 index 0000000..72eba52 --- /dev/null +++ b/chapter01/player_schema_2.sql @@ -0,0 +1,49 @@ +DROP DATABASE players_2; +CREATE DATABASE players_2; +USE players_2; + +CREATE TABLE players ( + id int(11) NOT NULL AUTO_INCREMENT, + name TEXT, + salary DECIMAL(9,2), + PRIMARY KEY (id) +); + +INSERT INTO players (id, name, salary) VALUES (1, "Matthew 'm_giff' Gifford", 89000.00); +INSERT INTO players (id, name, salary) VALUES (2, "Matthew 'Iron Helix' Bouley", 75000.00); +INSERT INTO players (id, name, salary) VALUES (3, "Luke 'Cable Boy' Bouley", 75000.50); + +CREATE TABLE games ( + id int(11) NOT NULL AUTO_INCREMENT, + name TEXT, + PRIMARY KEY (id) +); + +INSERT INTO games (id, name) VALUES (1, 'Eagle Beagle Ballad'); +INSERT INTO games (id, name) VALUES (2, 'Camel Tender Redux'); +INSERT INTO games (id, name) VALUES (3, 'Super Dunkball II: The Return'); +INSERT INTO games (id, name) VALUES (4, 'Turn the Corner SE: CRX vs Porche'); + +CREATE TABLE wins ( + id int(11) NOT NULL AUTO_INCREMENT, + player_id int(11) NOT NULL, + game_id int(11) NOT NULL, + quantity int(11) NOT NULL, + PRIMARY KEY (id) +); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (1, 1, 3); +INSERT INTO wins (player_id, game_id, quantity) VALUES (1, 3, 5); +INSERT INTO wins (player_id, game_id, quantity) VALUES (1, 2, 9); +INSERT INTO wins (player_id, game_id, quantity) VALUES (1, 4, 9); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (2, 1, 8); +INSERT INTO wins (player_id, game_id, quantity) VALUES (2, 3, 5); +INSERT INTO wins (player_id, game_id, quantity) VALUES (2, 2, 13); +INSERT INTO wins (player_id, game_id, quantity) VALUES (2, 4, 5); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (3, 1, 2); +INSERT INTO wins (player_id, game_id, quantity) VALUES (3, 3, 15); +INSERT INTO wins (player_id, game_id, quantity) VALUES (3, 2, 4); +INSERT INTO wins (player_id, game_id, quantity) VALUES (3, 4, 6); + diff --git a/chapter01/player_wins.rb b/chapter01/player_wins.rb new file mode 100644 index 0000000..2cc270b --- /dev/null +++ b/chapter01/player_wins.rb @@ -0,0 +1,54 @@ +require 'active_record' + +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :username => 'root', # This is the default username and password + :password => '', # for MySQL, but note that if you have a + # different username and password, + # you should change it. + :database => 'players_2') + + +class Player < ActiveRecord::Base + has_many :wins + def total_wins + total_wins = 0 + self.wins.each do |win| + total_wins = total_wins + win.quantity + end + total_wins + end +end +class Game < ActiveRecord::Base + has_many :wins +end +class Win < ActiveRecord::Base + belongs_to :game + belongs_to :player +end + +games = Game.find(:all) + +games.each do |game| + highest_win=nil + game.wins.each do |win| + highest_win = win if highest_win.nil? or + win.quantity > highest_win.quantity + end + + puts "#{game.name}: #{highest_win.player.name} with #{highest_win.quantity} wins" +end + +players = Player.find(:all) + +highest_winning_player = nil + +players.each do |player| + highest_winning_player = player if + highest_winning_player.nil? or + player.total_wins > highest_winning_player.total_wins +end + +puts "Highest Winning Player: #{highest_winning_player.name} with #{highest_winning_player.total_wins} wins" + diff --git a/chapter02/player_schema_3.sql b/chapter02/player_schema_3.sql new file mode 100644 index 0000000..3c7c2e8 --- /dev/null +++ b/chapter02/player_schema_3.sql @@ -0,0 +1,81 @@ +DROP DATABASE IF EXISTS players_3 ; +CREATE DATABASE players_3; +USE players_3; + +CREATE TABLE players ( + id int(11) NOT NULL AUTO_INCREMENT, + name TEXT, + drink TEXT, + salary DECIMAL(9,2), + PRIMARY KEY (id) +); + +INSERT INTO players (id, name, drink, salary) VALUES + (1, "Matthew 'm_giff' Gifford", "Moxie", 89000.00); +INSERT INTO players (id, name, drink, salary) VALUES + (2, "Matthew 'Iron Helix' Bouley", "Moxie", 75000.00); +INSERT INTO players (id, name, drink, salary) VALUES + (3, "Luke 'Cable Boy' Bouley", "Moxie", 75000.50); +INSERT INTO players (id, name, drink, salary) VALUES + (4, "Andrew 'steven-tyler-xavier' Thomas", 'Fresca', 75000.50); +INSERT INTO players (id, name, drink, salary) VALUES + (5, "John 'dwy_dwy' Dwyer", 'Fresca', 89000.00); +INSERT INTO players (id, name, drink, salary) VALUES + (6, "Ryan 'the_dominator' Peacan", 'Fresca', 75000.50); +INSERT INTO players (id, name, drink, salary) VALUES + (7, "Michael 'Shaun Wiki' Southwick", 'Fresca', 75000.50); + +CREATE TABLE games ( + id int(11) NOT NULL AUTO_INCREMENT, + name TEXT, + PRIMARY KEY (id) +); + +INSERT INTO games (id, name) VALUES (1, 'Bubble Recyler'); +INSERT INTO games (id, name) VALUES (2, 'Computer Repair King'); +INSERT INTO games (id, name) VALUES (3, 'Super Dunkball II: The Return'); +INSERT INTO games (id, name) VALUES (4, 'Sudden Decelleration: No Time to Think'); + +CREATE TABLE wins ( + id int(11) NOT NULL AUTO_INCREMENT, + player_id int(11) NOT NULL, + game_id int(11) NOT NULL, + quantity int(11) NOT NULL, + PRIMARY KEY (id) +); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (1, 1, 3); +INSERT INTO wins (player_id, game_id, quantity) VALUES (1, 3, 8); +INSERT INTO wins (player_id, game_id, quantity) VALUES (1, 2, 3); +INSERT INTO wins (player_id, game_id, quantity) VALUES (1, 4, 9); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (2, 1, 8); +INSERT INTO wins (player_id, game_id, quantity) VALUES (2, 3, 10); +INSERT INTO wins (player_id, game_id, quantity) VALUES (2, 2, 7); +INSERT INTO wins (player_id, game_id, quantity) VALUES (2, 4, 5); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (3, 1, 8); +INSERT INTO wins (player_id, game_id, quantity) VALUES (3, 3, 4); +INSERT INTO wins (player_id, game_id, quantity) VALUES (3, 2, 20); +INSERT INTO wins (player_id, game_id, quantity) VALUES (3, 4, 8); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (4, 1, 8); +INSERT INTO wins (player_id, game_id, quantity) VALUES (4, 3, 9); +INSERT INTO wins (player_id, game_id, quantity) VALUES (4, 2, 6); +INSERT INTO wins (player_id, game_id, quantity) VALUES (4, 4, 3); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (5, 1, 7); +INSERT INTO wins (player_id, game_id, quantity) VALUES (5, 3, 1); +INSERT INTO wins (player_id, game_id, quantity) VALUES (5, 2, 9); +INSERT INTO wins (player_id, game_id, quantity) VALUES (5, 4, 4); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (6, 1, 2); +INSERT INTO wins (player_id, game_id, quantity) VALUES (6, 3, 10); +INSERT INTO wins (player_id, game_id, quantity) VALUES (6, 2, 8); +INSERT INTO wins (player_id, game_id, quantity) VALUES (6, 4, 9); + +INSERT INTO wins (player_id, game_id, quantity) VALUES (7, 1, 2); +INSERT INTO wins (player_id, game_id, quantity) VALUES (7, 3, 1); +INSERT INTO wins (player_id, game_id, quantity) VALUES (7, 2, 4); +INSERT INTO wins (player_id, game_id, quantity) VALUES (7, 4, 9); + diff --git a/chapter02/salary_distribution.rb b/chapter02/salary_distribution.rb new file mode 100644 index 0000000..a0567d5 --- /dev/null +++ b/chapter02/salary_distribution.rb @@ -0,0 +1,38 @@ +require 'active_record' + +# Establish a connection to the database... +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :username => 'root', # This is the default username and password + :password => '', # for MySQL, but note that if you have a + # different username and password, + # you should change it. + :database => 'players_3') + +# ... set up our models ... +class Player < ActiveRecord::Base + has_many :wins +end +class Game < ActiveRecord::Base + has_many :wins +end +class Win < ActiveRecord::Base + belongs_to :game + belongs_to :player +end + +# ... and perform our calculation: + +puts "Salary\t\tCount" + +Player.calculate(:count, :id, :group=>'salary').each do |player| + salary, count = *player + puts "$#{'%0.2f' % salary}\t#{count} " + + # Note that the '%0.25f' % call formats the value as a float + # with two decimal points; the String's % operator works + # similarly to the C sprintf function. + +end + diff --git a/chapter03/all_players.rb b/chapter03/all_players.rb new file mode 100644 index 0000000..3ef97d6 --- /dev/null +++ b/chapter03/all_players.rb @@ -0,0 +1,81 @@ +require 'gruff' +require 'active_record' + +game_id_to_analyze = 5 + +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :username => 'your_mysql_username_here', + :password => 'your_mysql_password_here', + :database => 'players_4') + + +class Player < ActiveRecord::Base + has_many :wins +end + +class Game < ActiveRecord::Base + has_many :wins +end + +class Play < ActiveRecord::Base + belongs_to :game + belongs_to :player +end + +class Event < ActiveRecord::Base + belongs_to :play +end + +def puts_underlined(text, underline_char="=") + puts text + puts underline_char * text.length +end + +pic_dir='./all_players_graph_pics' +Dir.mkdir(pic_dir) unless File.exists?(pic_dir) + +line_chart = Gruff::Line.new(1024) +index=0 +columns = {} +Event.find(:all, :group=>'event DESC').each do |e| + columns[index] = e.event + index=index+1 +end + +line_chart.labels = columns +line_chart.legend_font_size = 10 +line_chart.legend_box_size = 10 +line_chart.title = "Chart of All Players" +line_chart.minimum_value = 0 +line_chart.maximum_value = 110 + +Player.find(:all).each do |player| + total_games = Play.count(:conditions=>['game_id = ? AND player_id = ?', + game_id_to_analyze, player.id]).to_f + total_wins = Play.count(:conditions=>['game_id = ? AND player_id = ? AND won=1', + game_id_to_analyze, player.id]).to_f + + sql = "SELECT event, avg(time) as average_time + + FROM events as e + INNER JOIN + plays as p + ON e.play_id=p.id + WHERE p.game_id='#{game_id_to_analyze}' + AND + p.player_id='#{player.id}' + GROUP + BY e.event DESC;" + data = [] + Event.find_by_sql(sql).each do |row| + data << (row.average_time.to_i/1000) + end + + line_chart.data(player.name, data ) + +end + +line_chart.write("all_players.png") + diff --git a/chapter03/guitar_chart.rb b/chapter03/guitar_chart.rb new file mode 100644 index 0000000..5984188 --- /dev/null +++ b/chapter03/guitar_chart.rb @@ -0,0 +1,12 @@ +require 'gruff' + +line_chart = Gruff::Bar.new() +line_chart.labels = {0=>'Value (USD)'} +line_chart.title = "My Guitar Collection" + +{"'70 Strat"=>2500, + "'69 Tele"=>2000, + "'02 Modded Mexi Strat Squier"=>400}.each do |guitar, value| + line_chart.data(guitar, value ) + end + diff --git a/chapter03/player_4.sql b/chapter03/player_4.sql new file mode 100644 index 0000000..f7676c5 --- /dev/null +++ b/chapter03/player_4.sql @@ -0,0 +1,1103 @@ +DROP DATABASE IF EXISTS players_4 ; +CREATE DATABASE players_4; +USE players_4; + +CREATE TABLE players ( + id INT(11) NOT NULL AUTO_INCREMENT, + name TEXT, + nickname TEXT, + drink TEXT, + salary DECIMAL(9,2), + PRIMARY KEY (id) +); + +INSERT INTO players (id, name, nickname, drink, salary) VALUES + (1, "Matthew Gifford", 'm_giff', "Moxie", 89000.00); +INSERT INTO players (id, name, nickname, drink, salary) VALUES + (2, "Matthew Bouley", 'Iron Helix', "Moxie", 75000.00); +INSERT INTO players (id, name, nickname, drink, salary) VALUES + (3, "Luke Bouley", 'Cable Boy', "Moxie", 75000.50); +INSERT INTO players (id, name, nickname, drink, salary) VALUES + (4, "Andrew Thomas", 'ste-ty-xav', 'Fresca', 75000.50); +INSERT INTO players (id, name, nickname, drink, salary) VALUES + (5, "John Dwyer", 'dwy_dwy', 'Fresca', 89000.00); +INSERT INTO players (id, name, nickname, drink, salary) VALUES + (6, "Ryan Peacan", 'the_dominator', 'Fresca', 75000.50); +INSERT INTO players (id, name, nickname, drink, salary) VALUES + (7, "Michael Southwick", 'Shaun Wiki', 'Fresca', 75000.50); + +CREATE TABLE games ( + id INT(11) NOT NULL AUTO_INCREMENT, + name TEXT, + PRIMARY KEY (id) +); + +INSERT INTO games (id, name) VALUES (1, 'Bubble Recyler'); +INSERT INTO games (id, name) VALUES (2, 'Computer Repair King'); +INSERT INTO games (id, name) VALUES (3, 'Super Dunkball II: The Return'); +INSERT INTO games (id, name) VALUES (4, 'Sudden Decelleration: No Time to Think'); +INSERT INTO games (id, name) VALUES (5, 'Tech Website Baron'); + +CREATE TABLE plays ( + id INT(11) NOT NULL, + player_id INT(11) NOT NULL, + game_id INT(11) NOT NULL, + won TINYINT NOT NULL, + PRIMARY KEY (id) +); + +CREATE TABLE events( + play_id INT(11) NOT NULL, + event VARCHAR(25) NOT NULL, + time INT(11) NOT NULL +); +INSERT INTO plays (id, player_id, game_id, won) VALUES (0, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (1, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (2, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (3, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (4, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (5, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (6, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (7, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (8, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (9, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (10, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (11, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (12, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (13, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (14, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (15, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (16, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (17, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (18, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (19, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (20, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (21, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (22, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (23, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (24, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (25, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (26, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (27, 5, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (28, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (29, 5, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (30, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (31, 6, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (32, 6, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (33, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (34, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (35, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (36, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (37, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (38, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (39, 6, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (40, 6, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (41, 6, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (42, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (43, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (44, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (45, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (46, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (47, 6, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (48, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (49, 6, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (50, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (51, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (52, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (53, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (54, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (55, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (56, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (57, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (58, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (59, 6, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (60, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (61, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (62, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (63, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (64, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (65, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (66, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (67, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (68, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (69, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (70, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (71, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (72, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (73, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (74, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (75, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (76, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (77, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (78, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (79, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (80, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (81, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (82, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (83, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (84, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (85, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (86, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (87, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (88, 1, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (89, 1, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (90, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (91, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (92, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (93, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (94, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (95, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (96, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (97, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (98, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (99, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (100, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (101, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (102, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (103, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (104, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (105, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (106, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (107, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (108, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (109, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (110, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (111, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (112, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (113, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (114, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (115, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (116, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (117, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (118, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (119, 7, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (120, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (121, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (122, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (123, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (124, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (125, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (126, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (127, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (128, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (129, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (130, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (131, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (132, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (133, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (134, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (135, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (136, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (137, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (138, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (139, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (140, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (141, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (142, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (143, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (144, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (145, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (146, 2, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (147, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (148, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (149, 2, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (150, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (151, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (152, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (153, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (154, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (155, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (156, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (157, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (158, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (159, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (160, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (161, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (162, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (163, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (164, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (165, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (166, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (167, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (168, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (169, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (170, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (171, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (172, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (173, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (174, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (175, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (176, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (177, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (178, 3, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (179, 3, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (180, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (181, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (182, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (183, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (184, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (185, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (186, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (187, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (188, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (189, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (190, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (191, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (192, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (193, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (194, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (195, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (196, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (197, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (198, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (199, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (200, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (201, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (202, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (203, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (204, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (205, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (206, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (207, 4, 5, 0); +INSERT INTO plays (id, player_id, game_id, won) VALUES (208, 4, 5, 1); +INSERT INTO plays (id, player_id, game_id, won) VALUES (209, 4, 5, 1); +INSERT INTO events (play_id, event, time) VALUES(0, 'Built DC', 2146.8); +INSERT INTO events (play_id, event, time) VALUES(0, 'Built MC', 27867); +INSERT INTO events (play_id, event, time) VALUES(0, 'Built PR', 65349); +INSERT INTO events (play_id, event, time) VALUES(0, 'Went Public', 86104); +INSERT INTO events (play_id, event, time) VALUES(1, 'Built DC', 8466.0); +INSERT INTO events (play_id, event, time) VALUES(1, 'Built MC', 29454); +INSERT INTO events (play_id, event, time) VALUES(1, 'Built PR', 57896); +INSERT INTO events (play_id, event, time) VALUES(1, 'Went Public', 79587); +INSERT INTO events (play_id, event, time) VALUES(2, 'Built DC', 31455.6); +INSERT INTO events (play_id, event, time) VALUES(2, 'Built MC', 96938); +INSERT INTO events (play_id, event, time) VALUES(2, 'Built PR', 79327); +INSERT INTO events (play_id, event, time) VALUES(2, 'Went Public', 87553); +INSERT INTO events (play_id, event, time) VALUES(3, 'Built DC', 5669.2); +INSERT INTO events (play_id, event, time) VALUES(3, 'Built MC', 25671); +INSERT INTO events (play_id, event, time) VALUES(3, 'Built PR', 44772); +INSERT INTO events (play_id, event, time) VALUES(3, 'Went Public', 82628); +INSERT INTO events (play_id, event, time) VALUES(4, 'Built DC', 1440.4); +INSERT INTO events (play_id, event, time) VALUES(4, 'Built MC', 38580); +INSERT INTO events (play_id, event, time) VALUES(4, 'Built PR', 66312); +INSERT INTO events (play_id, event, time) VALUES(4, 'Went Public', 69400); +INSERT INTO events (play_id, event, time) VALUES(5, 'Built DC', 32402.8); +INSERT INTO events (play_id, event, time) VALUES(5, 'Built MC', 37495); +INSERT INTO events (play_id, event, time) VALUES(5, 'Built PR', 90677); +INSERT INTO events (play_id, event, time) VALUES(5, 'Went Public', 141434); +INSERT INTO events (play_id, event, time) VALUES(6, 'Built DC', 24743.6); +INSERT INTO events (play_id, event, time) VALUES(6, 'Built MC', 58155); +INSERT INTO events (play_id, event, time) VALUES(6, 'Built PR', 99943); +INSERT INTO events (play_id, event, time) VALUES(6, 'Went Public', 66096); +INSERT INTO events (play_id, event, time) VALUES(7, 'Built DC', 38835.6); +INSERT INTO events (play_id, event, time) VALUES(7, 'Built MC', 83724); +INSERT INTO events (play_id, event, time) VALUES(7, 'Built PR', 107976); +INSERT INTO events (play_id, event, time) VALUES(7, 'Went Public', 125064); +INSERT INTO events (play_id, event, time) VALUES(8, 'Built DC', 968.0); +INSERT INTO events (play_id, event, time) VALUES(8, 'Built MC', 22170); +INSERT INTO events (play_id, event, time) VALUES(8, 'Built PR', 59426); +INSERT INTO events (play_id, event, time) VALUES(8, 'Went Public', 142630); +INSERT INTO events (play_id, event, time) VALUES(9, 'Built DC', 5861.2); +INSERT INTO events (play_id, event, time) VALUES(9, 'Built MC', 30005); +INSERT INTO events (play_id, event, time) VALUES(9, 'Built PR', 45446); +INSERT INTO events (play_id, event, time) VALUES(9, 'Went Public', 81025); +INSERT INTO events (play_id, event, time) VALUES(10, 'Built DC', 7048.8); +INSERT INTO events (play_id, event, time) VALUES(10, 'Built MC', 89084); +INSERT INTO events (play_id, event, time) VALUES(10, 'Built PR', 64509); +INSERT INTO events (play_id, event, time) VALUES(10, 'Went Public', 85324); +INSERT INTO events (play_id, event, time) VALUES(11, 'Built DC', 37806.8); +INSERT INTO events (play_id, event, time) VALUES(11, 'Built MC', 106697); +INSERT INTO events (play_id, event, time) VALUES(11, 'Built PR', 83550); +INSERT INTO events (play_id, event, time) VALUES(11, 'Went Public', 97563); +INSERT INTO events (play_id, event, time) VALUES(12, 'Built DC', 1550.4); +INSERT INTO events (play_id, event, time) VALUES(12, 'Built MC', 27168); +INSERT INTO events (play_id, event, time) VALUES(12, 'Built PR', 61250); +INSERT INTO events (play_id, event, time) VALUES(12, 'Went Public', 85076); +INSERT INTO events (play_id, event, time) VALUES(13, 'Built DC', 2481.2); +INSERT INTO events (play_id, event, time) VALUES(13, 'Built MC', 94543); +INSERT INTO events (play_id, event, time) VALUES(13, 'Built PR', 49802); +INSERT INTO events (play_id, event, time) VALUES(13, 'Went Public', 106684); +INSERT INTO events (play_id, event, time) VALUES(14, 'Built DC', 2038.8); +INSERT INTO events (play_id, event, time) VALUES(14, 'Built MC', 26844); +INSERT INTO events (play_id, event, time) VALUES(14, 'Built PR', 43428); +INSERT INTO events (play_id, event, time) VALUES(14, 'Went Public', 85771); +INSERT INTO events (play_id, event, time) VALUES(15, 'Built DC', 7345.6); +INSERT INTO events (play_id, event, time) VALUES(15, 'Built MC', 24577); +INSERT INTO events (play_id, event, time) VALUES(15, 'Built PR', 62961); +INSERT INTO events (play_id, event, time) VALUES(15, 'Went Public', 64357); +INSERT INTO events (play_id, event, time) VALUES(16, 'Built DC', 4024.8); +INSERT INTO events (play_id, event, time) VALUES(16, 'Built MC', 24661); +INSERT INTO events (play_id, event, time) VALUES(16, 'Built PR', 42181); +INSERT INTO events (play_id, event, time) VALUES(16, 'Went Public', 66694); +INSERT INTO events (play_id, event, time) VALUES(17, 'Built DC', 4955.2); +INSERT INTO events (play_id, event, time) VALUES(17, 'Built MC', 29848); +INSERT INTO events (play_id, event, time) VALUES(17, 'Built PR', 47524); +INSERT INTO events (play_id, event, time) VALUES(17, 'Went Public', 77887); +INSERT INTO events (play_id, event, time) VALUES(18, 'Built DC', 8159.2); +INSERT INTO events (play_id, event, time) VALUES(18, 'Built MC', 41884); +INSERT INTO events (play_id, event, time) VALUES(18, 'Built PR', 54007); +INSERT INTO events (play_id, event, time) VALUES(18, 'Went Public', 96951); +INSERT INTO events (play_id, event, time) VALUES(19, 'Built DC', 17785.6); +INSERT INTO events (play_id, event, time) VALUES(19, 'Built MC', 106847); +INSERT INTO events (play_id, event, time) VALUES(19, 'Built PR', 90728); +INSERT INTO events (play_id, event, time) VALUES(19, 'Went Public', 102050); +INSERT INTO events (play_id, event, time) VALUES(20, 'Built DC', 12394.0); +INSERT INTO events (play_id, event, time) VALUES(20, 'Built MC', 68508); +INSERT INTO events (play_id, event, time) VALUES(20, 'Built PR', 61822); +INSERT INTO events (play_id, event, time) VALUES(20, 'Went Public', 130488); +INSERT INTO events (play_id, event, time) VALUES(21, 'Built DC', 1036.4); +INSERT INTO events (play_id, event, time) VALUES(21, 'Built MC', 44605); +INSERT INTO events (play_id, event, time) VALUES(21, 'Built PR', 59335); +INSERT INTO events (play_id, event, time) VALUES(21, 'Went Public', 76953); +INSERT INTO events (play_id, event, time) VALUES(22, 'Built DC', 2539.6); +INSERT INTO events (play_id, event, time) VALUES(22, 'Built MC', 78815); +INSERT INTO events (play_id, event, time) VALUES(22, 'Built PR', 134878); +INSERT INTO events (play_id, event, time) VALUES(22, 'Went Public', 135020); +INSERT INTO events (play_id, event, time) VALUES(23, 'Built DC', 2953.6); +INSERT INTO events (play_id, event, time) VALUES(23, 'Built MC', 38900); +INSERT INTO events (play_id, event, time) VALUES(23, 'Built PR', 54599); +INSERT INTO events (play_id, event, time) VALUES(23, 'Went Public', 74198); +INSERT INTO events (play_id, event, time) VALUES(24, 'Built DC', 2663.6); +INSERT INTO events (play_id, event, time) VALUES(24, 'Built MC', 45711); +INSERT INTO events (play_id, event, time) VALUES(24, 'Built PR', 59737); +INSERT INTO events (play_id, event, time) VALUES(24, 'Went Public', 67951); +INSERT INTO events (play_id, event, time) VALUES(25, 'Built DC', 6249.2); +INSERT INTO events (play_id, event, time) VALUES(25, 'Built MC', 115578); +INSERT INTO events (play_id, event, time) VALUES(25, 'Built PR', 124943); +INSERT INTO events (play_id, event, time) VALUES(25, 'Went Public', 69048); +INSERT INTO events (play_id, event, time) VALUES(26, 'Built DC', 3108.4); +INSERT INTO events (play_id, event, time) VALUES(26, 'Built MC', 35267); +INSERT INTO events (play_id, event, time) VALUES(26, 'Built PR', 47192); +INSERT INTO events (play_id, event, time) VALUES(26, 'Went Public', 65206); +INSERT INTO events (play_id, event, time) VALUES(27, 'Built DC', 38999.2); +INSERT INTO events (play_id, event, time) VALUES(27, 'Built MC', 52179); +INSERT INTO events (play_id, event, time) VALUES(27, 'Built PR', 54867); +INSERT INTO events (play_id, event, time) VALUES(27, 'Went Public', 160966); +INSERT INTO events (play_id, event, time) VALUES(28, 'Built DC', 1380.4); +INSERT INTO events (play_id, event, time) VALUES(28, 'Built MC', 36868); +INSERT INTO events (play_id, event, time) VALUES(28, 'Built PR', 56133); +INSERT INTO events (play_id, event, time) VALUES(28, 'Went Public', 70664); +INSERT INTO events (play_id, event, time) VALUES(29, 'Built DC', 2692.4); +INSERT INTO events (play_id, event, time) VALUES(29, 'Built MC', 43706); +INSERT INTO events (play_id, event, time) VALUES(29, 'Built PR', 55413); +INSERT INTO events (play_id, event, time) VALUES(29, 'Went Public', 83349); +INSERT INTO events (play_id, event, time) VALUES(30, 'Built DC', 15220.8); +INSERT INTO events (play_id, event, time) VALUES(30, 'Built MC', 11524.0); +INSERT INTO events (play_id, event, time) VALUES(30, 'Built PR', 56665.6); +INSERT INTO events (play_id, event, time) VALUES(30, 'Went Public', 97162); +INSERT INTO events (play_id, event, time) VALUES(31, 'Built DC', 2816.4); +INSERT INTO events (play_id, event, time) VALUES(31, 'Built MC', 17399.6); +INSERT INTO events (play_id, event, time) VALUES(31, 'Built PR', 17115.2); +INSERT INTO events (play_id, event, time) VALUES(31, 'Went Public', 85598); +INSERT INTO events (play_id, event, time) VALUES(32, 'Built DC', 3849.2); +INSERT INTO events (play_id, event, time) VALUES(32, 'Built MC', 9326.0); +INSERT INTO events (play_id, event, time) VALUES(32, 'Built PR', 23521.2); +INSERT INTO events (play_id, event, time) VALUES(32, 'Went Public', 65966); +INSERT INTO events (play_id, event, time) VALUES(33, 'Built DC', 39632.4); +INSERT INTO events (play_id, event, time) VALUES(33, 'Built MC', 29296.0); +INSERT INTO events (play_id, event, time) VALUES(33, 'Built PR', 54522.0); +INSERT INTO events (play_id, event, time) VALUES(33, 'Went Public', 135081); +INSERT INTO events (play_id, event, time) VALUES(34, 'Built DC', 1884.0); +INSERT INTO events (play_id, event, time) VALUES(34, 'Built MC', 15436.0); +INSERT INTO events (play_id, event, time) VALUES(34, 'Built PR', 37742.4); +INSERT INTO events (play_id, event, time) VALUES(34, 'Went Public', 100319); +INSERT INTO events (play_id, event, time) VALUES(35, 'Built DC', 33180.8); +INSERT INTO events (play_id, event, time) VALUES(35, 'Built MC', 9159.6); +INSERT INTO events (play_id, event, time) VALUES(35, 'Built PR', 29998.0); +INSERT INTO events (play_id, event, time) VALUES(35, 'Went Public', 108797); +INSERT INTO events (play_id, event, time) VALUES(36, 'Built DC', 5013.6); +INSERT INTO events (play_id, event, time) VALUES(36, 'Built MC', 19146.4); +INSERT INTO events (play_id, event, time) VALUES(36, 'Built PR', 45293.2); +INSERT INTO events (play_id, event, time) VALUES(36, 'Went Public', 142222); +INSERT INTO events (play_id, event, time) VALUES(37, 'Built DC', 28080.4); +INSERT INTO events (play_id, event, time) VALUES(37, 'Built MC', 26508.4); +INSERT INTO events (play_id, event, time) VALUES(37, 'Built PR', 36561.2); +INSERT INTO events (play_id, event, time) VALUES(37, 'Went Public', 117535); +INSERT INTO events (play_id, event, time) VALUES(38, 'Built DC', 3885.6); +INSERT INTO events (play_id, event, time) VALUES(38, 'Built MC', 20775.6); +INSERT INTO events (play_id, event, time) VALUES(38, 'Built PR', 55325.2); +INSERT INTO events (play_id, event, time) VALUES(38, 'Went Public', 111093); +INSERT INTO events (play_id, event, time) VALUES(39, 'Built DC', 5760.4); +INSERT INTO events (play_id, event, time) VALUES(39, 'Built MC', 16857.6); +INSERT INTO events (play_id, event, time) VALUES(39, 'Built PR', 18213.6); +INSERT INTO events (play_id, event, time) VALUES(39, 'Went Public', 62133); +INSERT INTO events (play_id, event, time) VALUES(40, 'Built DC', 4610.4); +INSERT INTO events (play_id, event, time) VALUES(40, 'Built MC', 10519.2); +INSERT INTO events (play_id, event, time) VALUES(40, 'Built PR', 22803.2); +INSERT INTO events (play_id, event, time) VALUES(40, 'Went Public', 64223); +INSERT INTO events (play_id, event, time) VALUES(41, 'Built DC', 957.2); +INSERT INTO events (play_id, event, time) VALUES(41, 'Built MC', 11480.0); +INSERT INTO events (play_id, event, time) VALUES(41, 'Built PR', 21735.6); +INSERT INTO events (play_id, event, time) VALUES(41, 'Went Public', 65380); +INSERT INTO events (play_id, event, time) VALUES(42, 'Built DC', 7994.8); +INSERT INTO events (play_id, event, time) VALUES(42, 'Built MC', 48151.2); +INSERT INTO events (play_id, event, time) VALUES(42, 'Built PR', 47062.4); +INSERT INTO events (play_id, event, time) VALUES(42, 'Went Public', 148896); +INSERT INTO events (play_id, event, time) VALUES(43, 'Built DC', 29326.4); +INSERT INTO events (play_id, event, time) VALUES(43, 'Built MC', 17270.8); +INSERT INTO events (play_id, event, time) VALUES(43, 'Built PR', 51284.4); +INSERT INTO events (play_id, event, time) VALUES(43, 'Went Public', 146902); +INSERT INTO events (play_id, event, time) VALUES(44, 'Built DC', 19954.0); +INSERT INTO events (play_id, event, time) VALUES(44, 'Built MC', 13168.4); +INSERT INTO events (play_id, event, time) VALUES(44, 'Built PR', 29486.0); +INSERT INTO events (play_id, event, time) VALUES(44, 'Went Public', 62017); +INSERT INTO events (play_id, event, time) VALUES(45, 'Built DC', 20244.4); +INSERT INTO events (play_id, event, time) VALUES(45, 'Built MC', 22419.6); +INSERT INTO events (play_id, event, time) VALUES(45, 'Built PR', 28740.4); +INSERT INTO events (play_id, event, time) VALUES(45, 'Went Public', 104711); +INSERT INTO events (play_id, event, time) VALUES(46, 'Built DC', 25838.0); +INSERT INTO events (play_id, event, time) VALUES(46, 'Built MC', 32878.0); +INSERT INTO events (play_id, event, time) VALUES(46, 'Built PR', 33094.4); +INSERT INTO events (play_id, event, time) VALUES(46, 'Went Public', 74720); +INSERT INTO events (play_id, event, time) VALUES(47, 'Built DC', 6766.0); +INSERT INTO events (play_id, event, time) VALUES(47, 'Built MC', 16708.8); +INSERT INTO events (play_id, event, time) VALUES(47, 'Built PR', 23171.6); +INSERT INTO events (play_id, event, time) VALUES(47, 'Went Public', 77073); +INSERT INTO events (play_id, event, time) VALUES(48, 'Built DC', 21477.6); +INSERT INTO events (play_id, event, time) VALUES(48, 'Built MC', 32794.4); +INSERT INTO events (play_id, event, time) VALUES(48, 'Built PR', 35913.2); +INSERT INTO events (play_id, event, time) VALUES(48, 'Went Public', 97605); +INSERT INTO events (play_id, event, time) VALUES(49, 'Built DC', 7390.4); +INSERT INTO events (play_id, event, time) VALUES(49, 'Built MC', 10839.6); +INSERT INTO events (play_id, event, time) VALUES(49, 'Built PR', 23021.2); +INSERT INTO events (play_id, event, time) VALUES(49, 'Went Public', 78349); +INSERT INTO events (play_id, event, time) VALUES(50, 'Built DC', 9042.8); +INSERT INTO events (play_id, event, time) VALUES(50, 'Built MC', 32389.6); +INSERT INTO events (play_id, event, time) VALUES(50, 'Built PR', 42315.6); +INSERT INTO events (play_id, event, time) VALUES(50, 'Went Public', 67901); +INSERT INTO events (play_id, event, time) VALUES(51, 'Built DC', 6906.8); +INSERT INTO events (play_id, event, time) VALUES(51, 'Built MC', 22019.2); +INSERT INTO events (play_id, event, time) VALUES(51, 'Built PR', 53580.8); +INSERT INTO events (play_id, event, time) VALUES(51, 'Went Public', 147875); +INSERT INTO events (play_id, event, time) VALUES(52, 'Built DC', 17654.4); +INSERT INTO events (play_id, event, time) VALUES(52, 'Built MC', 34783.6); +INSERT INTO events (play_id, event, time) VALUES(52, 'Built PR', 51785.2); +INSERT INTO events (play_id, event, time) VALUES(52, 'Went Public', 63954); +INSERT INTO events (play_id, event, time) VALUES(53, 'Built DC', 15141.6); +INSERT INTO events (play_id, event, time) VALUES(53, 'Built MC', 27023.2); +INSERT INTO events (play_id, event, time) VALUES(53, 'Built PR', 20016.4); +INSERT INTO events (play_id, event, time) VALUES(53, 'Went Public', 71830); +INSERT INTO events (play_id, event, time) VALUES(54, 'Built DC', 22556.8); +INSERT INTO events (play_id, event, time) VALUES(54, 'Built MC', 26761.6); +INSERT INTO events (play_id, event, time) VALUES(54, 'Built PR', 28890.4); +INSERT INTO events (play_id, event, time) VALUES(54, 'Went Public', 132494); +INSERT INTO events (play_id, event, time) VALUES(55, 'Built DC', 25278.8); +INSERT INTO events (play_id, event, time) VALUES(55, 'Built MC', 39284.0); +INSERT INTO events (play_id, event, time) VALUES(55, 'Built PR', 46138.8); +INSERT INTO events (play_id, event, time) VALUES(55, 'Went Public', 144206); +INSERT INTO events (play_id, event, time) VALUES(56, 'Built DC', 11247.2); +INSERT INTO events (play_id, event, time) VALUES(56, 'Built MC', 40448.8); +INSERT INTO events (play_id, event, time) VALUES(56, 'Built PR', 25425.6); +INSERT INTO events (play_id, event, time) VALUES(56, 'Went Public', 141686); +INSERT INTO events (play_id, event, time) VALUES(57, 'Built DC', 33532.4); +INSERT INTO events (play_id, event, time) VALUES(57, 'Built MC', 31089.2); +INSERT INTO events (play_id, event, time) VALUES(57, 'Built PR', 16877.2); +INSERT INTO events (play_id, event, time) VALUES(57, 'Went Public', 146055); +INSERT INTO events (play_id, event, time) VALUES(58, 'Built DC', 28366.0); +INSERT INTO events (play_id, event, time) VALUES(58, 'Built MC', 36763.6); +INSERT INTO events (play_id, event, time) VALUES(58, 'Built PR', 48380.8); +INSERT INTO events (play_id, event, time) VALUES(58, 'Went Public', 70965); +INSERT INTO events (play_id, event, time) VALUES(59, 'Built DC', 39331.2); +INSERT INTO events (play_id, event, time) VALUES(59, 'Built MC', 27465.6); +INSERT INTO events (play_id, event, time) VALUES(59, 'Built PR', 51862.0); +INSERT INTO events (play_id, event, time) VALUES(59, 'Went Public', 68892); +INSERT INTO events (play_id, event, time) VALUES(60, 'Built DC', 1201.2); +INSERT INTO events (play_id, event, time) VALUES(60, 'Built MC', 111499); +INSERT INTO events (play_id, event, time) VALUES(60, 'Built PR', 87273); +INSERT INTO events (play_id, event, time) VALUES(60, 'Went Public', 100750); +INSERT INTO events (play_id, event, time) VALUES(61, 'Built DC', 7649.6); +INSERT INTO events (play_id, event, time) VALUES(61, 'Built MC', 78130); +INSERT INTO events (play_id, event, time) VALUES(61, 'Built PR', 80178); +INSERT INTO events (play_id, event, time) VALUES(61, 'Went Public', 70715); +INSERT INTO events (play_id, event, time) VALUES(62, 'Built DC', 5104.0); +INSERT INTO events (play_id, event, time) VALUES(62, 'Built MC', 43043); +INSERT INTO events (play_id, event, time) VALUES(62, 'Built PR', 57220); +INSERT INTO events (play_id, event, time) VALUES(62, 'Went Public', 69040); +INSERT INTO events (play_id, event, time) VALUES(63, 'Built DC', 968.0); +INSERT INTO events (play_id, event, time) VALUES(63, 'Built MC', 38636); +INSERT INTO events (play_id, event, time) VALUES(63, 'Built PR', 55709); +INSERT INTO events (play_id, event, time) VALUES(63, 'Went Public', 69565); +INSERT INTO events (play_id, event, time) VALUES(64, 'Built DC', 6669.2); +INSERT INTO events (play_id, event, time) VALUES(64, 'Built MC', 42619); +INSERT INTO events (play_id, event, time) VALUES(64, 'Built PR', 55467); +INSERT INTO events (play_id, event, time) VALUES(64, 'Went Public', 63081); +INSERT INTO events (play_id, event, time) VALUES(65, 'Built DC', 15643.6); +INSERT INTO events (play_id, event, time) VALUES(65, 'Built MC', 110678); +INSERT INTO events (play_id, event, time) VALUES(65, 'Built PR', 64691); +INSERT INTO events (play_id, event, time) VALUES(65, 'Went Public', 92678); +INSERT INTO events (play_id, event, time) VALUES(66, 'Built DC', 9824.4); +INSERT INTO events (play_id, event, time) VALUES(66, 'Built MC', 22725); +INSERT INTO events (play_id, event, time) VALUES(66, 'Built PR', 63769); +INSERT INTO events (play_id, event, time) VALUES(66, 'Went Public', 82252); +INSERT INTO events (play_id, event, time) VALUES(67, 'Built DC', 10184.8); +INSERT INTO events (play_id, event, time) VALUES(67, 'Built MC', 28863); +INSERT INTO events (play_id, event, time) VALUES(67, 'Built PR', 57959); +INSERT INTO events (play_id, event, time) VALUES(67, 'Went Public', 86479); +INSERT INTO events (play_id, event, time) VALUES(68, 'Built DC', 14583.6); +INSERT INTO events (play_id, event, time) VALUES(68, 'Built MC', 29153); +INSERT INTO events (play_id, event, time) VALUES(68, 'Built PR', 71868); +INSERT INTO events (play_id, event, time) VALUES(68, 'Went Public', 110186); +INSERT INTO events (play_id, event, time) VALUES(69, 'Built DC', 2194.8); +INSERT INTO events (play_id, event, time) VALUES(69, 'Built MC', 38933); +INSERT INTO events (play_id, event, time) VALUES(69, 'Built PR', 117175); +INSERT INTO events (play_id, event, time) VALUES(69, 'Went Public', 66897); +INSERT INTO events (play_id, event, time) VALUES(70, 'Built DC', 10729.2); +INSERT INTO events (play_id, event, time) VALUES(70, 'Built MC', 34343); +INSERT INTO events (play_id, event, time) VALUES(70, 'Built PR', 49430); +INSERT INTO events (play_id, event, time) VALUES(70, 'Went Public', 80975); +INSERT INTO events (play_id, event, time) VALUES(71, 'Built DC', 825.2); +INSERT INTO events (play_id, event, time) VALUES(71, 'Built MC', 36724); +INSERT INTO events (play_id, event, time) VALUES(71, 'Built PR', 66773); +INSERT INTO events (play_id, event, time) VALUES(71, 'Went Public', 68196); +INSERT INTO events (play_id, event, time) VALUES(72, 'Built DC', 8266.0); +INSERT INTO events (play_id, event, time) VALUES(72, 'Built MC', 22265); +INSERT INTO events (play_id, event, time) VALUES(72, 'Built PR', 57058); +INSERT INTO events (play_id, event, time) VALUES(72, 'Went Public', 64816); +INSERT INTO events (play_id, event, time) VALUES(73, 'Built DC', 1406.4); +INSERT INTO events (play_id, event, time) VALUES(73, 'Built MC', 25007); +INSERT INTO events (play_id, event, time) VALUES(73, 'Built PR', 59303); +INSERT INTO events (play_id, event, time) VALUES(73, 'Went Public', 70741); +INSERT INTO events (play_id, event, time) VALUES(74, 'Built DC', 8088.4); +INSERT INTO events (play_id, event, time) VALUES(74, 'Built MC', 58323); +INSERT INTO events (play_id, event, time) VALUES(74, 'Built PR', 127743); +INSERT INTO events (play_id, event, time) VALUES(74, 'Went Public', 67904); +INSERT INTO events (play_id, event, time) VALUES(75, 'Built DC', 28218.4); +INSERT INTO events (play_id, event, time) VALUES(75, 'Built MC', 55825); +INSERT INTO events (play_id, event, time) VALUES(75, 'Built PR', 133454); +INSERT INTO events (play_id, event, time) VALUES(75, 'Went Public', 104527); +INSERT INTO events (play_id, event, time) VALUES(76, 'Built DC', 2409.2); +INSERT INTO events (play_id, event, time) VALUES(76, 'Built MC', 41599); +INSERT INTO events (play_id, event, time) VALUES(76, 'Built PR', 49222); +INSERT INTO events (play_id, event, time) VALUES(76, 'Went Public', 74839); +INSERT INTO events (play_id, event, time) VALUES(77, 'Built DC', 9960.8); +INSERT INTO events (play_id, event, time) VALUES(77, 'Built MC', 27509); +INSERT INTO events (play_id, event, time) VALUES(77, 'Built PR', 46465); +INSERT INTO events (play_id, event, time) VALUES(77, 'Went Public', 84512); +INSERT INTO events (play_id, event, time) VALUES(78, 'Built DC', 1738.0); +INSERT INTO events (play_id, event, time) VALUES(78, 'Built MC', 37636); +INSERT INTO events (play_id, event, time) VALUES(78, 'Built PR', 60235); +INSERT INTO events (play_id, event, time) VALUES(78, 'Went Public', 86552); +INSERT INTO events (play_id, event, time) VALUES(79, 'Built DC', 19761.6); +INSERT INTO events (play_id, event, time) VALUES(79, 'Built MC', 65100); +INSERT INTO events (play_id, event, time) VALUES(79, 'Built PR', 46301); +INSERT INTO events (play_id, event, time) VALUES(79, 'Went Public', 76048); +INSERT INTO events (play_id, event, time) VALUES(80, 'Built DC', 9342.0); +INSERT INTO events (play_id, event, time) VALUES(80, 'Built MC', 41514); +INSERT INTO events (play_id, event, time) VALUES(80, 'Built PR', 133596); +INSERT INTO events (play_id, event, time) VALUES(80, 'Went Public', 96187); +INSERT INTO events (play_id, event, time) VALUES(81, 'Built DC', 30966.4); +INSERT INTO events (play_id, event, time) VALUES(81, 'Built MC', 102646); +INSERT INTO events (play_id, event, time) VALUES(81, 'Built PR', 71841); +INSERT INTO events (play_id, event, time) VALUES(81, 'Went Public', 85363); +INSERT INTO events (play_id, event, time) VALUES(82, 'Built DC', 11917.2); +INSERT INTO events (play_id, event, time) VALUES(82, 'Built MC', 90931); +INSERT INTO events (play_id, event, time) VALUES(82, 'Built PR', 63351); +INSERT INTO events (play_id, event, time) VALUES(82, 'Went Public', 156036); +INSERT INTO events (play_id, event, time) VALUES(83, 'Built DC', 26460.4); +INSERT INTO events (play_id, event, time) VALUES(83, 'Built MC', 99728); +INSERT INTO events (play_id, event, time) VALUES(83, 'Built PR', 51756); +INSERT INTO events (play_id, event, time) VALUES(83, 'Went Public', 66051); +INSERT INTO events (play_id, event, time) VALUES(84, 'Built DC', 2106.8); +INSERT INTO events (play_id, event, time) VALUES(84, 'Built MC', 44219); +INSERT INTO events (play_id, event, time) VALUES(84, 'Built PR', 50381); +INSERT INTO events (play_id, event, time) VALUES(84, 'Went Public', 73081); +INSERT INTO events (play_id, event, time) VALUES(85, 'Built DC', 27396.8); +INSERT INTO events (play_id, event, time) VALUES(85, 'Built MC', 120303); +INSERT INTO events (play_id, event, time) VALUES(85, 'Built PR', 119463); +INSERT INTO events (play_id, event, time) VALUES(85, 'Went Public', 122204); +INSERT INTO events (play_id, event, time) VALUES(86, 'Built DC', 6550.0); +INSERT INTO events (play_id, event, time) VALUES(86, 'Built MC', 31683); +INSERT INTO events (play_id, event, time) VALUES(86, 'Built PR', 84796); +INSERT INTO events (play_id, event, time) VALUES(86, 'Went Public', 71100); +INSERT INTO events (play_id, event, time) VALUES(87, 'Built DC', 4629.2); +INSERT INTO events (play_id, event, time) VALUES(87, 'Built MC', 37170); +INSERT INTO events (play_id, event, time) VALUES(87, 'Built PR', 60168); +INSERT INTO events (play_id, event, time) VALUES(87, 'Went Public', 78887); +INSERT INTO events (play_id, event, time) VALUES(88, 'Built DC', 4015.2); +INSERT INTO events (play_id, event, time) VALUES(88, 'Built MC', 27448); +INSERT INTO events (play_id, event, time) VALUES(88, 'Built PR', 43840); +INSERT INTO events (play_id, event, time) VALUES(88, 'Went Public', 83738); +INSERT INTO events (play_id, event, time) VALUES(89, 'Built DC', 31258.0); +INSERT INTO events (play_id, event, time) VALUES(89, 'Built MC', 111471); +INSERT INTO events (play_id, event, time) VALUES(89, 'Built PR', 115997); +INSERT INTO events (play_id, event, time) VALUES(89, 'Went Public', 94484); +INSERT INTO events (play_id, event, time) VALUES(90, 'Built DC', 10496.4); +INSERT INTO events (play_id, event, time) VALUES(90, 'Built MC', 31924); +INSERT INTO events (play_id, event, time) VALUES(90, 'Built PR', 49105); +INSERT INTO events (play_id, event, time) VALUES(90, 'Went Public', 86581); +INSERT INTO events (play_id, event, time) VALUES(91, 'Built DC', 10520.8); +INSERT INTO events (play_id, event, time) VALUES(91, 'Built MC', 26221); +INSERT INTO events (play_id, event, time) VALUES(91, 'Built PR', 64191); +INSERT INTO events (play_id, event, time) VALUES(91, 'Went Public', 68520); +INSERT INTO events (play_id, event, time) VALUES(92, 'Built DC', 7352.0); +INSERT INTO events (play_id, event, time) VALUES(92, 'Built MC', 32560); +INSERT INTO events (play_id, event, time) VALUES(92, 'Built PR', 60424); +INSERT INTO events (play_id, event, time) VALUES(92, 'Went Public', 67834); +INSERT INTO events (play_id, event, time) VALUES(93, 'Built DC', 1288.8); +INSERT INTO events (play_id, event, time) VALUES(93, 'Built MC', 22114); +INSERT INTO events (play_id, event, time) VALUES(93, 'Built PR', 59371); +INSERT INTO events (play_id, event, time) VALUES(93, 'Went Public', 78564); +INSERT INTO events (play_id, event, time) VALUES(94, 'Built DC', 4929.2); +INSERT INTO events (play_id, event, time) VALUES(94, 'Built MC', 34179); +INSERT INTO events (play_id, event, time) VALUES(94, 'Built PR', 65953); +INSERT INTO events (play_id, event, time) VALUES(94, 'Went Public', 66983); +INSERT INTO events (play_id, event, time) VALUES(95, 'Built DC', 9437.6); +INSERT INTO events (play_id, event, time) VALUES(95, 'Built MC', 40839); +INSERT INTO events (play_id, event, time) VALUES(95, 'Built PR', 60439); +INSERT INTO events (play_id, event, time) VALUES(95, 'Went Public', 74658); +INSERT INTO events (play_id, event, time) VALUES(96, 'Built DC', 6691.6); +INSERT INTO events (play_id, event, time) VALUES(96, 'Built MC', 36848); +INSERT INTO events (play_id, event, time) VALUES(96, 'Built PR', 57997); +INSERT INTO events (play_id, event, time) VALUES(96, 'Went Public', 84833); +INSERT INTO events (play_id, event, time) VALUES(97, 'Built DC', 2205.2); +INSERT INTO events (play_id, event, time) VALUES(97, 'Built MC', 41877); +INSERT INTO events (play_id, event, time) VALUES(97, 'Built PR', 47359); +INSERT INTO events (play_id, event, time) VALUES(97, 'Went Public', 73026); +INSERT INTO events (play_id, event, time) VALUES(98, 'Built DC', 5466.8); +INSERT INTO events (play_id, event, time) VALUES(98, 'Built MC', 27846); +INSERT INTO events (play_id, event, time) VALUES(98, 'Built PR', 44501); +INSERT INTO events (play_id, event, time) VALUES(98, 'Went Public', 78258); +INSERT INTO events (play_id, event, time) VALUES(99, 'Built DC', 6742.0); +INSERT INTO events (play_id, event, time) VALUES(99, 'Built MC', 35877); +INSERT INTO events (play_id, event, time) VALUES(99, 'Built PR', 52645); +INSERT INTO events (play_id, event, time) VALUES(99, 'Went Public', 85235); +INSERT INTO events (play_id, event, time) VALUES(100, 'Built DC', 10368.4); +INSERT INTO events (play_id, event, time) VALUES(100, 'Built MC', 38082); +INSERT INTO events (play_id, event, time) VALUES(100, 'Built PR', 45241); +INSERT INTO events (play_id, event, time) VALUES(100, 'Went Public', 71877); +INSERT INTO events (play_id, event, time) VALUES(101, 'Built DC', 2134.8); +INSERT INTO events (play_id, event, time) VALUES(101, 'Built MC', 38104); +INSERT INTO events (play_id, event, time) VALUES(101, 'Built PR', 58176); +INSERT INTO events (play_id, event, time) VALUES(101, 'Went Public', 73821); +INSERT INTO events (play_id, event, time) VALUES(102, 'Built DC', 6843.6); +INSERT INTO events (play_id, event, time) VALUES(102, 'Built MC', 32394); +INSERT INTO events (play_id, event, time) VALUES(102, 'Built PR', 45605); +INSERT INTO events (play_id, event, time) VALUES(102, 'Went Public', 77002); +INSERT INTO events (play_id, event, time) VALUES(103, 'Built DC', 2360.0); +INSERT INTO events (play_id, event, time) VALUES(103, 'Built MC', 43316); +INSERT INTO events (play_id, event, time) VALUES(103, 'Built PR', 46949); +INSERT INTO events (play_id, event, time) VALUES(103, 'Went Public', 85516); +INSERT INTO events (play_id, event, time) VALUES(104, 'Built DC', 6193.2); +INSERT INTO events (play_id, event, time) VALUES(104, 'Built MC', 32668); +INSERT INTO events (play_id, event, time) VALUES(104, 'Built PR', 44857); +INSERT INTO events (play_id, event, time) VALUES(104, 'Went Public', 74164); +INSERT INTO events (play_id, event, time) VALUES(105, 'Built DC', 2660.4); +INSERT INTO events (play_id, event, time) VALUES(105, 'Built MC', 40253); +INSERT INTO events (play_id, event, time) VALUES(105, 'Built PR', 43334); +INSERT INTO events (play_id, event, time) VALUES(105, 'Went Public', 83223); +INSERT INTO events (play_id, event, time) VALUES(106, 'Built DC', 5560.8); +INSERT INTO events (play_id, event, time) VALUES(106, 'Built MC', 39976); +INSERT INTO events (play_id, event, time) VALUES(106, 'Built PR', 55146); +INSERT INTO events (play_id, event, time) VALUES(106, 'Went Public', 75036); +INSERT INTO events (play_id, event, time) VALUES(107, 'Built DC', 5472.4); +INSERT INTO events (play_id, event, time) VALUES(107, 'Built MC', 31743); +INSERT INTO events (play_id, event, time) VALUES(107, 'Built PR', 52053); +INSERT INTO events (play_id, event, time) VALUES(107, 'Went Public', 73166); +INSERT INTO events (play_id, event, time) VALUES(108, 'Built DC', 2604.4); +INSERT INTO events (play_id, event, time) VALUES(108, 'Built MC', 23825); +INSERT INTO events (play_id, event, time) VALUES(108, 'Built PR', 64571); +INSERT INTO events (play_id, event, time) VALUES(108, 'Went Public', 80768); +INSERT INTO events (play_id, event, time) VALUES(109, 'Built DC', 9941.6); +INSERT INTO events (play_id, event, time) VALUES(109, 'Built MC', 32938); +INSERT INTO events (play_id, event, time) VALUES(109, 'Built PR', 52047); +INSERT INTO events (play_id, event, time) VALUES(109, 'Went Public', 68599); +INSERT INTO events (play_id, event, time) VALUES(110, 'Built DC', 1374.8); +INSERT INTO events (play_id, event, time) VALUES(110, 'Built MC', 32591); +INSERT INTO events (play_id, event, time) VALUES(110, 'Built PR', 50624); +INSERT INTO events (play_id, event, time) VALUES(110, 'Went Public', 67482); +INSERT INTO events (play_id, event, time) VALUES(111, 'Built DC', 4278.8); +INSERT INTO events (play_id, event, time) VALUES(111, 'Built MC', 41068); +INSERT INTO events (play_id, event, time) VALUES(111, 'Built PR', 55368); +INSERT INTO events (play_id, event, time) VALUES(111, 'Went Public', 78091); +INSERT INTO events (play_id, event, time) VALUES(112, 'Built DC', 10341.2); +INSERT INTO events (play_id, event, time) VALUES(112, 'Built MC', 33790); +INSERT INTO events (play_id, event, time) VALUES(112, 'Built PR', 45542); +INSERT INTO events (play_id, event, time) VALUES(112, 'Went Public', 81272); +INSERT INTO events (play_id, event, time) VALUES(113, 'Built DC', 9873.6); +INSERT INTO events (play_id, event, time) VALUES(113, 'Built MC', 41762); +INSERT INTO events (play_id, event, time) VALUES(113, 'Built PR', 55409); +INSERT INTO events (play_id, event, time) VALUES(113, 'Went Public', 76956); +INSERT INTO events (play_id, event, time) VALUES(114, 'Built DC', 6999.6); +INSERT INTO events (play_id, event, time) VALUES(114, 'Built MC', 25010); +INSERT INTO events (play_id, event, time) VALUES(114, 'Built PR', 42767); +INSERT INTO events (play_id, event, time) VALUES(114, 'Went Public', 82218); +INSERT INTO events (play_id, event, time) VALUES(115, 'Built DC', 990.0); +INSERT INTO events (play_id, event, time) VALUES(115, 'Built MC', 32807); +INSERT INTO events (play_id, event, time) VALUES(115, 'Built PR', 48271); +INSERT INTO events (play_id, event, time) VALUES(115, 'Went Public', 66155); +INSERT INTO events (play_id, event, time) VALUES(116, 'Built DC', 8426.4); +INSERT INTO events (play_id, event, time) VALUES(116, 'Built MC', 23952); +INSERT INTO events (play_id, event, time) VALUES(116, 'Built PR', 60605); +INSERT INTO events (play_id, event, time) VALUES(116, 'Went Public', 80704); +INSERT INTO events (play_id, event, time) VALUES(117, 'Built DC', 2267.2); +INSERT INTO events (play_id, event, time) VALUES(117, 'Built MC', 29146); +INSERT INTO events (play_id, event, time) VALUES(117, 'Built PR', 62370); +INSERT INTO events (play_id, event, time) VALUES(117, 'Went Public', 83613); +INSERT INTO events (play_id, event, time) VALUES(118, 'Built DC', 9220.8); +INSERT INTO events (play_id, event, time) VALUES(118, 'Built MC', 44586); +INSERT INTO events (play_id, event, time) VALUES(118, 'Built PR', 57941); +INSERT INTO events (play_id, event, time) VALUES(118, 'Went Public', 77673); +INSERT INTO events (play_id, event, time) VALUES(119, 'Built DC', 1976.0); +INSERT INTO events (play_id, event, time) VALUES(119, 'Built MC', 44865); +INSERT INTO events (play_id, event, time) VALUES(119, 'Built PR', 42698); +INSERT INTO events (play_id, event, time) VALUES(119, 'Went Public', 84158); +INSERT INTO events (play_id, event, time) VALUES(120, 'Built DC', 10487.2); +INSERT INTO events (play_id, event, time) VALUES(120, 'Built MC', 12208.0); +INSERT INTO events (play_id, event, time) VALUES(120, 'Built PR', 63772); +INSERT INTO events (play_id, event, time) VALUES(120, 'Went Public', 86400); +INSERT INTO events (play_id, event, time) VALUES(121, 'Built DC', 5112.8); +INSERT INTO events (play_id, event, time) VALUES(121, 'Built MC', 12720.0); +INSERT INTO events (play_id, event, time) VALUES(121, 'Built PR', 58643); +INSERT INTO events (play_id, event, time) VALUES(121, 'Went Public', 86998); +INSERT INTO events (play_id, event, time) VALUES(122, 'Built DC', 10750.8); +INSERT INTO events (play_id, event, time) VALUES(122, 'Built MC', 17901.6); +INSERT INTO events (play_id, event, time) VALUES(122, 'Built PR', 42785); +INSERT INTO events (play_id, event, time) VALUES(122, 'Went Public', 83405); +INSERT INTO events (play_id, event, time) VALUES(123, 'Built DC', 9205.2); +INSERT INTO events (play_id, event, time) VALUES(123, 'Built MC', 14625.2); +INSERT INTO events (play_id, event, time) VALUES(123, 'Built PR', 60608); +INSERT INTO events (play_id, event, time) VALUES(123, 'Went Public', 62941); +INSERT INTO events (play_id, event, time) VALUES(124, 'Built DC', 3822.4); +INSERT INTO events (play_id, event, time) VALUES(124, 'Built MC', 13565.6); +INSERT INTO events (play_id, event, time) VALUES(124, 'Built PR', 49094); +INSERT INTO events (play_id, event, time) VALUES(124, 'Went Public', 76906); +INSERT INTO events (play_id, event, time) VALUES(125, 'Built DC', 2399.2); +INSERT INTO events (play_id, event, time) VALUES(125, 'Built MC', 14965.2); +INSERT INTO events (play_id, event, time) VALUES(125, 'Built PR', 43538); +INSERT INTO events (play_id, event, time) VALUES(125, 'Went Public', 76440); +INSERT INTO events (play_id, event, time) VALUES(126, 'Built DC', 20025.2); +INSERT INTO events (play_id, event, time) VALUES(126, 'Built MC', 31670.8); +INSERT INTO events (play_id, event, time) VALUES(126, 'Built PR', 59958); +INSERT INTO events (play_id, event, time) VALUES(126, 'Went Public', 88198); +INSERT INTO events (play_id, event, time) VALUES(127, 'Built DC', 7200.0); +INSERT INTO events (play_id, event, time) VALUES(127, 'Built MC', 21142.8); +INSERT INTO events (play_id, event, time) VALUES(127, 'Built PR', 92298); +INSERT INTO events (play_id, event, time) VALUES(127, 'Went Public', 95279); +INSERT INTO events (play_id, event, time) VALUES(128, 'Built DC', 973.2); +INSERT INTO events (play_id, event, time) VALUES(128, 'Built MC', 17422.4); +INSERT INTO events (play_id, event, time) VALUES(128, 'Built PR', 55800); +INSERT INTO events (play_id, event, time) VALUES(128, 'Went Public', 65250); +INSERT INTO events (play_id, event, time) VALUES(129, 'Built DC', 10058.8); +INSERT INTO events (play_id, event, time) VALUES(129, 'Built MC', 14910.8); +INSERT INTO events (play_id, event, time) VALUES(129, 'Built PR', 50006); +INSERT INTO events (play_id, event, time) VALUES(129, 'Went Public', 73764); +INSERT INTO events (play_id, event, time) VALUES(130, 'Built DC', 18515.6); +INSERT INTO events (play_id, event, time) VALUES(130, 'Built MC', 31064.8); +INSERT INTO events (play_id, event, time) VALUES(130, 'Built PR', 94965); +INSERT INTO events (play_id, event, time) VALUES(130, 'Went Public', 117239); +INSERT INTO events (play_id, event, time) VALUES(131, 'Built DC', 931.6); +INSERT INTO events (play_id, event, time) VALUES(131, 'Built MC', 8986.8); +INSERT INTO events (play_id, event, time) VALUES(131, 'Built PR', 59442); +INSERT INTO events (play_id, event, time) VALUES(131, 'Went Public', 83579); +INSERT INTO events (play_id, event, time) VALUES(132, 'Built DC', 1582.4); +INSERT INTO events (play_id, event, time) VALUES(132, 'Built MC', 9077.2); +INSERT INTO events (play_id, event, time) VALUES(132, 'Built PR', 59010); +INSERT INTO events (play_id, event, time) VALUES(132, 'Went Public', 72765); +INSERT INTO events (play_id, event, time) VALUES(133, 'Built DC', 21484.0); +INSERT INTO events (play_id, event, time) VALUES(133, 'Built MC', 14765.6); +INSERT INTO events (play_id, event, time) VALUES(133, 'Built PR', 139543); +INSERT INTO events (play_id, event, time) VALUES(133, 'Went Public', 126410); +INSERT INTO events (play_id, event, time) VALUES(134, 'Built DC', 9933.6); +INSERT INTO events (play_id, event, time) VALUES(134, 'Built MC', 11744.8); +INSERT INTO events (play_id, event, time) VALUES(134, 'Built PR', 55848); +INSERT INTO events (play_id, event, time) VALUES(134, 'Went Public', 65710); +INSERT INTO events (play_id, event, time) VALUES(135, 'Built DC', 10548.0); +INSERT INTO events (play_id, event, time) VALUES(135, 'Built MC', 14846.0); +INSERT INTO events (play_id, event, time) VALUES(135, 'Built PR', 48185); +INSERT INTO events (play_id, event, time) VALUES(135, 'Went Public', 75598); +INSERT INTO events (play_id, event, time) VALUES(136, 'Built DC', 7330.8); +INSERT INTO events (play_id, event, time) VALUES(136, 'Built MC', 14648.8); +INSERT INTO events (play_id, event, time) VALUES(136, 'Built PR', 53649); +INSERT INTO events (play_id, event, time) VALUES(136, 'Went Public', 68151); +INSERT INTO events (play_id, event, time) VALUES(137, 'Built DC', 17750.8); +INSERT INTO events (play_id, event, time) VALUES(137, 'Built MC', 12101.6); +INSERT INTO events (play_id, event, time) VALUES(137, 'Built PR', 127805); +INSERT INTO events (play_id, event, time) VALUES(137, 'Went Public', 142230); +INSERT INTO events (play_id, event, time) VALUES(138, 'Built DC', 10466.4); +INSERT INTO events (play_id, event, time) VALUES(138, 'Built MC', 12889.6); +INSERT INTO events (play_id, event, time) VALUES(138, 'Built PR', 57172); +INSERT INTO events (play_id, event, time) VALUES(138, 'Went Public', 85551); +INSERT INTO events (play_id, event, time) VALUES(139, 'Built DC', 7479.6); +INSERT INTO events (play_id, event, time) VALUES(139, 'Built MC', 13676.0); +INSERT INTO events (play_id, event, time) VALUES(139, 'Built PR', 47046); +INSERT INTO events (play_id, event, time) VALUES(139, 'Went Public', 73449); +INSERT INTO events (play_id, event, time) VALUES(140, 'Built DC', 15041.2); +INSERT INTO events (play_id, event, time) VALUES(140, 'Built MC', 31081.6); +INSERT INTO events (play_id, event, time) VALUES(140, 'Built PR', 89250); +INSERT INTO events (play_id, event, time) VALUES(140, 'Went Public', 150365); +INSERT INTO events (play_id, event, time) VALUES(141, 'Built DC', 3668.0); +INSERT INTO events (play_id, event, time) VALUES(141, 'Built MC', 11077.6); +INSERT INTO events (play_id, event, time) VALUES(141, 'Built PR', 62996); +INSERT INTO events (play_id, event, time) VALUES(141, 'Went Public', 80737); +INSERT INTO events (play_id, event, time) VALUES(142, 'Built DC', 12430.0); +INSERT INTO events (play_id, event, time) VALUES(142, 'Built MC', 24939.2); +INSERT INTO events (play_id, event, time) VALUES(142, 'Built PR', 141490); +INSERT INTO events (play_id, event, time) VALUES(142, 'Went Public', 155175); +INSERT INTO events (play_id, event, time) VALUES(143, 'Built DC', 9534.4); +INSERT INTO events (play_id, event, time) VALUES(143, 'Built MC', 13753.2); +INSERT INTO events (play_id, event, time) VALUES(143, 'Built PR', 64029); +INSERT INTO events (play_id, event, time) VALUES(143, 'Went Public', 63068); +INSERT INTO events (play_id, event, time) VALUES(144, 'Built DC', 24599.2); +INSERT INTO events (play_id, event, time) VALUES(144, 'Built MC', 41408.4); +INSERT INTO events (play_id, event, time) VALUES(144, 'Built PR', 42289); +INSERT INTO events (play_id, event, time) VALUES(144, 'Went Public', 81363); +INSERT INTO events (play_id, event, time) VALUES(145, 'Built DC', 3569.6); +INSERT INTO events (play_id, event, time) VALUES(145, 'Built MC', 18510.8); +INSERT INTO events (play_id, event, time) VALUES(145, 'Built PR', 47180); +INSERT INTO events (play_id, event, time) VALUES(145, 'Went Public', 75391); +INSERT INTO events (play_id, event, time) VALUES(146, 'Built DC', 4096.0); +INSERT INTO events (play_id, event, time) VALUES(146, 'Built MC', 23702.0); +INSERT INTO events (play_id, event, time) VALUES(146, 'Built PR', 92539); +INSERT INTO events (play_id, event, time) VALUES(146, 'Went Public', 106854); +INSERT INTO events (play_id, event, time) VALUES(147, 'Built DC', 3967.6); +INSERT INTO events (play_id, event, time) VALUES(147, 'Built MC', 17510.4); +INSERT INTO events (play_id, event, time) VALUES(147, 'Built PR', 47824); +INSERT INTO events (play_id, event, time) VALUES(147, 'Went Public', 82684); +INSERT INTO events (play_id, event, time) VALUES(148, 'Built DC', 10377.6); +INSERT INTO events (play_id, event, time) VALUES(148, 'Built MC', 15397.2); +INSERT INTO events (play_id, event, time) VALUES(148, 'Built PR', 48256); +INSERT INTO events (play_id, event, time) VALUES(148, 'Went Public', 67827); +INSERT INTO events (play_id, event, time) VALUES(149, 'Built DC', 9543.2); +INSERT INTO events (play_id, event, time) VALUES(149, 'Built MC', 10179.6); +INSERT INTO events (play_id, event, time) VALUES(149, 'Built PR', 43212); +INSERT INTO events (play_id, event, time) VALUES(149, 'Went Public', 76488); +INSERT INTO events (play_id, event, time) VALUES(150, 'Built DC', 1364.4); +INSERT INTO events (play_id, event, time) VALUES(150, 'Built MC', 36535); +INSERT INTO events (play_id, event, time) VALUES(150, 'Built PR', 24700.0); +INSERT INTO events (play_id, event, time) VALUES(150, 'Went Public', 67587); +INSERT INTO events (play_id, event, time) VALUES(151, 'Built DC', 3486.0); +INSERT INTO events (play_id, event, time) VALUES(151, 'Built MC', 23734); +INSERT INTO events (play_id, event, time) VALUES(151, 'Built PR', 17100.8); +INSERT INTO events (play_id, event, time) VALUES(151, 'Went Public', 65656); +INSERT INTO events (play_id, event, time) VALUES(152, 'Built DC', 5911.6); +INSERT INTO events (play_id, event, time) VALUES(152, 'Built MC', 40794); +INSERT INTO events (play_id, event, time) VALUES(152, 'Built PR', 22649.6); +INSERT INTO events (play_id, event, time) VALUES(152, 'Went Public', 70905); +INSERT INTO events (play_id, event, time) VALUES(153, 'Built DC', 6715.2); +INSERT INTO events (play_id, event, time) VALUES(153, 'Built MC', 29519); +INSERT INTO events (play_id, event, time) VALUES(153, 'Built PR', 17157.6); +INSERT INTO events (play_id, event, time) VALUES(153, 'Went Public', 70787); +INSERT INTO events (play_id, event, time) VALUES(154, 'Built DC', 5984.4); +INSERT INTO events (play_id, event, time) VALUES(154, 'Built MC', 24116); +INSERT INTO events (play_id, event, time) VALUES(154, 'Built PR', 24288.4); +INSERT INTO events (play_id, event, time) VALUES(154, 'Went Public', 81959); +INSERT INTO events (play_id, event, time) VALUES(155, 'Built DC', 2257.2); +INSERT INTO events (play_id, event, time) VALUES(155, 'Built MC', 24354); +INSERT INTO events (play_id, event, time) VALUES(155, 'Built PR', 26106.0); +INSERT INTO events (play_id, event, time) VALUES(155, 'Went Public', 84022); +INSERT INTO events (play_id, event, time) VALUES(156, 'Built DC', 3713.6); +INSERT INTO events (play_id, event, time) VALUES(156, 'Built MC', 60883); +INSERT INTO events (play_id, event, time) VALUES(156, 'Built PR', 39128.0); +INSERT INTO events (play_id, event, time) VALUES(156, 'Went Public', 133908); +INSERT INTO events (play_id, event, time) VALUES(157, 'Built DC', 1447.2); +INSERT INTO events (play_id, event, time) VALUES(157, 'Built MC', 28992); +INSERT INTO events (play_id, event, time) VALUES(157, 'Built PR', 23886.0); +INSERT INTO events (play_id, event, time) VALUES(157, 'Went Public', 83128); +INSERT INTO events (play_id, event, time) VALUES(158, 'Built DC', 17910.8); +INSERT INTO events (play_id, event, time) VALUES(158, 'Built MC', 48989); +INSERT INTO events (play_id, event, time) VALUES(158, 'Built PR', 51268.4); +INSERT INTO events (play_id, event, time) VALUES(158, 'Went Public', 143905); +INSERT INTO events (play_id, event, time) VALUES(159, 'Built DC', 1873.2); +INSERT INTO events (play_id, event, time) VALUES(159, 'Built MC', 29864); +INSERT INTO events (play_id, event, time) VALUES(159, 'Built PR', 23347.2); +INSERT INTO events (play_id, event, time) VALUES(159, 'Went Public', 80618); +INSERT INTO events (play_id, event, time) VALUES(160, 'Built DC', 14868.8); +INSERT INTO events (play_id, event, time) VALUES(160, 'Built MC', 82945); +INSERT INTO events (play_id, event, time) VALUES(160, 'Built PR', 41804.0); +INSERT INTO events (play_id, event, time) VALUES(160, 'Went Public', 90217); +INSERT INTO events (play_id, event, time) VALUES(161, 'Built DC', 17316.0); +INSERT INTO events (play_id, event, time) VALUES(161, 'Built MC', 47828); +INSERT INTO events (play_id, event, time) VALUES(161, 'Built PR', 53216.4); +INSERT INTO events (play_id, event, time) VALUES(161, 'Went Public', 159267); +INSERT INTO events (play_id, event, time) VALUES(162, 'Built DC', 31951.2); +INSERT INTO events (play_id, event, time) VALUES(162, 'Built MC', 118436); +INSERT INTO events (play_id, event, time) VALUES(162, 'Built PR', 35182.0); +INSERT INTO events (play_id, event, time) VALUES(162, 'Went Public', 160822); +INSERT INTO events (play_id, event, time) VALUES(163, 'Built DC', 10397.6); +INSERT INTO events (play_id, event, time) VALUES(163, 'Built MC', 115795); +INSERT INTO events (play_id, event, time) VALUES(163, 'Built PR', 31788.4); +INSERT INTO events (play_id, event, time) VALUES(163, 'Went Public', 96880); +INSERT INTO events (play_id, event, time) VALUES(164, 'Built DC', 9793.6); +INSERT INTO events (play_id, event, time) VALUES(164, 'Built MC', 37388); +INSERT INTO events (play_id, event, time) VALUES(164, 'Built PR', 17343.6); +INSERT INTO events (play_id, event, time) VALUES(164, 'Went Public', 81749); +INSERT INTO events (play_id, event, time) VALUES(165, 'Built DC', 14774.8); +INSERT INTO events (play_id, event, time) VALUES(165, 'Built MC', 118510); +INSERT INTO events (play_id, event, time) VALUES(165, 'Built PR', 23864.4); +INSERT INTO events (play_id, event, time) VALUES(165, 'Went Public', 130400); +INSERT INTO events (play_id, event, time) VALUES(166, 'Built DC', 10882.4); +INSERT INTO events (play_id, event, time) VALUES(166, 'Built MC', 23391); +INSERT INTO events (play_id, event, time) VALUES(166, 'Built PR', 24029.6); +INSERT INTO events (play_id, event, time) VALUES(166, 'Went Public', 154740); +INSERT INTO events (play_id, event, time) VALUES(167, 'Built DC', 29909.6); +INSERT INTO events (play_id, event, time) VALUES(167, 'Built MC', 47937); +INSERT INTO events (play_id, event, time) VALUES(167, 'Built PR', 29750.0); +INSERT INTO events (play_id, event, time) VALUES(167, 'Went Public', 73050); +INSERT INTO events (play_id, event, time) VALUES(168, 'Built DC', 35465.6); +INSERT INTO events (play_id, event, time) VALUES(168, 'Built MC', 114168); +INSERT INTO events (play_id, event, time) VALUES(168, 'Built PR', 41004.8); +INSERT INTO events (play_id, event, time) VALUES(168, 'Went Public', 147224); +INSERT INTO events (play_id, event, time) VALUES(169, 'Built DC', 17876.0); +INSERT INTO events (play_id, event, time) VALUES(169, 'Built MC', 112134); +INSERT INTO events (play_id, event, time) VALUES(169, 'Built PR', 22976.8); +INSERT INTO events (play_id, event, time) VALUES(169, 'Went Public', 82759); +INSERT INTO events (play_id, event, time) VALUES(170, 'Built DC', 3159.6); +INSERT INTO events (play_id, event, time) VALUES(170, 'Built MC', 118154); +INSERT INTO events (play_id, event, time) VALUES(170, 'Built PR', 16997.6); +INSERT INTO events (play_id, event, time) VALUES(170, 'Went Public', 119049); +INSERT INTO events (play_id, event, time) VALUES(171, 'Built DC', 19790.4); +INSERT INTO events (play_id, event, time) VALUES(171, 'Built MC', 68652); +INSERT INTO events (play_id, event, time) VALUES(171, 'Built PR', 36619.6); +INSERT INTO events (play_id, event, time) VALUES(171, 'Went Public', 67665); +INSERT INTO events (play_id, event, time) VALUES(172, 'Built DC', 24780.8); +INSERT INTO events (play_id, event, time) VALUES(172, 'Built MC', 121215); +INSERT INTO events (play_id, event, time) VALUES(172, 'Built PR', 21998.0); +INSERT INTO events (play_id, event, time) VALUES(172, 'Went Public', 109427); +INSERT INTO events (play_id, event, time) VALUES(173, 'Built DC', 9273.6); +INSERT INTO events (play_id, event, time) VALUES(173, 'Built MC', 22939); +INSERT INTO events (play_id, event, time) VALUES(173, 'Built PR', 16986.4); +INSERT INTO events (play_id, event, time) VALUES(173, 'Went Public', 64919); +INSERT INTO events (play_id, event, time) VALUES(174, 'Built DC', 9885.6); +INSERT INTO events (play_id, event, time) VALUES(174, 'Built MC', 97608); +INSERT INTO events (play_id, event, time) VALUES(174, 'Built PR', 29482.0); +INSERT INTO events (play_id, event, time) VALUES(174, 'Went Public', 72668); +INSERT INTO events (play_id, event, time) VALUES(175, 'Built DC', 6615.6); +INSERT INTO events (play_id, event, time) VALUES(175, 'Built MC', 71280); +INSERT INTO events (play_id, event, time) VALUES(175, 'Built PR', 22275.6); +INSERT INTO events (play_id, event, time) VALUES(175, 'Went Public', 74653); +INSERT INTO events (play_id, event, time) VALUES(176, 'Built DC', 12049.6); +INSERT INTO events (play_id, event, time) VALUES(176, 'Built MC', 36322); +INSERT INTO events (play_id, event, time) VALUES(176, 'Built PR', 50867.6); +INSERT INTO events (play_id, event, time) VALUES(176, 'Went Public', 108168); +INSERT INTO events (play_id, event, time) VALUES(177, 'Built DC', 8666.4); +INSERT INTO events (play_id, event, time) VALUES(177, 'Built MC', 29219); +INSERT INTO events (play_id, event, time) VALUES(177, 'Built PR', 24136.4); +INSERT INTO events (play_id, event, time) VALUES(177, 'Went Public', 69109); +INSERT INTO events (play_id, event, time) VALUES(178, 'Built DC', 10208.0); +INSERT INTO events (play_id, event, time) VALUES(178, 'Built MC', 46956); +INSERT INTO events (play_id, event, time) VALUES(178, 'Built PR', 21644.0); +INSERT INTO events (play_id, event, time) VALUES(178, 'Went Public', 75576); +INSERT INTO events (play_id, event, time) VALUES(179, 'Built DC', 16360.8); +INSERT INTO events (play_id, event, time) VALUES(179, 'Built MC', 111553); +INSERT INTO events (play_id, event, time) VALUES(179, 'Built PR', 44081.6); +INSERT INTO events (play_id, event, time) VALUES(179, 'Went Public', 152294); +INSERT INTO events (play_id, event, time) VALUES(180, 'Built DC', 4778.4); +INSERT INTO events (play_id, event, time) VALUES(180, 'Built MC', 18090.8); +INSERT INTO events (play_id, event, time) VALUES(180, 'Built PR', 56768); +INSERT INTO events (play_id, event, time) VALUES(180, 'Went Public', 33680.4); +INSERT INTO events (play_id, event, time) VALUES(181, 'Built DC', 11670.4); +INSERT INTO events (play_id, event, time) VALUES(181, 'Built MC', 41780.0); +INSERT INTO events (play_id, event, time) VALUES(181, 'Built PR', 79372); +INSERT INTO events (play_id, event, time) VALUES(181, 'Went Public', 64432.4); +INSERT INTO events (play_id, event, time) VALUES(182, 'Built DC', 7630.4); +INSERT INTO events (play_id, event, time) VALUES(182, 'Built MC', 13393.2); +INSERT INTO events (play_id, event, time) VALUES(182, 'Built PR', 59554); +INSERT INTO events (play_id, event, time) VALUES(182, 'Went Public', 29142.0); +INSERT INTO events (play_id, event, time) VALUES(183, 'Built DC', 8486.4); +INSERT INTO events (play_id, event, time) VALUES(183, 'Built MC', 11978.8); +INSERT INTO events (play_id, event, time) VALUES(183, 'Built PR', 64332); +INSERT INTO events (play_id, event, time) VALUES(183, 'Went Public', 30353.2); +INSERT INTO events (play_id, event, time) VALUES(184, 'Built DC', 30376.8); +INSERT INTO events (play_id, event, time) VALUES(184, 'Built MC', 28673.2); +INSERT INTO events (play_id, event, time) VALUES(184, 'Built PR', 62574); +INSERT INTO events (play_id, event, time) VALUES(184, 'Went Public', 44317.6); +INSERT INTO events (play_id, event, time) VALUES(185, 'Built DC', 10530.8); +INSERT INTO events (play_id, event, time) VALUES(185, 'Built MC', 9326.0); +INSERT INTO events (play_id, event, time) VALUES(185, 'Built PR', 66677); +INSERT INTO events (play_id, event, time) VALUES(185, 'Went Public', 29335.6); +INSERT INTO events (play_id, event, time) VALUES(186, 'Built DC', 4395.2); +INSERT INTO events (play_id, event, time) VALUES(186, 'Built MC', 28881.2); +INSERT INTO events (play_id, event, time) VALUES(186, 'Built PR', 122141); +INSERT INTO events (play_id, event, time) VALUES(186, 'Went Public', 58744.4); +INSERT INTO events (play_id, event, time) VALUES(187, 'Built DC', 5127.6); +INSERT INTO events (play_id, event, time) VALUES(187, 'Built MC', 13626.0); +INSERT INTO events (play_id, event, time) VALUES(187, 'Built PR', 52879); +INSERT INTO events (play_id, event, time) VALUES(187, 'Went Public', 29191.6); +INSERT INTO events (play_id, event, time) VALUES(188, 'Built DC', 5159.2); +INSERT INTO events (play_id, event, time) VALUES(188, 'Built MC', 12898.8); +INSERT INTO events (play_id, event, time) VALUES(188, 'Built PR', 64698); +INSERT INTO events (play_id, event, time) VALUES(188, 'Went Public', 26516.4); +INSERT INTO events (play_id, event, time) VALUES(189, 'Built DC', 10787.6); +INSERT INTO events (play_id, event, time) VALUES(189, 'Built MC', 9170.0); +INSERT INTO events (play_id, event, time) VALUES(189, 'Built PR', 60505); +INSERT INTO events (play_id, event, time) VALUES(189, 'Went Public', 27637.6); +INSERT INTO events (play_id, event, time) VALUES(190, 'Built DC', 7393.2); +INSERT INTO events (play_id, event, time) VALUES(190, 'Built MC', 12667.2); +INSERT INTO events (play_id, event, time) VALUES(190, 'Built PR', 60141); +INSERT INTO events (play_id, event, time) VALUES(190, 'Went Public', 31816.0); +INSERT INTO events (play_id, event, time) VALUES(191, 'Built DC', 37024.8); +INSERT INTO events (play_id, event, time) VALUES(191, 'Built MC', 35758.0); +INSERT INTO events (play_id, event, time) VALUES(191, 'Built PR', 104065); +INSERT INTO events (play_id, event, time) VALUES(191, 'Went Public', 53449.6); +INSERT INTO events (play_id, event, time) VALUES(192, 'Built DC', 6434.8); +INSERT INTO events (play_id, event, time) VALUES(192, 'Built MC', 9144.8); +INSERT INTO events (play_id, event, time) VALUES(192, 'Built PR', 59979); +INSERT INTO events (play_id, event, time) VALUES(192, 'Went Public', 25647.2); +INSERT INTO events (play_id, event, time) VALUES(193, 'Built DC', 7921.6); +INSERT INTO events (play_id, event, time) VALUES(193, 'Built MC', 11963.6); +INSERT INTO events (play_id, event, time) VALUES(193, 'Built PR', 58889); +INSERT INTO events (play_id, event, time) VALUES(193, 'Went Public', 27721.2); +INSERT INTO events (play_id, event, time) VALUES(194, 'Built DC', 2266.0); +INSERT INTO events (play_id, event, time) VALUES(194, 'Built MC', 16512.0); +INSERT INTO events (play_id, event, time) VALUES(194, 'Built PR', 57222); +INSERT INTO events (play_id, event, time) VALUES(194, 'Went Public', 32694.8); +INSERT INTO events (play_id, event, time) VALUES(195, 'Built DC', 3148.0); +INSERT INTO events (play_id, event, time) VALUES(195, 'Built MC', 16871.6); +INSERT INTO events (play_id, event, time) VALUES(195, 'Built PR', 57669); +INSERT INTO events (play_id, event, time) VALUES(195, 'Went Public', 31602.4); +INSERT INTO events (play_id, event, time) VALUES(196, 'Built DC', 26448.0); +INSERT INTO events (play_id, event, time) VALUES(196, 'Built MC', 18916.0); +INSERT INTO events (play_id, event, time) VALUES(196, 'Built PR', 82735); +INSERT INTO events (play_id, event, time) VALUES(196, 'Went Public', 63254.0); +INSERT INTO events (play_id, event, time) VALUES(197, 'Built DC', 6367.6); +INSERT INTO events (play_id, event, time) VALUES(197, 'Built MC', 27337.6); +INSERT INTO events (play_id, event, time) VALUES(197, 'Built PR', 141092); +INSERT INTO events (play_id, event, time) VALUES(197, 'Went Public', 58848.4); +INSERT INTO events (play_id, event, time) VALUES(198, 'Built DC', 4446.4); +INSERT INTO events (play_id, event, time) VALUES(198, 'Built MC', 16471.2); +INSERT INTO events (play_id, event, time) VALUES(198, 'Built PR', 52566); +INSERT INTO events (play_id, event, time) VALUES(198, 'Went Public', 31711.2); +INSERT INTO events (play_id, event, time) VALUES(199, 'Built DC', 13584.8); +INSERT INTO events (play_id, event, time) VALUES(199, 'Built MC', 8885.6); +INSERT INTO events (play_id, event, time) VALUES(199, 'Built PR', 49963); +INSERT INTO events (play_id, event, time) VALUES(199, 'Went Public', 28119.6); +INSERT INTO events (play_id, event, time) VALUES(200, 'Built DC', 7540.8); +INSERT INTO events (play_id, event, time) VALUES(200, 'Built MC', 13356.4); +INSERT INTO events (play_id, event, time) VALUES(200, 'Built PR', 52754); +INSERT INTO events (play_id, event, time) VALUES(200, 'Went Public', 29440.0); +INSERT INTO events (play_id, event, time) VALUES(201, 'Built DC', 25452.8); +INSERT INTO events (play_id, event, time) VALUES(201, 'Built MC', 31160.8); +INSERT INTO events (play_id, event, time) VALUES(201, 'Built PR', 42110); +INSERT INTO events (play_id, event, time) VALUES(201, 'Went Public', 60895.2); +INSERT INTO events (play_id, event, time) VALUES(202, 'Built DC', 1205.2); +INSERT INTO events (play_id, event, time) VALUES(202, 'Built MC', 13422.0); +INSERT INTO events (play_id, event, time) VALUES(202, 'Built PR', 57047); +INSERT INTO events (play_id, event, time) VALUES(202, 'Went Public', 25456.0); +INSERT INTO events (play_id, event, time) VALUES(203, 'Built DC', 34012.8); +INSERT INTO events (play_id, event, time) VALUES(203, 'Built MC', 35974.0); +INSERT INTO events (play_id, event, time) VALUES(203, 'Built PR', 101194); +INSERT INTO events (play_id, event, time) VALUES(203, 'Went Public', 26584.4); +INSERT INTO events (play_id, event, time) VALUES(204, 'Built DC', 6890.0); +INSERT INTO events (play_id, event, time) VALUES(204, 'Built MC', 12200.0); +INSERT INTO events (play_id, event, time) VALUES(204, 'Built PR', 42466); +INSERT INTO events (play_id, event, time) VALUES(204, 'Went Public', 26640.8); +INSERT INTO events (play_id, event, time) VALUES(205, 'Built DC', 4488.4); +INSERT INTO events (play_id, event, time) VALUES(205, 'Built MC', 24311.2); +INSERT INTO events (play_id, event, time) VALUES(205, 'Built PR', 90660); +INSERT INTO events (play_id, event, time) VALUES(205, 'Went Public', 46427.2); +INSERT INTO events (play_id, event, time) VALUES(206, 'Built DC', 2852.0); +INSERT INTO events (play_id, event, time) VALUES(206, 'Built MC', 9666.8); +INSERT INTO events (play_id, event, time) VALUES(206, 'Built PR', 46156); +INSERT INTO events (play_id, event, time) VALUES(206, 'Went Public', 26983.6); +INSERT INTO events (play_id, event, time) VALUES(207, 'Built DC', 14419.6); +INSERT INTO events (play_id, event, time) VALUES(207, 'Built MC', 45225.2); +INSERT INTO events (play_id, event, time) VALUES(207, 'Built PR', 54907); +INSERT INTO events (play_id, event, time) VALUES(207, 'Went Public', 44546.0); +INSERT INTO events (play_id, event, time) VALUES(208, 'Built DC', 10586.0); +INSERT INTO events (play_id, event, time) VALUES(208, 'Built MC', 17317.2); +INSERT INTO events (play_id, event, time) VALUES(208, 'Built PR', 58131); +INSERT INTO events (play_id, event, time) VALUES(208, 'Went Public', 27313.6); +INSERT INTO events (play_id, event, time) VALUES(209, 'Built DC', 10318.8); +INSERT INTO events (play_id, event, time) VALUES(209, 'Built MC', 12520.4); +INSERT INTO events (play_id, event, time) VALUES(209, 'Built PR', 63398); +INSERT INTO events (play_id, event, time) VALUES(209, 'Went Public', 28464.4); diff --git a/chapter03/player_bar_charts.rb b/chapter03/player_bar_charts.rb new file mode 100644 index 0000000..45ce3d7 --- /dev/null +++ b/chapter03/player_bar_charts.rb @@ -0,0 +1,78 @@ +require 'gruff' +require 'active_record' + +game_id_to_analyze = 5 + +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :username => 'root', # This is the default username and password + :password => '', # for MySQL, but note that if you have a + # different username and password, + # you should change it. + :database => 'players_4') + + +class Player < ActiveRecord::Base + has_many :plays +end + +class Game < ActiveRecord::Base + has_many :plays +end + +class Play < ActiveRecord::Base + belongs_to :game + belongs_to :player +end + +class Event < ActiveRecord::Base + belongs_to :plays +end + +columns = Event.find(:all, :group=>'event DESC') +pic_dir='./player_graph_pics' #Used to store the graph pictures. +Dir.mkdir(pic_dir) unless File.exists?(pic_dir) + +Player.find(:all).each do |player| + bar_chart = Gruff::Bar.new(1024) + bar_chart.legend_font_size = 12 + total_games = Play.count(:conditions=>['game_id = ? ' << + 'AND player_id = ?', + + game_id_to_analyze, + player.id]).to_f + + total_wins = Play.count(:conditions=>['game_id = ? ' << + 'AND player_id = ? ' << + 'AND won=1', + + game_id_to_analyze, + player.id]).to_f + + win_ratio = (total_wins / total_games * 100).to_i unless total_games == 0 + win_ratio ||= 0 + bar_chart.title = "#{player.name} " << + "(#{win_ratio}% won)" + bar_chart.minimum_value = 0 + bar_chart.maximum_value = 110 + + sql = "SELECT event, AVG(time) as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='#{game_id_to_analyze}' + AND + p.player_id='#{player.id}' + GROUP + BY e.event DESC;" + data = [] + Event.find_by_sql(sql).each do |row| + bar_chart.data row.event, (row.average_time.to_i/1000) + end + bar_chart.labels = {0=>'Time'} + bar_chart.write("#{pic_dir}/player_#{player.id}.png") +end + diff --git a/chapter03/widget_chart_scruffy.rb b/chapter03/widget_chart_scruffy.rb new file mode 100644 index 0000000..cfbb0ba --- /dev/null +++ b/chapter03/widget_chart_scruffy.rb @@ -0,0 +1,23 @@ +require 'scruffy' + +sprocket_output = {"Jan"=>500, + "Feb"=>750, + "Apr"=>380} + +widget_output = {"Jan"=>350, + "Feb"=>650, + "Apr"=>560} + +graph = Scruffy::Graph.new( + :title => "Widget and Sprocket Output", + :theme => Scruffy::Themes::Keynote.new) + +graph.add(:bar, 'Sprockets', sprocket_output.values) +graph.add(:line, 'Widgets', widget_output.values) + +graph.point_markers = widget_output.keys + +graph.render( :width => 800, + :as=>'PNG', + :to => "widgets_and_sprockets.png") + diff --git a/chapter04/desktop_team_performance_graph.rb b/chapter04/desktop_team_performance_graph.rb new file mode 100644 index 0000000..6a27b9c --- /dev/null +++ b/chapter04/desktop_team_performance_graph.rb @@ -0,0 +1,138 @@ +require 'fox16' +require 'active_record' +require 'optparse' +require 'rubygems' +require 'gruff' +require 'active_record' + +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :username => 'insert_your_mysql_username_here', + :password => 'insert_your_mysql_password_here', + :database => 'players_4') + +class Player < ActiveRecord::Base + has_many :wins +end +class Game < ActiveRecord::Base + has_many :wins +end +class Play < ActiveRecord::Base + belongs_to :game + belongs_to :player +end +class Event < ActiveRecord::Base + belongs_to :play +end + + +include Fox + +class ParagonGraphWindow + def initialize + + @main_window=FXMainWindow.new(get_app, + "Paragon Studios Player Reporting Software", + nil, nil, DECOR_ALL ) + @main_window.width=640; @main_window.height=480 + + control_matrix=FXMatrix.new(@main_window,4, MATRIX_BY_COLUMNS) + + FXLabel.new(control_matrix, 'Game:') + @game_combobox = FXComboBox.new(control_matrix, 30, nil, + COMBOBOX_STATIC | FRAME_SUNKEN ) + @game_combobox.numVisible = 5 + @game_combobox.editable = false + + Game.find(:all).each do |game| + @game_combobox.appendItem(game.name , game.id) + end + @game_combobox.connect(SEL_COMMAND) do + update_display + end + + FXLabel.new(control_matrix, 'Player:') + + @player_combobox = FXComboBox.new(control_matrix, 35, nil, + COMBOBOX_STATIC | FRAME_SUNKEN ) + @player_combobox.numVisible = 5 + @player_combobox.editable = false + + Player.find(:all).each do |player| + @player_combobox.appendItem(player.name , player.id) + end + + @player_combobox.connect(SEL_COMMAND) do + update_display + end + + @graph_picture_viewer = FXImageView.new(@main_window , nil, nil, 0, + LAYOUT_FILL_X | LAYOUT_FILL_Y) + + @graph_picture_viewer.connect( SEL_CONFIGURE ) do + update_display + end + + @main_window.show( PLACEMENT_SCREEN ) + end + def update_display + game_id_to_analyze = @game_combobox.getItemData(@game_combobox.currentItem) + player = Player.find(@player_combobox.getItemData( + @player_combobox.currentItem)) + bar_chart = Gruff::Bar.new("#{@graph_picture_viewer.width}x" << + "#{@graph_picture_viewer.height}") + bar_chart.legend_font_size = 12 + total_games = Play.count(:conditions=>['game_id = ? AND ' << + 'player_id = ?', + game_id_to_analyze, player.id] + ).to_f || 0 + total_wins = Play.count(:conditions=>['game_id = ? AND ' << + 'player_id = ? AND won=1', + game_id_to_analyze, player.id] + ).to_f || 0 + + bar_chart.title = "#{player.name} (#{'%i' % (total_games==0 ? '0' : (total_wins/total_games * 100))}% won)" + bar_chart.minimum_value = 0 + bar_chart.maximum_value = 110 + + sql = "SELECT event, AVG(time) as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='#{game_id_to_analyze}' + AND + p.player_id='#{player.id}' + GROUP BY e.event DESC;" + data = [] + Event.find_by_sql(sql).each do |row| + bar_chart.data row.event, (row.average_time.to_i/1000) + end + bar_chart.labels = {0=>'Time'} + chart_png_filename = "./player_#{player.id}.png" + bar_chart.write(chart_png_filename) + + pic = FXPNGImage.new(FXApp.instance()) + + FXFileStream.open(chart_png_filename, + FXStreamLoad) { |stream| pic.loadPixels(stream) } + + pic.create + @graph_picture_viewer.image = pic + File.unlink(chart_png_filename) + + end + +end + +fox_application=FXApp.new + +ParagonGraphWindow.new + +FXApp.instance().create # Note that getApp returns the same FXApp instance + # as fox_application references. + +FXApp.instance().run + diff --git a/chapter04/simple_fx_ruby_example.rb b/chapter04/simple_fx_ruby_example.rb new file mode 100644 index 0000000..82d9248 --- /dev/null +++ b/chapter04/simple_fx_ruby_example.rb @@ -0,0 +1,25 @@ +require 'fox16' + +include Fox + +myApp = FXApp.new + +mainWindow=FXMainWindow.new(myApp, "Simple FXRuby Control Demo", + :padding =>10, :vSpacing=>10) + +my_first_button= FXButton.new(mainWindow, 'Example Button Control') +my_first_button.connect(SEL_COMMAND) do + my_first_button.text="In a real-life situation, this would do something." +end + +FXTextField.new(mainWindow, 30).text = 'Example Text Control' + +FXRadioButton.new(mainWindow, "Example Radio Control") +FXCheckButton.new(mainWindow, "Example Check Control") + +myApp.create + +mainWindow.show( PLACEMENT_SCREEN ) + +myApp.run + diff --git a/chapter04/spreadsheet_hello_world.rb b/chapter04/spreadsheet_hello_world.rb new file mode 100644 index 0000000..ae28a8e --- /dev/null +++ b/chapter04/spreadsheet_hello_world.rb @@ -0,0 +1,9 @@ +require "spreadsheet/excel" +include Spreadsheet + +workbook = Excel.new("test.xls") +worksheet = workbook.add_worksheet + +worksheet.write(0, 0, 'Hello, world!') +workbook.close + diff --git a/chapter04/spreadsheet_team_performance.rb b/chapter04/spreadsheet_team_performance.rb new file mode 100644 index 0000000..0ca84eb --- /dev/null +++ b/chapter04/spreadsheet_team_performance.rb @@ -0,0 +1,84 @@ +require 'active_record' +require 'optparse' +require 'rubygems' +require 'active_record' + +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :username => 'insert_your_mysql_username_here', + :password => 'insert_your_mysql_password_here', + :database => 'players_4') + +class Player < ActiveRecord::Base + has_many :wins +end +class Game < ActiveRecord::Base + has_many :wins +end +class Play < ActiveRecord::Base + belongs_to :game + belongs_to :player +end + +require 'spreadsheet/excel' +include Spreadsheet +spreadsheet_file = "spreadsheet_report.xls" +workbook = Excel.new(spreadsheet_file) +worksheet = workbook.add_worksheet + + +page_header_format = Format.new(:color=>'black', :bold=>true, :size=>30) +player_name_format = Format.new(:color=>'black', :bold=>true) +header_format = Format.new(:color=>'gray', :bold=>true) +data_format = Format.new() + +workbook.add_format(page_header_format) +workbook.add_format(player_name_format) +workbook.add_format(header_format) +workbook.add_format(data_format) + +worksheet.format_column(0, 35, data_format) + +current_row=0 + +worksheet.write(current_row, 0, 'Player Win/Loss Report', page_header_format) + +current_row=current_row+1 + +Player.find(:all).each do |player| + + worksheet.format_row(current_row, current_row==1 ? 20 : 33, player_name_format) + worksheet.write(current_row, 0, player.name) + current_row=current_row+1 + + worksheet.write(current_row, 0, ['Game', 'Wins', 'Losses'], header_format) + current_row=current_row+1 + + + Game.find(:all).each do |game| + + win_count = Play.count(:conditions=>[ + "player_id = ? AND + game_id= ? AND + won=true", + + player.id, + game.id]) + + loss_count = Play.count(:conditions=>[ + "player_id = ? AND + game_id= ? AND + won=false", + + player.id, + game.id]) + + worksheet.write(current_row, 0, [game.name, win_count, loss_count]) + current_row=current_row+1 + end + +end + +workbook.close + diff --git a/chapter05/actor_schedule/Rakefile b/chapter05/actor_schedule/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/chapter05/actor_schedule/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/chapter05/actor_schedule/app/controllers/application.rb b/chapter05/actor_schedule/app/controllers/application.rb new file mode 100644 index 0000000..28031d7 --- /dev/null +++ b/chapter05/actor_schedule/app/controllers/application.rb @@ -0,0 +1,7 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + # Pick a unique cookie name to distinguish our session data from others' + session :session_key => '_actor_schedule_session_id' +end diff --git a/chapter05/actor_schedule/app/controllers/homepage_controller.rb b/chapter05/actor_schedule/app/controllers/homepage_controller.rb new file mode 100644 index 0000000..4b220b3 --- /dev/null +++ b/chapter05/actor_schedule/app/controllers/homepage_controller.rb @@ -0,0 +1,10 @@ +class HomepageController < ApplicationController + def index + @actors_today = [] + @actors_tommorow = [] + Actor.find(:all).each do |actor| + @actors_today << {:actor=>actor, :bookings=>actor.booking.find(:all, :conditions=>['TO_DAYS(booked_at)=TO_DAYS(NOW())'])} + @actors_tommorow << {:actor=>actor, :bookings=>actor.booking.find(:all, :conditions=>['TO_DAYS(booked_at)=TO_DAYS(NOW())+1'])} + end + end +end diff --git a/chapter05/actor_schedule/app/helpers/application_helper.rb b/chapter05/actor_schedule/app/helpers/application_helper.rb new file mode 100644 index 0000000..22a7940 --- /dev/null +++ b/chapter05/actor_schedule/app/helpers/application_helper.rb @@ -0,0 +1,3 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper +end diff --git a/chapter05/actor_schedule/app/helpers/homepage_helper.rb b/chapter05/actor_schedule/app/helpers/homepage_helper.rb new file mode 100644 index 0000000..c5bbfe5 --- /dev/null +++ b/chapter05/actor_schedule/app/helpers/homepage_helper.rb @@ -0,0 +1,2 @@ +module HomepageHelper +end diff --git a/chapter05/actor_schedule/app/models/actor.rb b/chapter05/actor_schedule/app/models/actor.rb new file mode 100644 index 0000000..86d84fc --- /dev/null +++ b/chapter05/actor_schedule/app/models/actor.rb @@ -0,0 +1,3 @@ +class Actor < ActiveRecord::Base + has_many :booking +end diff --git a/chapter05/actor_schedule/app/models/booking.rb b/chapter05/actor_schedule/app/models/booking.rb new file mode 100644 index 0000000..6d706a2 --- /dev/null +++ b/chapter05/actor_schedule/app/models/booking.rb @@ -0,0 +1,5 @@ +class Booking < ActiveRecord::Base + belongs_to :actor + belongs_to :project + belongs_to :room +end diff --git a/chapter05/actor_schedule/app/models/project.rb b/chapter05/actor_schedule/app/models/project.rb new file mode 100644 index 0000000..5bc7957 --- /dev/null +++ b/chapter05/actor_schedule/app/models/project.rb @@ -0,0 +1,2 @@ +class Project < ActiveRecord::Base +end diff --git a/chapter05/actor_schedule/app/models/room.rb b/chapter05/actor_schedule/app/models/room.rb new file mode 100644 index 0000000..b9632b1 --- /dev/null +++ b/chapter05/actor_schedule/app/models/room.rb @@ -0,0 +1,2 @@ +class Room < ActiveRecord::Base +end diff --git a/chapter05/actor_schedule/app/views/homepage/index.rhtml b/chapter05/actor_schedule/app/views/homepage/index.rhtml new file mode 100644 index 0000000..b40c9fc --- /dev/null +++ b/chapter05/actor_schedule/app/views/homepage/index.rhtml @@ -0,0 +1,39 @@ + + +

Today's Schedule:

+ +<%@actors_today.each do |actor_today| + +%> +

<%=actor_today[:actor].name%>

+

<%=actor_today[:bookings].length>0 ? + actor_today[:bookings].map do |b| + "#{b.booked_at.strftime('%I:%m%p')}, " << + "#{b.room.name}, " << + "#{b.project.name}" end.join('
') : + 'Nothing for today!'%>

+ +<%end %> + + +

Tommorow's Schedule:

+ +<%@actors_tommorow.each do |actor_tommorow| + +%> +

<%=actor_tommorow[:actor].name%>

+

<%=actor_tommorow[:bookings].length>0 ? + actor_tommorow[:bookings].map do |b| + "#{b.booked_at.strftime('%I:%m%p')}, " << + "#{b.room.name}, " << + "#{b.project.name}" end.join('
') : + 'Nothing for tommorow!'%>

+ + +<%end %> + + diff --git a/chapter05/actor_schedule/app/views/layouts/application.rhtml b/chapter05/actor_schedule/app/views/layouts/application.rhtml new file mode 100644 index 0000000..6e1d1c0 --- /dev/null +++ b/chapter05/actor_schedule/app/views/layouts/application.rhtml @@ -0,0 +1,8 @@ + + + Actor Schedule Report + + + <%=@content_for_layout%> + + diff --git a/chapter05/actor_schedule/config/boot.rb b/chapter05/actor_schedule/config/boot.rb new file mode 100644 index 0000000..128fe76 --- /dev/null +++ b/chapter05/actor_schedule/config/boot.rb @@ -0,0 +1,45 @@ +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb + +unless defined?(RAILS_ROOT) + root_path = File.join(File.dirname(__FILE__), '..') + + unless RUBY_PLATFORM =~ /mswin32/ + require 'pathname' + root_path = Pathname.new(root_path).cleanpath(true).to_s + end + + RAILS_ROOT = root_path +end + +unless defined?(Rails::Initializer) + if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + else + require 'rubygems' + + environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join + environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/ + rails_gem_version = $1 + + if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version + # Asking for 1.1.6 will give you 1.1.6.5206, if available -- makes it easier to use beta gems + rails_gem = Gem.cache.search('rails', "~>#{version}.0").sort_by { |g| g.version.version }.last + + if rails_gem + require_gem "rails", "=#{rails_gem.version.version}" + require rails_gem.full_gem_path + '/lib/initializer' + else + STDERR.puts %(Cannot find gem for Rails ~>#{version}.0: + Install the missing gem with 'gem install -v=#{version} rails', or + change environment.rb to define RAILS_GEM_VERSION with your desired version. + ) + exit 1 + end + else + require_gem "rails" + require 'initializer' + end + end + + Rails::Initializer.run(:set_load_path) +end \ No newline at end of file diff --git a/chapter05/actor_schedule/config/database.yml b/chapter05/actor_schedule/config/database.yml new file mode 100644 index 0000000..cc5f748 --- /dev/null +++ b/chapter05/actor_schedule/config/database.yml @@ -0,0 +1,36 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install mysql +# On MacOS X: +# gem install mysql -- --include=/usr/local/lib +# On Windows: +# gem install mysql +# Choose the win32 build. +# Install MySQL and put its /bin directory on your path. +# +# 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: actor_schedule_development + username: root + password: + host: localhost + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: actor_schedule_test + username: root + password: + host: localhost + +production: + adapter: mysql + database: actor_schedule_production + username: root + password: + host: localhost diff --git a/chapter05/actor_schedule/config/environment.rb b/chapter05/actor_schedule/config/environment.rb new file mode 100644 index 0000000..bf470ee --- /dev/null +++ b/chapter05/actor_schedule/config/environment.rb @@ -0,0 +1,60 @@ +# Be sure to restart your web server when you modify this file. + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Specifies gem version of Rails to use when vendor/rails is not present +# RAILS_GEM_VERSION = '1.2.0' unless defined? RAILS_GEM_VERSION + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here + + # Skip frameworks you're not going to use (only works if using vendor/rails) + # config.frameworks -= [ :action_web_service, :action_mailer ] + + # Only load the plugins named here, by default all plugins in vendor/plugins are loaded + # config.plugins = %W( exception_notification ssl_requirement ) + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Use the database for sessions instead of the file system + # (create the session table with 'rake db:sessions:create') + # config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # See Rails::Configuration for more options +end + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register "application/x-mobile", :mobile + +# Include your application configuration below diff --git a/chapter05/actor_schedule/config/environments/development.rb b/chapter05/actor_schedule/config/environments/development.rb new file mode 100644 index 0000000..0589aa9 --- /dev/null +++ b/chapter05/actor_schedule/config/environments/development.rb @@ -0,0 +1,21 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false +config.action_view.debug_rjs = true + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false diff --git a/chapter05/actor_schedule/config/environments/production.rb b/chapter05/actor_schedule/config/environments/production.rb new file mode 100644 index 0000000..cb295b8 --- /dev/null +++ b/chapter05/actor_schedule/config/environments/production.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors, bad email addresses will be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/chapter05/actor_schedule/config/environments/test.rb b/chapter05/actor_schedule/config/environments/test.rb new file mode 100644 index 0000000..f0689b9 --- /dev/null +++ b/chapter05/actor_schedule/config/environments/test.rb @@ -0,0 +1,19 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Tell ActionMailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test \ No newline at end of file diff --git a/chapter05/actor_schedule/config/routes.rb b/chapter05/actor_schedule/config/routes.rb new file mode 100644 index 0000000..9bbc463 --- /dev/null +++ b/chapter05/actor_schedule/config/routes.rb @@ -0,0 +1,23 @@ +ActionController::Routing::Routes.draw do |map| + # The priority is based upon order of creation: first created -> highest priority. + + # Sample of regular route: + # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' + # This route can be invoked with purchase_url(:id => product.id) + + # You can have the root of your site routed by hooking up '' + # -- just remember to delete public/index.html. + # map.connect '', :controller => "welcome" + + # Allow downloading Web Service WSDL as a file with an extension + # instead of a file named 'wsdl' + map.connect ':controller/service.wsdl', :action => 'wsdl' + + # Install the default route as the lowest priority. + map.connect ':controller/:action/:id.:format' + map.connect ':controller/:action/:id' +end diff --git a/chapter05/actor_schedule/db/migrate/001_initial_schema.rb b/chapter05/actor_schedule/db/migrate/001_initial_schema.rb new file mode 100644 index 0000000..e934ecb --- /dev/null +++ b/chapter05/actor_schedule/db/migrate/001_initial_schema.rb @@ -0,0 +1,28 @@ +class InitialSchema < ActiveRecord::Migration + def self.up + create_table :actors do |t| + t.column :name, :text, :length=>45 + t.column :phone, :text, :length=>13 + end + create_table :projects do |t| + t.column :name, :text, :length=>25 + end + create_table :rooms do |t| + t.column :name, :text, :length=>25 + end + create_table :bookings do |t| + t.column :actor_id, :integer + t.column :room_id, :integer + t.column :project_id, :integer + t.column :booked_at, :datetime + end + end + + def self.down + drop_table :actors + drop_table :projects + drop_table :rooms + drop_table :bookings + end +end + diff --git a/chapter05/actor_schedule/db/schema.rb b/chapter05/actor_schedule/db/schema.rb new file mode 100644 index 0000000..1aed699 --- /dev/null +++ b/chapter05/actor_schedule/db/schema.rb @@ -0,0 +1,27 @@ +# This file is autogenerated. Instead of editing this file, please use the +# migrations feature of ActiveRecord to incrementally modify your database, and +# then regenerate this schema definition. + +ActiveRecord::Schema.define(:version => 1) do + + create_table "actors", :force => true do |t| + t.column "name", :text + t.column "phone", :text + end + + create_table "bookings", :force => true do |t| + t.column "actor_id", :integer + t.column "room_id", :integer + t.column "project_id", :integer + t.column "booked_at", :datetime + end + + create_table "projects", :force => true do |t| + t.column "name", :text + end + + create_table "rooms", :force => true do |t| + t.column "name", :text + end + +end diff --git a/chapter05/actor_schedule/doc/README_FOR_APP b/chapter05/actor_schedule/doc/README_FOR_APP new file mode 100644 index 0000000..ac6c149 --- /dev/null +++ b/chapter05/actor_schedule/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake appdoc" to generate API documentation for your models and controllers. \ No newline at end of file diff --git a/chapter05/actor_schedule/public/.htaccess b/chapter05/actor_schedule/public/.htaccess new file mode 100644 index 0000000..d3c9983 --- /dev/null +++ b/chapter05/actor_schedule/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

Application error

Rails application failed to start properly" \ No newline at end of file diff --git a/chapter05/actor_schedule/public/404.html b/chapter05/actor_schedule/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/chapter05/actor_schedule/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
+

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/chapter05/actor_schedule/public/500.html b/chapter05/actor_schedule/public/500.html new file mode 100644 index 0000000..f0aee0e --- /dev/null +++ b/chapter05/actor_schedule/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong + + + + + +
+

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/chapter05/actor_schedule/public/dispatch.cgi b/chapter05/actor_schedule/public/dispatch.cgi new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter05/actor_schedule/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter05/actor_schedule/public/dispatch.fcgi b/chapter05/actor_schedule/public/dispatch.fcgi new file mode 100644 index 0000000..5d9b8ec --- /dev/null +++ b/chapter05/actor_schedule/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!c:/ruby/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) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/chapter05/actor_schedule/public/dispatch.rb b/chapter05/actor_schedule/public/dispatch.rb new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter05/actor_schedule/public/dispatch.rb @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter05/actor_schedule/public/favicon.ico b/chapter05/actor_schedule/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/chapter05/actor_schedule/public/images/rails.png b/chapter05/actor_schedule/public/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/chapter05/actor_schedule/public/images/rails.png differ diff --git a/chapter05/actor_schedule/public/index.html b/chapter05/actor_schedule/public/index.html new file mode 100644 index 0000000..a2daab7 --- /dev/null +++ b/chapter05/actor_schedule/public/index.html @@ -0,0 +1,277 @@ + + + + + Ruby on Rails: Welcome aboard + + + + + + +
+ + +
+ + + + +
+

Getting started

+

Here’s how to get rolling:

+ +
    +
  1. +

    Create your databases and edit config/database.yml

    +

    Rails needs to know your login and password.

    +
  2. + +
  3. +

    Use script/generate to create your models and controllers

    +

    To see all available options, run it without parameters.

    +
  4. + +
  5. +

    Set up a default route and remove or rename this file

    +

    Routes are setup in config/routes.rb.

    +
  6. +
+
+
+ + +
+ + \ No newline at end of file diff --git a/chapter05/actor_schedule/public/javascripts/application.js b/chapter05/actor_schedule/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/chapter05/actor_schedule/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/chapter05/actor_schedule/public/javascripts/controls.js b/chapter05/actor_schedule/public/javascripts/controls.js new file mode 100644 index 0000000..8c273f8 --- /dev/null +++ b/chapter05/actor_schedule/public/javascripts/controls.js @@ -0,0 +1,833 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005, 2006 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 +// 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, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + 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; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + 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; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + 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++ + 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()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + 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 { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// 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. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + 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) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + 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)) + return ""; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + paramName: "value", + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = this.options.paramName; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = this.options.paramName; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/chapter05/actor_schedule/public/javascripts/dragdrop.js b/chapter05/actor_schedule/public/javascripts/dragdrop.js new file mode 100644 index 0000000..c71ddb8 --- /dev/null +++ b/chapter05/actor_schedule/public/javascripts/dragdrop.js @@ -0,0 +1,942 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// 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/ + +if(typeof Effect == 'undefined') + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + 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; + } 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( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + 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); + }, + + reset: function() { + 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) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + 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); + } 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)]; + // Mozilla-based browsers fire successive mousemove events with + // 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.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) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') + 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}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + 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 + + this.delta = this.currentDelta(); + this.options = options; + 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(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + 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.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); + 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); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + 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 ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + 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); + 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]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*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); + this.scrollInterval = null; + 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; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + 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) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + 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)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + 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({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // 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); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + $(e).down('.'+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); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + 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); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, 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); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + 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, + parent: parent, + 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) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + 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); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var 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]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).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) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/chapter05/actor_schedule/public/javascripts/effects.js b/chapter05/actor_schedule/public/javascripts/effects.js new file mode 100644 index 0000000..3b02eda --- /dev/null +++ b/chapter05/actor_schedule/public/javascripts/effects.js @@ -0,0 +1,1088 @@ +// Copyright (c) 2005, 2006 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/ + +// 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)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + return element; +} + +Element.getOpacity = function(element){ + element = $(element); + var opacity; + if (opacity = element.getStyle('opacity')) + return parseFloat(opacity); + if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + element.setStyle({ opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : 1.0 }); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + element.setStyle({opacity: value}); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle( + { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } + return element; +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + Math.round((pos % (1/pulses)) * pulses) == 0 ? + ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : + 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) + ); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } +}; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + 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), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Event = Class.create(); +Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { + initialize: function() { + var options = Object.extend({ + duration: 0 + }, arguments[0] || {}); + this.start(options); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + 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]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + 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(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + 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, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ 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(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // 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, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + 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' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); + effect.element.down().undoPositioned(); + } + }, arguments[1] || {}) + ); +} + +// 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, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + 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({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +} + +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, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +Effect.Morph = Class.create(); +Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: '' + }, arguments[1] || {}); + 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 ) + }); + } + this.transforms = this.options.style.parseStyle().map(function(property){ + var originalValue = this.element.getStyle(property[0]); + return $H({ + style: property[0], + originalValue: property[1].unit=='color' ? + parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: property[1].unit=='color' ? + parseColor(property[1].value) : property[1].value, + unit: property[1].unit + }); + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ) + }); + }, + update: function(position) { + var style = $H(), value = null; + this.transforms.each(function(transform){ + value = transform.unit=='color' ? + $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(transform.originalValue[i]+ + (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) : + transform.originalValue + Math.round( + ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; + style[transform.style] = value; + }); + this.element.setStyle(style); + } +}); + +Effect.Transform = Class.create(); +Object.extend(Effect.Transform.prototype, { + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || {}; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + var data = $H(track).values().first(); + this.tracks.push($H({ + ids: $H(track).keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var elements = [$(track.ids) || $$(track.ids)].flatten(); + return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage', + 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle', + 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth', + 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor', + 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content', + 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction', + 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', + 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight', + 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight', + 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity', + 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY', + 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore', + 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes', + 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress', + 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top', + 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows', + 'width', 'wordSpacing', 'zIndex']; + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.prototype.parseStyle = function(){ + var element = Element.extend(document.createElement('div')); + element.innerHTML = '

    '; + var style = element.down().style, styleRules = $H(); + + Element.CSS_PROPERTIES.each(function(property){ + if(style[property]) styleRules[property] = style[property]; + }); + + var result = $H(); + + styleRules.each(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if(value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if(Element.CSS_LENGTH.test(value)) + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/), + value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null; + + result[property.underscore().dasherize()] = $H({ value:value, unit:unit }); + }.bind(this)); + + return result; +}; + +Element.morph = function(element, style) { + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); + return element; +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/chapter05/actor_schedule/public/javascripts/prototype.js b/chapter05/actor_schedule/public/javascripts/prototype.js new file mode 100644 index 0000000..cedfeee --- /dev/null +++ b/chapter05/actor_schedule/public/javascripts/prototype.js @@ -0,0 +1,2514 @@ +/* Prototype JavaScript framework, version 1.5.0 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0', + BrowserFeatures: { + XPath: !!document.evaluate + }, + + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + emptyFunction: function() {}, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +String.interpret = function(value){ + return value == null ? '' : String(value); +} + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('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) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var name = decodeURIComponent(pair[0]); + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; + + if (hash[name] !== undefined) { + if (hash[name].constructor != Array) + hash[name] = [hash[name]]; + if (value) hash[name].push(value); + } + else hash[name] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + 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; + }, + + capitalize: function(){ + 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(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.replace(/\\/g, '\\\\'); + if (useDoubleQuotes) + return '"' + escapedString.replace(/"/g, '\\"') + '"'; + else + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function() { + return this.inject([], function(array, value) { + return array.include(value) ? array : array.concat([value]); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string){ + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if(window.opera){ + Array.prototype.concat = function(){ + var array = []; + for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for(var i = 0, length = arguments.length; i < length; i++) { + if(arguments[i].constructor == Array) { + for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(obj) { + Object.extend(this, obj || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + + if (pair.value && pair.value.constructor == Array) { + var values = pair.value.compact(); + if (values.length < 2) pair.value = values.reduce(); + else { + key = encodeURIComponent(pair.key); + values.each(function(value) { + value = value != undefined ? encodeURIComponent(value) : ''; + parts.push(key + '=' + encodeURIComponent(value)); + }); + return; + } + } + if (pair.value == undefined) pair[1] = ''; + parts.push(pair.map(encodeURIComponent).join('=')); + }); + + return parts.join('&'); + } +}); + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + } +}); + +function $H(object) { + if (object && object.constructor == Hash) return object; + return new Hash(object); +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + var params = this.options.parameters, method = this.options.method; + + if (!['get', 'post'].include(method)) { + // simulate other verbs over post + params['_method'] = method; + method = 'post'; + } + + params = Hash.toQueryString(params); + if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_=' + + // when GET, append parameters to URL + if (method == 'get' && params) + this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; + + try { + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + var body = method == 'post' ? (this.options.postBody || params) : null; + + this.transport.send(body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.options.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.getHeader('Content-type') || 'text/javascript').strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? eval('(' + json + ')') : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + 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++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; +} + +document.getElementsByClassName = function(className, parentElement) { + if (Prototype.BrowserFeatures.XPath) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } else { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child; + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + if (Element.hasClassName(child, className)) + elements.push(Element.extend(child)); + } + return elements; + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element || _nativeExtensions || element.nodeType == 3) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; + + if (element.tagName == 'FORM') + Object.extend(methods, Form.Methods); + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) + Object.extend(methods, Form.Element.Methods); + + Object.extend(methods, Element.Methods.Simulated); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + 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(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + return Selector.findElement($(element).ancestors(), expression, index); + }, + + down: function(element, expression, index) { + return Selector.findElement($(element).descendants(), expression, index); + }, + + previous: function(element, expression, index) { + return Selector.findElement($(element).previousSiblings(), expression, index); + }, + + next: function(element, expression, index) { + return Selector.findElement($(element).nextSiblings(), expression, index); + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (document.all && !window.opera) { + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + if(attribute) return attribute.nodeValue; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + if (['float','cssFloat'].include(style)) + style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); + style = style.camelize(); + var value = element.style[style]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } else if (element.currentStyle) { + value = element.currentStyle[style]; + } + } + + if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) + value = element['offset'+style.capitalize()] + 'px'; + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + if(style == 'opacity') { + if(value) return parseFloat(value); + if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) { + var value = style[name]; + if(name == 'opacity') { + if (value == 1) { + value = (/Gecko/.test(navigator.userAgent) && + !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else if(value == '') { + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')'; + } + } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; + element.style[name.camelize()] = value; + } + 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) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); + +Element._attributeTranslations = {}; + +Element._attributeTranslations.names = { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" +}; + +Element._attributeTranslations.values = { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } +}; + +Object.extend(Element._attributeTranslations.values, { + href: Element._attributeTranslations.values._getAttr, + src: Element._attributeTranslations.values._getAttr, + disabled: Element._attributeTranslations.values._flag, + checked: Element._attributeTranslations.values._flag, + readonly: Element._attributeTranslations.values._flag, + multiple: Element._attributeTranslations.values._flag +}); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations; + attribute = t.names[attribute] || attribute; + return $(element).getAttributeNode(attribute).specified; + } +}; + +// IE is missing .innerHTML support for TABLE-related elements +if (document.all && !window.opera){ + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
    ' + html.stripScripts() + '
    '; + depth = 4; + } + $A(element.childNodes).each(function(node){ + element.removeChild(node) + }); + depth.times(function(){ div = div.firstChild }); + + $A(div.childNodes).each( + function(node){ element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + } +}; + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { + var className = 'HTML' + tag + 'Element'; + if(window[className]) return; + var klass = window[className] = {}; + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; + }); + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + if (typeof HTMLElement != 'undefined') { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Form.Methods, HTMLFormElement.prototype); + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { + copy(Form.Element.Methods, klass.prototype); + }); + _nativeExtensions = true; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.readAttribute("id") == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0, length = clause.length; i < length; i++) + conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + element = $(element); \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0, length = scope.length; i < length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +Object.extend(Selector, { + matchElements: function(elements, expression) { + var selector = new Selector(expression); + return elements.select(selector.match.bind(selector)).map(Element.extend); + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') index = expression, expression = false; + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + return expressions.map(function(expression) { + return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.inject([], function(elements, result) { + return elements.concat(selector.findElements(result || element)); + }); + }); + }).flatten(); + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != undefined) { + if (result[key]) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + 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; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.blur(); + element.disabled = 'true'; + }); + return form; + }, + + enable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.disabled = ''; + }); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + } +} + +Object.extend(Form, Form.Methods); + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select && ( element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type) ) ) + element.select(); + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.blur(); + element.disabled = false; + return element; + } +} + +Object.extend(Form.Element, Form.Element.Methods); +var Field = Form.Element; +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +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, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (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; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + 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(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(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) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff --git a/chapter05/actor_schedule/public/robots.txt b/chapter05/actor_schedule/public/robots.txt new file mode 100644 index 0000000..4ab9e89 --- /dev/null +++ b/chapter05/actor_schedule/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/chapter05/actor_schedule/script/about b/chapter05/actor_schedule/script/about new file mode 100644 index 0000000..7b07d46 --- /dev/null +++ b/chapter05/actor_schedule/script/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about' \ No newline at end of file diff --git a/chapter05/actor_schedule/script/breakpointer b/chapter05/actor_schedule/script/breakpointer new file mode 100644 index 0000000..64af76e --- /dev/null +++ b/chapter05/actor_schedule/script/breakpointer @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' \ No newline at end of file diff --git a/chapter05/actor_schedule/script/console b/chapter05/actor_schedule/script/console new file mode 100644 index 0000000..42f28f7 --- /dev/null +++ b/chapter05/actor_schedule/script/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' \ No newline at end of file diff --git a/chapter05/actor_schedule/script/destroy b/chapter05/actor_schedule/script/destroy new file mode 100644 index 0000000..fa0e6fc --- /dev/null +++ b/chapter05/actor_schedule/script/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' \ No newline at end of file diff --git a/chapter05/actor_schedule/script/generate b/chapter05/actor_schedule/script/generate new file mode 100644 index 0000000..ef976e0 --- /dev/null +++ b/chapter05/actor_schedule/script/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' \ No newline at end of file diff --git a/chapter05/actor_schedule/script/performance/benchmarker b/chapter05/actor_schedule/script/performance/benchmarker new file mode 100644 index 0000000..c842d35 --- /dev/null +++ b/chapter05/actor_schedule/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/chapter05/actor_schedule/script/performance/profiler b/chapter05/actor_schedule/script/performance/profiler new file mode 100644 index 0000000..d855ac8 --- /dev/null +++ b/chapter05/actor_schedule/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/chapter05/actor_schedule/script/plugin b/chapter05/actor_schedule/script/plugin new file mode 100644 index 0000000..26ca64c --- /dev/null +++ b/chapter05/actor_schedule/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' \ No newline at end of file diff --git a/chapter05/actor_schedule/script/process/inspector b/chapter05/actor_schedule/script/process/inspector new file mode 100644 index 0000000..bf25ad8 --- /dev/null +++ b/chapter05/actor_schedule/script/process/inspector @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/inspector' diff --git a/chapter05/actor_schedule/script/process/reaper b/chapter05/actor_schedule/script/process/reaper new file mode 100644 index 0000000..c77f045 --- /dev/null +++ b/chapter05/actor_schedule/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/chapter05/actor_schedule/script/process/spawner b/chapter05/actor_schedule/script/process/spawner new file mode 100644 index 0000000..7118f39 --- /dev/null +++ b/chapter05/actor_schedule/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/chapter05/actor_schedule/script/runner b/chapter05/actor_schedule/script/runner new file mode 100644 index 0000000..ccc30f9 --- /dev/null +++ b/chapter05/actor_schedule/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/chapter05/actor_schedule/script/server b/chapter05/actor_schedule/script/server new file mode 100644 index 0000000..dfabcb8 --- /dev/null +++ b/chapter05/actor_schedule/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/chapter05/actor_schedule/test/fixtures/actors.yml b/chapter05/actor_schedule/test/fixtures/actors.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter05/actor_schedule/test/fixtures/actors.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter05/actor_schedule/test/fixtures/bookings.yml b/chapter05/actor_schedule/test/fixtures/bookings.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter05/actor_schedule/test/fixtures/bookings.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter05/actor_schedule/test/fixtures/projects.yml b/chapter05/actor_schedule/test/fixtures/projects.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter05/actor_schedule/test/fixtures/projects.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter05/actor_schedule/test/fixtures/rooms.yml b/chapter05/actor_schedule/test/fixtures/rooms.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter05/actor_schedule/test/fixtures/rooms.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter05/actor_schedule/test/functional/homepage_controller_test.rb b/chapter05/actor_schedule/test/functional/homepage_controller_test.rb new file mode 100644 index 0000000..2ee4597 --- /dev/null +++ b/chapter05/actor_schedule/test/functional/homepage_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'homepage_controller' + +# Re-raise errors caught by the controller. +class HomepageController; def rescue_action(e) raise e end; end + +class HomepageControllerTest < Test::Unit::TestCase + def setup + @controller = HomepageController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter05/actor_schedule/test/test_helper.rb b/chapter05/actor_schedule/test/test_helper.rb new file mode 100644 index 0000000..a299c7f --- /dev/null +++ b/chapter05/actor_schedule/test/test_helper.rb @@ -0,0 +1,28 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' + +class Test::Unit::TestCase + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # Add more helper methods to be used by all tests here... +end diff --git a/chapter05/actor_schedule/test/unit/actors_test.rb b/chapter05/actor_schedule/test/unit/actors_test.rb new file mode 100644 index 0000000..14bf56f --- /dev/null +++ b/chapter05/actor_schedule/test/unit/actors_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ActorsTest < Test::Unit::TestCase + fixtures :actors + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter05/actor_schedule/test/unit/bookings_test.rb b/chapter05/actor_schedule/test/unit/bookings_test.rb new file mode 100644 index 0000000..0d55c93 --- /dev/null +++ b/chapter05/actor_schedule/test/unit/bookings_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class BookingsTest < Test::Unit::TestCase + fixtures :bookings + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter05/actor_schedule/test/unit/projects_test.rb b/chapter05/actor_schedule/test/unit/projects_test.rb new file mode 100644 index 0000000..1f91bf5 --- /dev/null +++ b/chapter05/actor_schedule/test/unit/projects_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class ProjectsTest < Test::Unit::TestCase + fixtures :projects + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter05/actor_schedule/test/unit/rooms_test.rb b/chapter05/actor_schedule/test/unit/rooms_test.rb new file mode 100644 index 0000000..e98b59f --- /dev/null +++ b/chapter05/actor_schedule/test/unit/rooms_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class RoomsTest < Test::Unit::TestCase + fixtures :rooms + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter05/actor_schedule_data.sql b/chapter05/actor_schedule_data.sql new file mode 100644 index 0000000..04e60c9 --- /dev/null +++ b/chapter05/actor_schedule_data.sql @@ -0,0 +1,43 @@ +DELETE FROM actors; +DELETE FROM projects; +DELETE FROM rooms; +DELETE FROM bookings; + +INSERT INTO actors (id, name) VALUES + (1, 'Jim Thompson'); +INSERT INTO actors (id, name) VALUES + (2, 'Becky Leuser'); +INSERT INTO actors (id, name) VALUES + (3, 'Elizabeth Berube'); +INSERT INTO actors (id, name) VALUES + (4, 'Dave Guuseman'); +INSERT INTO actors (id, name) VALUES + (5, 'Tom Jimson'); + +INSERT INTO projects (id, name) VALUES + (1, 'Turbo Bowling Intro Sequence'); +INSERT INTO projects (id, name) VALUES + (2, 'Seven for Dinner Win Game Sequence'); +INSERT INTO projects (id, name) VALUES + (3, 'Seven for Dinner Lost Game Sequence'); + +INSERT INTO rooms (id, name) VALUES + (1, 'L120, Little Hall'); +INSERT INTO rooms (id, name) VALUES + (2, 'L112, Little Hall'); +INSERT INTO rooms (id, name) VALUES + (3, 'M120, Tech Center'); + +INSERT INTO bookings (actor_id, room_id, project_id, booked_at) VALUES + (1,1,2, DATE_ADD( NOW(), INTERVAL 3 HOUR )); +INSERT INTO bookings (actor_id, room_id, project_id, booked_at) VALUES + (1,1,3, DATE_ADD( NOW(), INTERVAL 4 HOUR )); +INSERT INTO bookings (actor_id, room_id, project_id, booked_at) VALUES + (3,2,2, DATE_ADD( NOW(), INTERVAL 1 DAY )); +INSERT INTO bookings (actor_id, room_id, project_id, booked_at) VALUES + (2,3,1, DATE_ADD( NOW(), INTERVAL 5 HOUR )); +INSERT INTO bookings (actor_id, room_id, project_id, booked_at) VALUES + (4,1,2, DATE_ADD( NOW(), INTERVAL 10 HOUR )); +INSERT INTO bookings (actor_id, room_id, project_id, booked_at) VALUES + (5,3,1, DATE_ADD( NOW(), INTERVAL 1 HOUR )); + diff --git a/chapter05/team_performance_web/README b/chapter05/team_performance_web/README new file mode 100644 index 0000000..a1db73c --- /dev/null +++ b/chapter05/team_performance_web/README @@ -0,0 +1,203 @@ +== Welcome to Rails + +Rails is a web-application and persistence framework that includes everything +needed to create database-backed web-applications according to the +Model-View-Control pattern of separation. This pattern splits the view (also +called the presentation) into "dumb" templates that are primarily responsible +for inserting pre-built data in between HTML tags. The model contains the +"smart" domain objects (such as Account, Product, Person, Post) that holds all +the business logic and knows how to persist themselves to a database. The +controller handles the incoming requests (such as Save New Account, Update +Product, Show Post) by manipulating the model and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, start a new Rails application using the rails command + and your application name. Ex: rails myapp + (If you've downloaded Rails in a complete tgz or zip, this step is already done) +2. Change directory into myapp and start the web server: script/server (run with --help for options) +3. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!" +4. Follow the guidelines to start developing your application + + +== Web Servers + +By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise +Rails will use WEBrick, the webserver that ships with Ruby. When you run script/server, +Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures +that you can always get up and running quickly. + +Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is +suitable for development and deployment of Rails applications. If you have Ruby Gems installed, +getting up and running with mongrel is as easy as: gem install mongrel. +More info at: http://mongrel.rubyforge.org + +If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than +Mongrel and WEBrick and also suited for production use, but requires additional +installation and currently only works well on OS X/Unix (Windows users are encouraged +to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from +http://www.lighttpd.net. + +And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby +web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not +for production. + +But of course its also possible to run Rails on any platform that supports FCGI. +Apache, LiteSpeed, IIS are just a few. For more information on FCGI, +please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands running +on the server.log and development.log. Rails will automatically display debugging +and runtime information to these files. Debugging info will also be shown in the +browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code using +the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: + +* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two online (and free) books will bring you up to speed on the Ruby language +and also on programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your Mongrel or +Webrick server with --debugger. This means that you can break out of execution at any point +in the code, investigate and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find(:all) + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you enter "cont" + + +== Console + +You can interact with the domain model by starting the console through script/console. +Here you'll have all parts of the application configured, just like it is when the +application is running. You can inspect domain models, change values, and save to the +database. Starting the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like script/console production. + +To reload your controllers and models after launching the console run reload! + + +== Description of Contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from ApplicationController + which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descend from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblogs/index.erb for the WeblogsController#index action. All views use eRuby + syntax. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the common + header/footer method of wrapping views. In your views, define a layout using the + layout :default and create a file named default.erb. Inside default.erb, + call <% yield %> to render the view using this layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are generated + for you automatically when using script/generate for controllers. Helpers can be used to + wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all + the sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when generated + using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong under controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains subdirectories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. This should be + set as the DOCUMENT_ROOT of your web server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the script/generate scripts, template + test files will be generated for you and placed in this directory. + +vendor + External libraries that the application depends on. Also includes the plugins subdirectory. + This directory is in the load path. diff --git a/chapter05/team_performance_web/Rakefile b/chapter05/team_performance_web/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/chapter05/team_performance_web/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/chapter05/team_performance_web/app/controllers/application.rb b/chapter05/team_performance_web/app/controllers/application.rb new file mode 100644 index 0000000..fa632aa --- /dev/null +++ b/chapter05/team_performance_web/app/controllers/application.rb @@ -0,0 +1,8 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + # Pick a unique cookie name to distinguish our session data from others' + session :session_key => '_team_performance_web_session_id' + protect_from_forgery # :secret => '5946bdaf4fb685c582d0e732d4bcfb1d' +end diff --git a/chapter05/team_performance_web/app/controllers/home_controller.rb b/chapter05/team_performance_web/app/controllers/home_controller.rb new file mode 100644 index 0000000..6a85171 --- /dev/null +++ b/chapter05/team_performance_web/app/controllers/home_controller.rb @@ -0,0 +1,6 @@ +class HomeController < ApplicationController + def index + @available_players =Player.find(:all) + @available_games = Game.find(:all) + end +end diff --git a/chapter05/team_performance_web/app/controllers/performance_controller.rb b/chapter05/team_performance_web/app/controllers/performance_controller.rb new file mode 100644 index 0000000..5184cef --- /dev/null +++ b/chapter05/team_performance_web/app/controllers/performance_controller.rb @@ -0,0 +1,33 @@ +class PerformanceController < ApplicationController + def show + @player = Player.find_by_id(params[:player_id]) + @game = Game.find_by_id(params[:game_id]) + + @events = Event.find(:all, + :select=>'event, ' << + 'AVG(time)/1000 as average_time', + :group=>'events.event DESC', + :joins=>' INNER JOIN plays ON events.play_id=plays.id', + :conditions=>["plays.game_id = ? AND plays.player_id= ?", + @game.id, @player.id] + ).map { |event| + {:event=>event.event, + :average_time=>event.average_time.to_i} + } + + + respond_to do |format| + format.html { render :layout=>false if request.xhr? } + format.text { render :layout=>false } + format.xml { render :xml=>{'player'=>@player, + 'game'=>@game, + 'events'=>@events + }.to_xml(:root=>'player_performance_report', + :skip_types=>true) } + format.json { render :json=>{'player'=>@player, + 'game'=>@game, + 'events'=>@events}.to_json } + end + + end +end diff --git a/chapter05/team_performance_web/app/helpers/application_helper.rb b/chapter05/team_performance_web/app/helpers/application_helper.rb new file mode 100644 index 0000000..22a7940 --- /dev/null +++ b/chapter05/team_performance_web/app/helpers/application_helper.rb @@ -0,0 +1,3 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper +end diff --git a/chapter05/team_performance_web/app/models/event.rb b/chapter05/team_performance_web/app/models/event.rb new file mode 100644 index 0000000..5efa3a0 --- /dev/null +++ b/chapter05/team_performance_web/app/models/event.rb @@ -0,0 +1,5 @@ +class Event < ActiveRecord::Base + belongs_to :play +end + + diff --git a/chapter05/team_performance_web/app/models/game.rb b/chapter05/team_performance_web/app/models/game.rb new file mode 100644 index 0000000..a6cc63e --- /dev/null +++ b/chapter05/team_performance_web/app/models/game.rb @@ -0,0 +1,3 @@ +class Game < ActiveRecord::Base + has_many :plays +end diff --git a/chapter05/team_performance_web/app/models/play.rb b/chapter05/team_performance_web/app/models/play.rb new file mode 100644 index 0000000..f17fda5 --- /dev/null +++ b/chapter05/team_performance_web/app/models/play.rb @@ -0,0 +1,6 @@ +class Play < ActiveRecord::Base + belongs_to :game + belongs_to :player +end + + diff --git a/chapter05/team_performance_web/app/models/player.rb b/chapter05/team_performance_web/app/models/player.rb new file mode 100644 index 0000000..3f78579 --- /dev/null +++ b/chapter05/team_performance_web/app/models/player.rb @@ -0,0 +1,3 @@ +class Player < ActiveRecord::Base + has_many :plays +end diff --git a/chapter05/team_performance_web/app/views/home/index.html.erb b/chapter05/team_performance_web/app/views/home/index.html.erb new file mode 100644 index 0000000..ac6fdec --- /dev/null +++ b/chapter05/team_performance_web/app/views/home/index.html.erb @@ -0,0 +1,42 @@ +

    Team Performance Reporting

    + +
    + <%=select 'player', 'id', + [['Click here to select a player',""]] + + @available_players.map { |p| + [p.name, p.id] }, + {:include_blank=>false} %> + <%=select 'game', 'id', + [['Click here to select a game',""]] + + @available_games.map { |g| + [g.name, g.id] }, + {:include_blank=>false} %> +
    +
    +
    + + diff --git a/chapter05/team_performance_web/app/views/layouts/application.html.erb b/chapter05/team_performance_web/app/views/layouts/application.html.erb new file mode 100644 index 0000000..150505c --- /dev/null +++ b/chapter05/team_performance_web/app/views/layouts/application.html.erb @@ -0,0 +1,15 @@ + + + Team Performance Web Analyzer + + <%=javascript_include_tag :defaults %> + + + + <%=yield%> + + diff --git a/chapter05/team_performance_web/app/views/performance/show.html.erb b/chapter05/team_performance_web/app/views/performance/show.html.erb new file mode 100644 index 0000000..1485328 --- /dev/null +++ b/chapter05/team_performance_web/app/views/performance/show.html.erb @@ -0,0 +1,16 @@ +<%if @events.length>1%> + +
    + <% + graph_params = { 'AllowScriptAccess'=>'SameDomain' } + %> + <%=flashobject_tag "/flash/open-flash-chart.swf", + :size=>"850x400", + :variables=>{'data'=>"/performance" << + "/#{@game.id}" << + "/#{@player.id}.text"} %> +
    + +<%else%> +

    <%=@player.name%> has no recorded data for <%=@game.name%>.

    +<%end%> diff --git a/chapter05/team_performance_web/app/views/performance/show.text.erb b/chapter05/team_performance_web/app/views/performance/show.text.erb new file mode 100644 index 0000000..340e2ee --- /dev/null +++ b/chapter05/team_performance_web/app/views/performance/show.text.erb @@ -0,0 +1,27 @@ +<% + labels = @events.map { |e| e[:event] } + values = @events.map { |e| e[:average_time] } + min = 0 + max = values.max + + + graph_variables = { "title"=>",{margin:10px;}", + "bar_3d"=>"60,#8E9BF0,#000000", + "values"=>"#{values.join(',')}", + "x_labels"=>"#{labels.join(',')}", + "y_max"=>max, + "y_min"=>min, + "x_axis_3d"=>"16", + "tool_tip"=>"#x_label#: #val#s Average Time", + "y_axis_colour"=>"#F0F0F0", + "y_grid_colour"=>"#E9E9E9", + "y_label_style"=>"12,#000000", + "x_axis_colour"=>"#6F6F7F", + "x_grid_colour"=>"#E9E9E9", + "x_label_style"=>"15,#000000", + + "bg_colour"=>"#F8F8FF" } + + %> +&<%=graph_variables.to_a.map { |key,val| "#{key}=#{val}" }.join("& &") %>& + diff --git a/chapter05/team_performance_web/config/boot.rb b/chapter05/team_performance_web/config/boot.rb new file mode 100644 index 0000000..b71c198 --- /dev/null +++ b/chapter05/team_performance_web/config/boot.rb @@ -0,0 +1,108 @@ +# Don't change this file! +# Configure your app in config/environment.rb and config/environments/*.rb + +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) + +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.exists?(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" + 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' + + unless rubygems_version >= '0.9.4' + $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) + exit 1 + end + + rescue LoadError + $stderr.puts %(Rails requires RubyGems >= 0.9.4. 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 + +# All that for this: +Rails.boot! diff --git a/chapter05/team_performance_web/config/database.yml b/chapter05/team_performance_web/config/database.yml new file mode 100644 index 0000000..ef1b373 --- /dev/null +++ b/chapter05/team_performance_web/config/database.yml @@ -0,0 +1,36 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install mysql +# On MacOS X: +# gem install mysql -- --include=/usr/local/lib +# On Windows: +# gem install mysql +# Choose the win32 build. +# Install MySQL and put its /bin directory on your path. +# +# 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: players_4 + username: root + password: + host: localhost + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: team_performance_web_test + username: root + password: + host: localhost + +production: + adapter: mysql + database: team_performance_web_production + username: root + password: + host: localhost diff --git a/chapter05/team_performance_web/config/environment.rb b/chapter05/team_performance_web/config/environment.rb new file mode 100644 index 0000000..2bd421d --- /dev/null +++ b/chapter05/team_performance_web/config/environment.rb @@ -0,0 +1,59 @@ +# 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.0.1' unless defined? RAILS_GEM_VERSION + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here. + # Application configuration should go into files in config/initializers + # -- all .rb files in that directory are automatically loaded. + # See Rails::Configuration for more options. + + # Skip frameworks you're not going to use (only works if using vendor/rails). + # To use Rails without a database, you must remove the Active Record framework + # config.frameworks -= [ :active_record, :active_resource, :action_mailer ] + + # Only load the plugins named here, in the order given. By default, all plugins + # in vendor/plugins are loaded in alphabetical order. + # :all can be used as a placeholder for all plugins not explicitly named + # config.plugins = [ :exception_notification, :ssl_requirement, :all ] + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # 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 = { + :session_key => '_team_performance_web_session', + :secret => '5bcf4fcbb841e4633f73da39e73e6055c43a44727be161f854da33deb9937c04cbe0817907ff3a68f10e294edbd01647958e4aac118d9e0d7b64ca77c8925ad1' + } + + # 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 'rake db:sessions:create') + # config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc +end \ No newline at end of file diff --git a/chapter05/team_performance_web/config/environments/development.rb b/chapter05/team_performance_web/config/environments/development.rb new file mode 100644 index 0000000..09a451f --- /dev/null +++ b/chapter05/team_performance_web/config/environments/development.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_view.debug_rjs = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false \ No newline at end of file diff --git a/chapter05/team_performance_web/config/environments/production.rb b/chapter05/team_performance_web/config/environments/production.rb new file mode 100644 index 0000000..cb295b8 --- /dev/null +++ b/chapter05/team_performance_web/config/environments/production.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors, bad email addresses will be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/chapter05/team_performance_web/config/environments/test.rb b/chapter05/team_performance_web/config/environments/test.rb new file mode 100644 index 0000000..58850a7 --- /dev/null +++ b/chapter05/team_performance_web/config/environments/test.rb @@ -0,0 +1,22 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Disable request forgery protection in test environment +config.action_controller.allow_forgery_protection = 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 diff --git a/chapter05/team_performance_web/config/initializers/inflections.rb b/chapter05/team_performance_web/config/initializers/inflections.rb new file mode 100644 index 0000000..09158b8 --- /dev/null +++ b/chapter05/team_performance_web/config/initializers/inflections.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end diff --git a/chapter05/team_performance_web/config/initializers/mime_types.rb b/chapter05/team_performance_web/config/initializers/mime_types.rb new file mode 100644 index 0000000..72aca7e --- /dev/null +++ b/chapter05/team_performance_web/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/chapter05/team_performance_web/config/routes.rb b/chapter05/team_performance_web/config/routes.rb new file mode 100644 index 0000000..50ad5ae --- /dev/null +++ b/chapter05/team_performance_web/config/routes.rb @@ -0,0 +1,41 @@ +ActionController::Routing::Routes.draw do |map| + map.connect 'performance/:game_id/:player_id', :controller=>'performance', :action=>'show' + map.connect 'performance/:game_id/:player_id.:format', :controller=>'performance', :action=>'show' + + map.connect "/", :controller=>'home' + + map.connect ':controller/:action/:id' + map.connect ':controller/:action/:id.:format' + + # The priority is based upon order of creation: first created -> highest priority. + + # Sample of regular route: + # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' + # This route can be invoked with purchase_url(:id => product.id) + + # Sample resource route (maps HTTP verbs to controller actions automatically): + # map.resources :products + + # Sample resource route with options: + # map.resources :products, :member => { :short => :get, :toggle => :post }, :collection => { :sold => :get } + + # Sample resource route with sub-resources: + # map.resources :products, :has_many => [ :comments, :sales ], :has_one => :seller + + # Sample resource route within a namespace: + # map.namespace :admin do |admin| + # # Directs /admin/products/* to Admin::ProductsController (app/controllers/admin/products_controller.rb) + # admin.resources :products + # end + + # You can have the root of your site routed with map.root -- just remember to delete public/index.html. + # map.root :controller => "welcome" + + # See how all your routes lay out with "rake routes" + + # Install the default routes as the lowest priority. +end diff --git a/chapter05/team_performance_web/db/migrate/001_create_players.rb b/chapter05/team_performance_web/db/migrate/001_create_players.rb new file mode 100644 index 0000000..42452e1 --- /dev/null +++ b/chapter05/team_performance_web/db/migrate/001_create_players.rb @@ -0,0 +1,10 @@ +class CreatePlayers < ActiveRecord::Migration + def self.up + create_table :players do |t| + end + end + + def self.down + drop_table :players + end +end diff --git a/chapter05/team_performance_web/doc/README_FOR_APP b/chapter05/team_performance_web/doc/README_FOR_APP new file mode 100644 index 0000000..fe41f5c --- /dev/null +++ b/chapter05/team_performance_web/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. diff --git a/chapter05/team_performance_web/log/development.log b/chapter05/team_performance_web/log/development.log new file mode 100644 index 0000000..d87225f --- /dev/null +++ b/chapter05/team_performance_web/log/development.log @@ -0,0 +1,14514 @@ +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:00:04) [GET] + Session ID: b3827301f16d18e07989b6edd267971d + Parameters: {"action"=>"index", "controller"=>"home"} +WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. Please install the C-based MySQL library instead (gem install mysql). + SQL (0.157000) SET SQL_AUTO_IS_NULL=0 +Rendering template within layouts/application +Rendering home/index + + +ActionView::TemplateError (uninitialized constant ActionView::Base::CompiledTemplates::Player) on line #4 of home/index.rhtml: +1:

    Team Performance Reporting

    +2: +3: <%form_tag '/home', :method=>:get do%> +4: <%=select 'player', 'id', Player.find(:all).map { |p| [p.name, p.id] } %> +5: <%=select 'game', 'id', Game.find(:all).map { |g| [g.name, g.id] }%> +6: <%=submit_tag 'View'%> +7: <%end%> + + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:263:in `load_missing_constant' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:453:in `const_missing' + app/views/home/index.rhtml:4:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:142:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:142:in `capture_erb_with_buffer' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:44:in `capture' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:417:in `form_tag_in_block' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:39:in `form_tag' + app/views/home/index.rhtml:3:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:00:51) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} +Rendering template within layouts/application +Rendering home/index + + +ActionView::TemplateError (uninitialized constant ActionView::Base::CompiledTemplates::Player) on line #4 of home/index.rhtml: +1:

    Team Performance Reporting

    +2: +3: <%form_tag '/home', :method=>:get do%> +4: <%=select_tag 'player', Player.find(:all).map { |p| [p.name, p.id] } %> +5: <%=select_tag 'game', Game.find(:all).map { |g| [g.name, g.id] }%> +6: <%=submit_tag 'View'%> +7: <%end%> + + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:263:in `load_missing_constant' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:453:in `const_missing' + app/views/home/index.rhtml:4:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:142:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:142:in `capture_erb_with_buffer' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:44:in `capture' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:417:in `form_tag_in_block' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:39:in `form_tag' + app/views/home/index.rhtml:3:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:01:39) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + + +NameError (uninitialized constant HomeController::Player): + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:478:in `const_missing' + /app/controllers/home_controller.rb:3:in `index' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:02:09) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.266000) SELECT * FROM `players`  + Player Columns (0.046000) SHOW FIELDS FROM `players` + Game Load (0.047000) SELECT * FROM `games`  + Game Columns (0.000000) SHOW FIELDS FROM `games` +Rendering template within layouts/application +Rendering home/index +Completed in 0.42200 (2 reqs/sec) | Rendering: 0.01500 (3%) | DB: 0.35900 (85%) | 200 OK [http://localhost/home] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:02:26) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Load (0.000000) SELECT * FROM `games`  + Game Columns (0.000000) SHOW FIELDS FROM `games` +Rendering template within layouts/application +Rendering home/index + + +ActionView::TemplateError (undefined method `dump=' for YAML:Module) on line #8 of home/index.rhtml: +5: <%=select_tag 'game', @available_games%> +6: <%=submit_tag 'View'%> +7: <%end%> +8: <%=YAML.dump=@available_games%> +9: <%if @player and @game and @events.length>0%> +10:
    +11: + + app/views/home/index.rhtml:8:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:02:44) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Load (0.000000) SELECT * FROM `games`  + Game Columns (0.015000) SHOW FIELDS FROM `games` +Rendering template within layouts/application +Rendering home/index +Completed in 0.32800 (3 reqs/sec) | Rendering: 0.09400 (28%) | DB: 0.01500 (4%) | 200 OK [http://localhost/home] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:03:06) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.25000 (4 reqs/sec) | Rendering: 0.04700 (18%) | DB: 0.00000 (0%) | 200 OK [http://localhost/home] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:03:19) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.031000) SHOW FIELDS FROM `players` + + +ActionView::TemplateError (undefined method `stringify_keys' for #) on line #4 of home/index.rhtml: +1:

    Team Performance Reporting

    +2: +3: <%form_tag '/home', :method=>:get do%> +4: <%=select_tag 'player', {}, @available_players.map { |p| [p.name, p.id] } %> +5: <%=select_tag 'game', {}, @available_games.map { |g| [g.name, g.id] }%> +6: <%=submit_tag 'View'%> +7: <%end%> + + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:80:in `select_tag' + app/views/home/index.rhtml:4:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:142:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:142:in `capture_erb_with_buffer' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:44:in `capture' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:417:in `form_tag_in_block' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:39:in `form_tag' + app/views/home/index.rhtml:3:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:03:42) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + + +ActionView::TemplateError (wrong number of arguments (2 for 3)) on line #4 of home/index.rhtml: +1:

    Team Performance Reporting

    +2: +3: <%form_tag '/home', :method=>:get do%> +4: <%=select 'player', @available_players.map { |p| [p.name, p.id] } %> +5: <%=select 'game', @available_games.map { |g| [g.name, g.id] }%> +6: <%=submit_tag 'View'%> +7: <%end%> + + app/views/home/index.rhtml:4:in `select' + app/views/home/index.rhtml:4:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:142:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:142:in `capture_erb_with_buffer' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/capture_helper.rb:44:in `capture' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:417:in `form_tag_in_block' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/helpers/form_tag_helper.rb:39:in `form_tag' + app/views/home/index.rhtml:3:in `_run_rhtml_47app47views47home47index46rhtml' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:03:55) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.016000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.03000 (38%) | DB: 0.03200 (41%) | 200 OK [http://localhost/home] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:04:46) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.40600 (2 reqs/sec) | Rendering: 0.34400 (84%) | DB: 0.01600 (3%) | 200 OK [http://localhost/home] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:04:49) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"1"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.016000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.047000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 1)  + Event Load (0.125000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='1' + AND + p.player_id='2' + GROUP BY e.event DESC; +Rendering template within layouts/application +Rendering home/index +Completed in 0.25000 (4 reqs/sec) | Rendering: 0.01600 (6%) | DB: 0.20300 (81%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=1&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:05:09) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"1"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 1)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='1' + AND + p.player_id='2' + GROUP BY e.event DESC; +Rendering template within layouts/application +Rendering home/index +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.03100 (32%) | DB: 0.01600 (17%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=1&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:05:12) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"1"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.016000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 1)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='1' + AND + p.player_id='2' + GROUP BY e.event DESC; +Rendering template within layouts/application +Rendering home/index +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.03100 (39%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=1&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:05:18) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"1"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 1)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='1' + AND + p.player_id='2' + GROUP BY e.event DESC; +Rendering template within layouts/application +Rendering home/index +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.01500 (15%) | DB: 0.01600 (17%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=1&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:05:22) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.016000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='2' + GROUP BY e.event DESC; + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +Completed in 0.73400 (1 reqs/sec) | Rendering: 0.53100 (72%) | DB: 0.04600 (6%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=5&commit=View] + + +Processing ApplicationController#index (for 127.0.0.1 at 2007-12-14 10:05:23) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {} + + +ActionController::RoutingError (No route matches "/javascripts/swfobject.js" with {:method=>:get}): + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/routing.rb:1438:in `recognize_path' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/routing.rb:1421:in `recognize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:170:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (not_found) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:05:59) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='2' + GROUP BY e.event DESC; + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.01600 (14%) | DB: 0.04700 (43%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=5&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:06:02) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='2' + GROUP BY e.event DESC; + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01500 (19%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=5&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:06:05) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='2' + GROUP BY e.event DESC; + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +Completed in 0.11000 (9 reqs/sec) | Rendering: 0.01600 (14%) | DB: 0.04600 (41%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=5&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:07:25) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.032000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='2' + GROUP BY e.event DESC; + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +Completed in 0.12500 (8 reqs/sec) | Rendering: 0.03100 (24%) | DB: 0.03200 (25%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=5&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:07:29) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"2"}, "action"=>"index", "controller"=>"home"} + Player Load (0.015000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 2)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='2' + GROUP BY e.event DESC; + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.03200 (41%) | DB: 0.03100 (39%) | 200 OK [http://localhost/home?player%5Bid%5D=2&game%5Bid%5D=5&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:07:41) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"7"}, "action"=>"index", "controller"=>"home"} + Player Load (0.016000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 7)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='7' + GROUP BY e.event DESC; + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.03100 (28%) | DB: 0.01600 (14%) | 200 OK [http://localhost/home?player%5Bid%5D=7&game%5Bid%5D=5&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:07:51) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"7"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 7)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='7' + GROUP BY e.event DESC; + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.01500 (13%) | DB: 0.03100 (28%) | 200 OK [http://localhost/home?player%5Bid%5D=7&game%5Bid%5D=5&commit=View] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:08:16) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"7"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = 7)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='7' + GROUP BY e.event DESC; + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index + + +ActionView::TemplateError (undefined method `flash_object' for #) on line #13 of home/index.rhtml: +10: <%=javascript_include_tag :defaults%> +11: +12:
    +13: <%=flash_object "/flash/open-flash-chart.swf"%> +14: \n" +_erbout.concat "\n" +_erbout.concat "
    \n" +end; _erbout.concat "\n" +_erbout +end +Backtrace: C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:58:in `compile_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +script/server:3 + + +ActionView::TemplateError (compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:15: syntax error, unexpected tSTRING_BEG, expecting '}' + "bar_3d"=>"60,#8E9BF0,#000000" + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:15: syntax error, unexpected tASSOC, expecting kEND + "bar_3d"=>"60,#8E9BF0,#000000" + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:16: syntax error, unexpected tASSOC, expecting kEND + "values"=>"<%=values.join(','); _erbout.concat "\"\n" + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:16: syntax error, unexpected $undefined, expecting kEND + "values"=>"<%=values.join(','); _erbout.concat "\"\n" + ^) on line #15 of home/index.rhtml: +12:
    +13: <% +14: graph_variables = { "title"=>",{margin:10px;}" +15: "bar_3d"=>"60,#8E9BF0,#000000" +16: "values"=>"<%=values.join(',')%>" +17: "x_labels"=>"<%=labels.join(',')%>" +18: "y_max"=>"<%=max%>" + + app/views/home/index.rhtml:58:in `compile_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:10:40) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"7"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 7)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='7' + GROUP BY e.event DESC; + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +ERROR: compiling _run_rhtml_47app47views47home47index46rhtml RAISED compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:16: syntax error, unexpected $undefined, expecting '}' + "values"=>"<%=values.join(','); _erbout.concat "\",\n" + ^ +Function body: def _run_rhtml_47app47views47home47index46rhtml(local_assigns) +_erbout = ''; _erbout.concat "

    Team Performance Reporting

    \n" +_erbout.concat "\n" +form_tag '/home', :method=>:get do; _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((select 'player', 'id', @available_players.map { |p| [p.name, p.id] } ).to_s); _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((select 'game', 'id', @available_games.map { |g| [g.name, g.id] }).to_s); _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((submit_tag 'View').to_s); _erbout.concat "\n" +end; _erbout.concat "\n" +if @player and @game and @events.length>0; _erbout.concat "\n" +_erbout.concat "
    \n" +_erbout.concat " "; _erbout.concat((javascript_include_tag :defaults).to_s); _erbout.concat "\n" +_erbout.concat "\n" +_erbout.concat "
    \n" +_erbout.concat " "; +graph_variables = { "title"=>",{margin:10px;}", + "bar_3d"=>"60,#8E9BF0,#000000", + "values"=>"<%=values.join(','); _erbout.concat "\",\n" +_erbout.concat " \"x_labels\"=>\""; _erbout.concat((labels.join(',')).to_s); _erbout.concat "\",\n" +_erbout.concat " \"y_max\"=>\""; _erbout.concat((max).to_s); _erbout.concat "\",\n" +_erbout.concat " \"y_min\"=>\""; _erbout.concat((min).to_s); _erbout.concat "\",\n" +_erbout.concat " \"y_ticks\"=>\"15,3,10\",\n" +_erbout.concat " #first is tick distance\n" +_erbout.concat " #second is distance from labels to chart\n" +_erbout.concat " #third is number of ticks.\n" +_erbout.concat "\n" +_erbout.concat " \"x_axis_3d\"=>\"16\",\n" +_erbout.concat " \"tool_tip\"=>\"#x_label#
    #val#s Average Time\",\n" +_erbout.concat " \"y_axis_colour\"=>\"#F0F0F0\",\n" +_erbout.concat " \"y_grid_colour\"=>\"#E9E9E9\",\n" +_erbout.concat " \"y_label_style\"=>\"12,#000000\",\n" +_erbout.concat ",\n" +_erbout.concat " \"x_axis_colour\"=>\"#6F6F7F\",\n" +_erbout.concat " \"x_grid_colour\"=>\"#E9E9E9\",\n" +_erbout.concat " \"x_label_style\"=>\"15,#000000\",\n" +_erbout.concat "\n" +_erbout.concat " \"bg_colour\"=>\"#F8F8FF\" } %>\n" +_erbout.concat " \n" +_erbout.concat((flashobject_tag "/flash/open-flash-chart.swf", + :size=>"850x400", :variables=>graph_variables ).to_s); _erbout.concat "\n" +_erbout.concat " \n" +_erbout.concat "\n" +_erbout.concat "
    \n" +end; _erbout.concat "\n" +_erbout +end +Backtrace: C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:58:in `compile_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +script/server:3 + + +ActionView::TemplateError (compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:16: syntax error, unexpected $undefined, expecting '}' + "values"=>"<%=values.join(','); _erbout.concat "\",\n" + ^) on line #16 of home/index.rhtml: +13: <% +14: graph_variables = { "title"=>",{margin:10px;}", +15: "bar_3d"=>"60,#8E9BF0,#000000", +16: "values"=>"<%=values.join(',')%>", +17: "x_labels"=>"<%=labels.join(',')%>", +18: "y_max"=>"<%=max%>", +19: "y_min"=>"<%=min%>", + + app/views/home/index.rhtml:58:in `compile_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:11:09) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"7"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 7)  + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='7' + GROUP BY e.event DESC; + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +ERROR: compiling _run_rhtml_47app47views47home47index46rhtml RAISED compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:18: syntax error, unexpected $undefined, expecting '}' + "y_max"=>"<%=max; _erbout.concat "\",\n" + ^ +Function body: def _run_rhtml_47app47views47home47index46rhtml(local_assigns) +_erbout = ''; _erbout.concat "

    Team Performance Reporting

    \n" +_erbout.concat "\n" +form_tag '/home', :method=>:get do; _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((select 'player', 'id', @available_players.map { |p| [p.name, p.id] } ).to_s); _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((select 'game', 'id', @available_games.map { |g| [g.name, g.id] }).to_s); _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((submit_tag 'View').to_s); _erbout.concat "\n" +end; _erbout.concat "\n" +if @player and @game and @events.length>0; _erbout.concat "\n" +_erbout.concat "
    \n" +_erbout.concat " "; _erbout.concat((javascript_include_tag :defaults).to_s); _erbout.concat "\n" +_erbout.concat "\n" +_erbout.concat "
    \n" +_erbout.concat " "; +graph_variables = { "title"=>",{margin:10px;}", + "bar_3d"=>"60,#8E9BF0,#000000", + "values"=>"#{values.join(',')}", + "x_labels"=>"#{labels.join(',')}", + "y_max"=>"<%=max; _erbout.concat "\",\n" +_erbout.concat " \"y_min\"=>\""; _erbout.concat((min).to_s); _erbout.concat "\",\n" +_erbout.concat " \"y_ticks\"=>\"15,3,10\",\n" +_erbout.concat " #first is tick distance\n" +_erbout.concat " #second is distance from labels to chart\n" +_erbout.concat " #third is number of ticks.\n" +_erbout.concat "\n" +_erbout.concat " \"x_axis_3d\"=>\"16\",\n" +_erbout.concat " \"tool_tip\"=>\"#x_label#
    #val#s Average Time\",\n" +_erbout.concat " \"y_axis_colour\"=>\"#F0F0F0\",\n" +_erbout.concat " \"y_grid_colour\"=>\"#E9E9E9\",\n" +_erbout.concat " \"y_label_style\"=>\"12,#000000\",\n" +_erbout.concat ",\n" +_erbout.concat " \"x_axis_colour\"=>\"#6F6F7F\",\n" +_erbout.concat " \"x_grid_colour\"=>\"#E9E9E9\",\n" +_erbout.concat " \"x_label_style\"=>\"15,#000000\",\n" +_erbout.concat "\n" +_erbout.concat " \"bg_colour\"=>\"#F8F8FF\" } %>\n" +_erbout.concat " \n" +_erbout.concat((flashobject_tag "/flash/open-flash-chart.swf", + :size=>"850x400", :variables=>graph_variables ).to_s); _erbout.concat "\n" +_erbout.concat " \n" +_erbout.concat "\n" +_erbout.concat "
    \n" +end; _erbout.concat "\n" +_erbout +end +Backtrace: C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:58:in `compile_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +script/server:3 + + +ActionView::TemplateError (compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:18: syntax error, unexpected $undefined, expecting '}' + "y_max"=>"<%=max; _erbout.concat "\",\n" + ^) on line #18 of home/index.rhtml: +15: "bar_3d"=>"60,#8E9BF0,#000000", +16: "values"=>"#{values.join(',')}", +17: "x_labels"=>"#{labels.join(',')}", +18: "y_max"=>"<%=max%>", +19: "y_min"=>"<%=min%>", +20: "y_ticks"=>"15,3,10", +21: #first is tick distance + + app/views/home/index.rhtml:58:in `compile_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:11:32) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"7"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.016000) SELECT * FROM `games`  + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 7)  + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='7' + GROUP BY e.event DESC; + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +ERROR: compiling _run_rhtml_47app47views47home47index46rhtml RAISED compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:36: syntax error, unexpected ',', expecting '}' +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:37: syntax error, unexpected tASSOC, expecting kEND + "x_axis_colour"=>"#6F6F7F", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:37: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:38: syntax error, unexpected tASSOC, expecting kEND + "x_grid_colour"=>"#E9E9E9", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:38: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:39: syntax error, unexpected tASSOC, expecting kEND + "x_label_style"=>"15,#000000", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:39: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:41: syntax error, unexpected tASSOC, expecting kEND + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:41: syntax error, unexpected '}', expecting kEND + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" + ^ +Function body: def _run_rhtml_47app47views47home47index46rhtml(local_assigns) +_erbout = ''; _erbout.concat "

    Team Performance Reporting

    \n" +_erbout.concat "\n" +form_tag '/home', :method=>:get do; _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((select 'player', 'id', @available_players.map { |p| [p.name, p.id] } ).to_s); _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((select 'game', 'id', @available_games.map { |g| [g.name, g.id] }).to_s); _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((submit_tag 'View').to_s); _erbout.concat "\n" +end; _erbout.concat "\n" +if @player and @game and @events.length>0; _erbout.concat "\n" +_erbout.concat "
    \n" +_erbout.concat " "; _erbout.concat((javascript_include_tag :defaults).to_s); _erbout.concat "\n" +_erbout.concat "\n" +_erbout.concat "
    \n" +_erbout.concat " "; +labels = @events.map { |e| e['event'] } + values = @events.map { |e| e['average_time'] } + min = 0 + max = values.max + + +graph_variables = { "title"=>",{margin:10px;}", + "bar_3d"=>"60,#8E9BF0,#000000", + "values"=>"#{values.join(',')}", + "x_labels"=>"#{labels.join(',')}", + "y_max"=>max, + "y_min"=>min, + "y_ticks"=>"15,3,10", + #first is tick distance + #second is distance from labels to chart + #third is number of ticks. + + "x_axis_3d"=>"16", + "tool_tip"=>"#x_label#
    #val#s Average Time", + "y_axis_colour"=>"#F0F0F0", + "y_grid_colour"=>"#E9E9E9", + "y_label_style"=>"12,#000000", +, + "x_axis_colour"=>"#6F6F7F", + "x_grid_colour"=>"#E9E9E9", + "x_label_style"=>"15,#000000", + + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" +_erbout.concat " \n" +_erbout.concat((flashobject_tag "/flash/open-flash-chart.swf", + :size=>"850x400", :variables=>graph_variables ).to_s); _erbout.concat "\n" +_erbout.concat " \n" +_erbout.concat "\n" +_erbout.concat "
    \n" +end; _erbout.concat "\n" +_erbout +end +Backtrace: C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:59:in `compile_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +script/server:3 + + +ActionView::TemplateError (compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:36: syntax error, unexpected ',', expecting '}' +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:37: syntax error, unexpected tASSOC, expecting kEND + "x_axis_colour"=>"#6F6F7F", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:37: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:38: syntax error, unexpected tASSOC, expecting kEND + "x_grid_colour"=>"#E9E9E9", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:38: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:39: syntax error, unexpected tASSOC, expecting kEND + "x_label_style"=>"15,#000000", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:39: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:41: syntax error, unexpected tASSOC, expecting kEND + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:41: syntax error, unexpected '}', expecting kEND + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" + ^) on line #36 of home/index.rhtml: +33: "y_axis_colour"=>"#F0F0F0", +34: "y_grid_colour"=>"#E9E9E9", +35: "y_label_style"=>"12,#000000", +36: , +37: "x_axis_colour"=>"#6F6F7F", +38: "x_grid_colour"=>"#E9E9E9", +39: "x_label_style"=>"15,#000000", + + app/views/home/index.rhtml:59:in `compile_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:11:46) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"7"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 7)  + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='7' + GROUP BY e.event DESC; + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index +ERROR: compiling _run_rhtml_47app47views47home47index46rhtml RAISED compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:36: syntax error, unexpected ',', expecting '}' +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:37: syntax error, unexpected tASSOC, expecting kEND + "x_axis_colour"=>"#6F6F7F", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:37: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:38: syntax error, unexpected tASSOC, expecting kEND + "x_grid_colour"=>"#E9E9E9", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:38: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:39: syntax error, unexpected tASSOC, expecting kEND + "x_label_style"=>"15,#000000", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:39: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:41: syntax error, unexpected tASSOC, expecting kEND + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:41: syntax error, unexpected '}', expecting kEND + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" + ^ +Function body: def _run_rhtml_47app47views47home47index46rhtml(local_assigns) +_erbout = ''; _erbout.concat "

    Team Performance Reporting

    \n" +_erbout.concat "\n" +form_tag '/home', :method=>:get do; _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((select 'player', 'id', @available_players.map { |p| [p.name, p.id] } ).to_s); _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((select 'game', 'id', @available_games.map { |g| [g.name, g.id] }).to_s); _erbout.concat "\n" +_erbout.concat " "; _erbout.concat((submit_tag 'View').to_s); _erbout.concat "\n" +end; _erbout.concat "\n" +if @player and @game and @events.length>0; _erbout.concat "\n" +_erbout.concat "
    \n" +_erbout.concat " "; _erbout.concat((javascript_include_tag :defaults).to_s); _erbout.concat "\n" +_erbout.concat "\n" +_erbout.concat "
    \n" +_erbout.concat " "; +labels = @events.map { |e| e['event'] } + values = @events.map { |e| e['average_time'] } + min = 0 + max = values.max + + +graph_variables = { "title"=>",{margin:10px;}", + "bar_3d"=>"60,#8E9BF0,#000000", + "values"=>"#{values.join(',')}", + "x_labels"=>"#{labels.join(',')}", + "y_max"=>max, + "y_min"=>min, + "y_ticks"=>"15,3,10", + #first is tick distance + #second is distance from labels to chart + #third is number of ticks. + + "x_axis_3d"=>"16", + "tool_tip"=>"#x_label#
    #val#s Average Time", + "y_axis_colour"=>"#F0F0F0", + "y_grid_colour"=>"#E9E9E9", + "y_label_style"=>"12,#000000", +, + "x_axis_colour"=>"#6F6F7F", + "x_grid_colour"=>"#E9E9E9", + "x_label_style"=>"15,#000000", + + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" +_erbout.concat " \n" +_erbout.concat((flashobject_tag "/flash/open-flash-chart.swf", + :size=>"850x400", :variables=>graph_variables ).to_s); _erbout.concat "\n" +_erbout.concat " \n" +_erbout.concat "\n" +_erbout.concat "
    \n" +end; _erbout.concat "\n" +_erbout +end +Backtrace: C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:59:in `compile_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +script/server:3 + + +ActionView::TemplateError (compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:36: syntax error, unexpected ',', expecting '}' +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:37: syntax error, unexpected tASSOC, expecting kEND + "x_axis_colour"=>"#6F6F7F", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:37: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:38: syntax error, unexpected tASSOC, expecting kEND + "x_grid_colour"=>"#E9E9E9", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:38: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:39: syntax error, unexpected tASSOC, expecting kEND + "x_label_style"=>"15,#000000", + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:39: syntax error, unexpected ',', expecting kEND +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:41: syntax error, unexpected tASSOC, expecting kEND + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:41: syntax error, unexpected '}', expecting kEND + "bg_colour"=>"#F8F8FF" } ; _erbout.concat "\n" + ^) on line #36 of home/index.rhtml: +33: "y_axis_colour"=>"#F0F0F0", +34: "y_grid_colour"=>"#E9E9E9", +35: "y_label_style"=>"12,#000000", +36: , +37: "x_axis_colour"=>"#6F6F7F", +38: "x_grid_colour"=>"#E9E9E9", +39: "x_label_style"=>"15,#000000", + + app/views/home/index.rhtml:59:in `compile_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 10:11:53) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"game"=>{"id"=>"5"}, "commit"=>"View", "player"=>{"id"=>"7"}, "action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.015000) SELECT * FROM `games`  + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 7)  + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 5)  + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time + + FROM events AS e + INNER JOIN + plays AS p + ON e.play_id=p.id + WHERE p.game_id='5' + AND + p.player_id='7' + GROUP BY e.event DESC; + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering home/index + + +ActionView::TemplateError (undefined method `flashobject_tag' for #) on line #42 of home/index.rhtml: +39: +40: "bg_colour"=>"#F8F8FF" } %> +41: +42: <%=flashobject_tag "/flash/open-flash-chart.swf", +43: :size=>"850x400", :variables=>graph_variables %> +44: \n" +_erbout +end +Backtrace: C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:25:in `compile_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' +c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' +c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' +c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' +c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' +c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' +script/server:3 + + +ActionView::TemplateError (compile error +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/views/home/index.rhtml:5: syntax error, unexpected ',', expecting '}' +_erbout.concat((select 'game', 'id', @available_games.map { |g| [g.name, g.id], {:includeBlank=>true} } ).to_s); _erbout.concat "\n" + ^) on line #5 of home/index.rhtml: +2: +3:
    +4: <%=select 'player', 'id', @available_players.map { |p| [p.name, p.id] }, {:includeBlank=>true} %> +5: <%=select 'game', 'id', @available_games.map { |g| [g.name, g.id], {:includeBlank=>true} } %> +6:
    +7:
    +8:
    + + app/views/home/index.rhtml:25:in `compile_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:383:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:836:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:261:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1163:in `default_render' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1169:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:29:25) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.078000) SELECT * FROM `players`  + Game Load (0.015000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.015000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.35900 (2 reqs/sec) | Rendering: 0.07900 (22%) | DB: 0.10800 (30%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:32) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (25%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:32) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.07900 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (40%) | 200 OK [http://localhost/performance/5/1.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:33) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.11000 (9 reqs/sec) | Rendering: 0.03100 (28%) | DB: 0.01600 (14%) | 200 OK [http://localhost/performance/4/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:35) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"3"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '3') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 3 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07900 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.03100 (39%) | 200 OK [http://localhost/performance/3/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:36) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (25%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:36) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.015000) SHOW FIELDS FROM `events` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03000 (38%) | 200 OK [http://localhost/performance/5/1.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:38) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (23%) | 200 OK [http://localhost/performance/1/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:39) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (50%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:39) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.06300 (57%) | 200 OK [http://localhost/performance/5/1.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:40) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"2", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '2') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 2) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/2] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:40) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"2", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = '2') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 2) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.01500 (15%) | DB: 0.03100 (32%) | 200 OK [http://localhost/performance/5/2.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:41) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"3", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '3') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 3) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04600 (42%) | 200 OK [http://localhost/performance/5/3] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:42) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"3", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '3') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 3) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (49%) | 200 OK [http://localhost/performance/5/3.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:43) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"4", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '4') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 4) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.15600 (6 reqs/sec) | Rendering: 0.01600 (10%) | DB: 0.04700 (30%) | 200 OK [http://localhost/performance/5/4] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:44) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"4", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '4') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 4) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/4.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:45) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"6", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.01500 (16%) | DB: 0.01600 (17%) | 200 OK [http://localhost/performance/5/6] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:45) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"6", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.07900 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/6.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:46) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"6", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.23400 (4 reqs/sec) | Rendering: 0.01600 (6%) | DB: 0.03200 (13%) | 200 OK [http://localhost/performance/1/6] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:48) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"6", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (49%) | 200 OK [http://localhost/performance/5/6] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:29:48) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"6", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.157000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.26600 (3 reqs/sec) | Rendering: 0.01600 (6%) | DB: 0.15700 (59%) | 200 OK [http://localhost/performance/5/6.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:32:36) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.01600 (17%) | DB: 0.01600 (17%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:32:39) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.016000) SHOW FIELDS FROM `games` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.04700 (50%) | DB: 0.01600 (17%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:32:45) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (23%) | 200 OK [http://localhost/performance/1/1] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:32:56) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.015000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.04700 (21 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (31%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:00) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (19%) | 200 OK [http://localhost/performance/1/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:01) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:01) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (34%) | 200 OK [http://localhost/performance/5/7.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:15) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"6", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.14000 (7 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03000 (21%) | 200 OK [http://localhost/performance/5/6] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:15) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"6", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/6.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:31) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"5", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '5') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 5) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.14100 (7 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (21%) | 200 OK [http://localhost/performance/5/5] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:31) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"5", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '5') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 5) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/5.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:32) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"4", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '4') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 4) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.12500 (8 reqs/sec) | Rendering: 0.01600 (12%) | DB: 0.01600 (12%) | 200 OK [http://localhost/performance/5/4] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:32) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"4", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '4') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 4) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (51%) | 200 OK [http://localhost/performance/5/4.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:34) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"6", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (39%) | 200 OK [http://localhost/performance/5/6] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:33:34) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"6", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.01600 (17%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/6.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:33:57) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.031000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (39%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:34:03) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.03100 (49%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:34:08) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01500 (19%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:34:08) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.01500 (16%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/7.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:34:09) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"6", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.14000 (7 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (10%) | 200 OK [http://localhost/performance/5/6] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:34:09) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"6", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (32%) | 200 OK [http://localhost/performance/5/6.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:34:11) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"5", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '5') LIMIT 1 + Game Columns (0.031000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 5) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.14100 (7 reqs/sec) | Rendering: 0.01600 (11%) | DB: 0.03100 (21%) | 200 OK [http://localhost/performance/5/5] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:34:11) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"5", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '5') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 5) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (17%) | 200 OK [http://localhost/performance/5/5.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:34:14) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"4", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '4') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 4) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (29%) | 200 OK [http://localhost/performance/5/4] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:34:14) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"4", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '4') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 4) GROUP BY events.event DESC +Rendering performance/show + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.07900 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.03100 (39%) | 200 OK [http://localhost/performance/5/4.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:35:05) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.015000) SELECT * FROM `players`  + Game Load (0.015000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.03000 (38%) | 200 OK [http://localhost/] + + +Processing ApplicationController#show (for 127.0.0.1 at 2007-12-14 14:35:10) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"6", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + + +SyntaxError (C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/controllers/performance_controller.rb:30: syntax error, unexpected kEND, expecting '}'): + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_without_new_constant_marking' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:202:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:94:in `require_or_load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:248:in `load_missing_constant' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:453:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:465:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/inflector.rb:257:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/core_ext/string/inflections.rb:148:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/routing.rb:1423:in `recognize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:170:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:35:31) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.015000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.01500 (16%) | DB: 0.03100 (33%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:35:36) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (33%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:35:37) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show + + +ActionView::TemplateError (You have a nil object when you didn't expect it! +You might have expected an instance of Array. +The error occurred while evaluating nil.<=>) on line #5 of performance/show.text.erb: +2: labels = @events.map { |e| e['event'] } +3: values = @events.map { |e| e['average_time'] } +4: min = 0 +5: max = values.max +6: +7: +8: graph_variables = { "title"=>",{margin:10px;}", + + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:679:in `max' + app/views/performance/show.text.erb:5:in `each' + app/views/performance/show.text.erb:5:in `max' + app/views/performance/show.text.erb:5:in `_run_erb_47app47views47performance47show46text46erb' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:390:in `compile_and_render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:366:in `render_template' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_view/base.rb:316:in `render_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1110:in `render_for_file' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:914:in `render_with_no_layout' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/layout.rb:269:in `render_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:51:in `render' + app/controllers/performance_controller.rb:22:in `show' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:131:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:131:in `custom' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:156:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:156:in `respond' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:150:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:150:in `respond' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:107:in `respond_to' + app/controllers/performance_controller.rb:20:in `show' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:35:40) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/4/7] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:36:02) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.03100 (39%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:36:06) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (50%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:36:06) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.01600 (14%) | DB: 0.03100 (28%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:40:10) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.063000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.20300 (4 reqs/sec) | Rendering: 0.04700 (23%) | DB: 0.06300 (31%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:40:22) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.016000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.12500 (8 reqs/sec) | Rendering: 0.06200 (49%) | DB: 0.01600 (12%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:40:31) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.016000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.016000) SHOW FIELDS FROM `games` +Completed in 0.12500 (8 reqs/sec) | Rendering: 0.03100 (24%) | DB: 0.03200 (25%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:40:35) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:40:35) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04600 (58%) | 200 OK [http://localhost/performance/5/1.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:40:41) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.156000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.21900 (4 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.15600 (71%) | 200 OK [http://localhost/performance/4/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:40:43) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:40:43) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (50%) | 200 OK [http://localhost/performance/5/1.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:40:52) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.015000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.04700 (43%) | DB: 0.01500 (13%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:40:57) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (23%) | 200 OK [http://localhost/performance/1/7] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:41:04) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.016000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01500 (19%) | DB: 0.01600 (20%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:41:08) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (49%) | 200 OK [http://localhost/performance/1/1] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:41:43) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.031000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.03100 (39%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:41:51) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.015000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (19%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:41:55) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (25%) | 200 OK [http://localhost/performance/5/1] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:42:06) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.03200 (50%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:42:09) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.20300 (4 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04600 (22%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:42:09) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (41%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:42:17) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.04700 (49%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:42:20) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01500 (19%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:42:21) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:44:06) [GET] + Session ID: f03545068a08c922e7c5009ae99f0447 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.015000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.01600 (25%) | DB: 0.01500 (23%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:44:12) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:44:14) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.031000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04700 (49%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:45:48) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.25000 (4 reqs/sec) | Rendering: 0.12500 (50%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 14:45:55) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.03200 (41%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:45:58) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01500 (19%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:45:59) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.25000 (4 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (12%) | 200 OK [http://localhost/performance/5/7.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:46:07) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (17%) | 200 OK [http://localhost/performance/5/7.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:46:09) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"json", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.06300 (57%) | 200 OK [http://localhost/performance/5/7.json] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:46:14) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (41%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:47:21) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (39%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:47:21) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (39%) | 200 OK [http://localhost/performance/5/7.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:48:09) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` + + +IndexError (index 32745 out of string): + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/core_ext/hash/conversions.rb:110:in `[]=' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/core_ext/hash/conversions.rb:110:in `to_xml' + /app/controllers/performance_controller.rb:25:in `show' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:131:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:131:in `custom' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:156:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:156:in `respond' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:150:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:150:in `respond' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/mime_responds.rb:107:in `respond_to' + /app/controllers/performance_controller.rb:19:in `show' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:48:15) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04600 (48%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:48:21) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.031000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.17200 (5 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.07800 (45%) | 200 OK [http://localhost/performance/5/7.xml] + + +Processing ApplicationController#show (for 127.0.0.1 at 2007-12-14 14:48:33) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + + +SyntaxError (C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/controllers/performance_controller.rb:25: odd number list for Hash + }.to_xml(:root=>'player_performance_report', :except=>{:type}) } + ^): + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_without_new_constant_marking' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:202:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:94:in `require_or_load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:248:in `load_missing_constant' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:453:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:465:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/inflector.rb:257:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/core_ext/string/inflections.rb:148:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/routing.rb:1423:in `recognize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:170:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) + + +Processing ApplicationController#show (for 127.0.0.1 at 2007-12-14 14:48:35) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + + +SyntaxError (C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/controllers/performance_controller.rb:25: odd number list for Hash + }.to_xml(:root=>'player_performance_report', :except=>{:type}) } + ^): + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_without_new_constant_marking' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:202:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:94:in `require_or_load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:248:in `load_missing_constant' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:453:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:465:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/inflector.rb:257:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/core_ext/string/inflections.rb:148:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/routing.rb:1423:in `recognize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:170:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:48:41) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.17200 (5 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03000 (17%) | 200 OK [http://localhost/performance/5/7.xml] + + +Processing ApplicationController#show (for 127.0.0.1 at 2007-12-14 14:49:17) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + + +SyntaxError (C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/controllers/performance_controller.rb:25: syntax error, unexpected kTRUE, expecting tASSOC + }.to_xml(:root=>'player_performance_report', :skip_types=true) } + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/controllers/performance_controller.rb:29: syntax error, unexpected kEND, expecting '}'): + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_without_new_constant_marking' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:202:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:94:in `require_or_load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:248:in `load_missing_constant' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:453:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:465:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/inflector.rb:257:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/core_ext/string/inflections.rb:148:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/routing.rb:1423:in `recognize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:170:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) + + +Processing ApplicationController#show (for 127.0.0.1 at 2007-12-14 14:49:18) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + + +SyntaxError (C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/controllers/performance_controller.rb:25: syntax error, unexpected kTRUE, expecting tASSOC + }.to_xml(:root=>'player_performance_report', :skip_types=true) } + ^ +C:/projects/rubyreporting/ch 5 - reports on the internet/team_performance_web/app/controllers/performance_controller.rb:29: syntax error, unexpected kEND, expecting '}'): + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_without_new_constant_marking' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:203:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:202:in `load_file' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:94:in `require_or_load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:248:in `load_missing_constant' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:453:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:465:in `const_missing' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/inflector.rb:257:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/core_ext/string/inflections.rb:148:in `constantize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/routing.rb:1423:in `recognize' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:170:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:49:21) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.11000 (9 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04700 (42%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:49:36) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.031000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.11000 (9 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (28%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:49:37) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (34%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:49:40) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.031000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.15700 (6 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.06300 (40%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:49:50) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (17%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:49:51) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04700 (49%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 14:49:53) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04700 (50%) | 200 OK [http://localhost/performance/5/7.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:24) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.015000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04700 (49%) | 200 OK [http://localhost/performance/4/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:25) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"3"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '3') LIMIT 1 + Event Load (0.031000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 3 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04700 (50%) | 200 OK [http://localhost/performance/3/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:27) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (25%) | 200 OK [http://localhost/performance/4/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:27) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.01600 (25%) | DB: 0.01500 (24%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:28) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07900 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (40%) | 200 OK [http://localhost/performance/5/7.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:30) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/4/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:30) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"3"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '3') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 3 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07900 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/3/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:32) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.01600 (25%) | DB: 0.01500 (23%) | 200 OK [http://localhost/performance/4/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:32) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01500 (19%) | DB: 0.01500 (19%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:03:33) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 15:21:24) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.015000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.01600 (25%) | DB: 0.01500 (23%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:21:27) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03000 (38%) | 200 OK [http://localhost/performance/1/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:21:28) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.12500 (8 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (25%) | 200 OK [http://localhost/performance/1/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:21:29) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"2"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '2') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 2 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.10900 (9 reqs/sec) | Rendering: 0.01500 (13%) | DB: 0.06300 (57%) | 200 OK [http://localhost/performance/2/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:21:31) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"3"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '3') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 3 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/3/1] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-14 15:41:33) [GET] + Session ID: c3860c5a1b0757655a983abdb7f71891 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (25%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:41:37) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.21800 (4 reqs/sec) | Rendering: 0.01500 (6%) | DB: 0.01600 (7%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:41:38) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (24%) | 200 OK [http://localhost/performance/5/1.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 15:56:41) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (32%) | 200 OK [http://localhost/performance/5/1.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:01:13) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (17%) | 200 OK [http://localhost/performance/5/1.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:09:17) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/1.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:13:16) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"xml", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (17%) | 200 OK [http://localhost/performance/5/1.xml] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:13:17) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering performance/show +Completed in 0.26500 (3 reqs/sec) | Rendering: 0.01500 (5%) | DB: 0.03200 (12%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:13:19) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.14000 (7 reqs/sec) | Rendering: 0.01500 (10%) | DB: 0.01600 (11%) | 200 OK [http://localhost/performance/5/1.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:13:21) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering template within layouts/application +Rendering performance/show +Completed in 0.26600 (3 reqs/sec) | Rendering: 0.01600 (6%) | DB: 0.04700 (17%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:13:23) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.047000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.15600 (6 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.06300 (40%) | 200 OK [http://localhost/performance/5/1.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:13:36) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.016000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/4/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:13:36) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (19%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-14 16:13:36) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (17%) | 200 OK [http://localhost/performance/5/1.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:01:32) [GET] + Session ID: 227f148fd5b940a89739dfbf7e0d2306 + Parameters: {"action"=>"index", "controller"=>"home"} +WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. Please install the C-based MySQL library instead (gem install mysql). + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:01:34) [GET] + Session ID: 227f148fd5b940a89739dfbf7e0d2306 + Parameters: {"action"=>"index", "controller"=>"home"} + + +Errno::EBADF (Bad file descriptor - connect(2)): + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/vendor/mysql.rb:111:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/vendor/mysql.rb:111:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/vendor/mysql.rb:111:in `real_connect' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/mysql_adapter.rb:471:in `connect' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/mysql_adapter.rb:165:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/mysql_adapter.rb:88:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/mysql_adapter.rb:88:in `mysql_connection' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/connection_specification.rb:291:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/connection_specification.rb:291:in `connection=' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/connection_specification.rb:259:in `retrieve_connection' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/connection_specification.rb:78:in `connection' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:01:56) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + SQL (0.000000) SET SQL_AUTO_IS_NULL=0 + Player Load (0.375000) SELECT * FROM `players`  + Game Load (0.047000) SELECT * FROM `games`  +Rendering home/index + Player Columns (0.188000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.68700 (1 reqs/sec) | Rendering: 0.03100 (4%) | DB: 0.61000 (88%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:02:18) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.016000) SELECT * FROM `players`  + Game Load (0.015000) SELECT * FROM `games`  +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.015000) SHOW FIELDS FROM `games` +Completed in 0.32800 (3 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.06200 (18%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:03:00) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.03200 (50%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:03:08) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"2"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.078000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '2') LIMIT 1 + Event Load (0.235000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 2 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.54700 (1 reqs/sec) | Rendering: 0.03100 (5%) | DB: 0.32900 (60%) | 200 OK [http://localhost/performance/2/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:03:11) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (33%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:03:12) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01500 (19%) | DB: 0.01500 (19%) | 200 OK [http://localhost/performance/5/1.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:03:34) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.016000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01500 (19%) | DB: 0.01600 (20%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:03:39) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/1/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:03:40) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.20300 (4 reqs/sec) | Rendering: 0.03100 (15%) | DB: 0.04700 (23%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:03:42) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.28200 (3 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04600 (16%) | 200 OK [http://localhost/performance/5/1.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:04:43) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.015000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01500 (19%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:04:47) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 1) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01600 (20%) | DB: 0.01500 (19%) | 200 OK [http://localhost/performance/1/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:04:48) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "method"=>:get, "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (49%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:04:49) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/1.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:11:10) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.01600 (17%) | DB: 0.01600 (17%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:11:14) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.29700 (3 reqs/sec) | Rendering: 0.03200 (10%) | DB: 0.01600 (5%) | 200 OK [http://localhost/performance/1/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:11:15) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.016000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.015000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (49%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:11:16) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (19%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:17:08) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.03100 (39%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:17:10) [GET] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.031000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.03100 (32%) | DB: 0.03100 (32%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 10:17:13) [POST] + Session ID: BAh7BiIKZmxhc2hJQzonQWN0aW9uQ29udHJvbGxlcjo6Rmxhc2g6OkZsYXNo%0ASGFzaHsABjoKQHVzZWR7AA%3D%3D--ad6db6ecc6fd2767d0ab4c22f4d71c31030cb416 + Parameters: {"player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"4"} + + +ActionController::InvalidAuthenticityToken (ActionController::InvalidAuthenticityToken): + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/request_forgery_protection.rb:73:in `verify_authenticity_token' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:469:in `send!' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:469:in `call' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:441:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:716:in `run_before_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:695:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (unprocessable_entity) +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 10:28:44) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (20%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:03:55) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} +WARNING: You're using the Ruby-based MySQL library that ships with Rails. This library is not suited for production. Please install the C-based MySQL library instead (gem install mysql). + SQL (0.000000) SET SQL_AUTO_IS_NULL=0 + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.04700 (50%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:03:55) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.03100 (49%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:04:01) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07900 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (39%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:04:02) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.53200 (1 reqs/sec) | Rendering: 0.03200 (6%) | DB: 0.04800 (9%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:11:54) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.03100 (49%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:11:57) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.21900 (4 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (14%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:11:58) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01600 (20%) | 200 OK [http://localhost/performance/5/7.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:12:51) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.031000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 7) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04700 (50%) | 200 OK [http://localhost/performance/4/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:12:52) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (41%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:12:53) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/7.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:14:17) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"4"} + Player Columns (0.031000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.015000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '4') LIMIT 1 + Event Load (0.047000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 4 AND plays.player_id= 7) GROUP BY events.event ASC +Rendering performance/show +Completed in 0.60900 (1 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.10800 (17%) | 200 OK [http://localhost/performance/4/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:14:18) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.047000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event ASC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.67200 (1 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.07900 (11%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:14:19) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event ASC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.11000 (9 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (29%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:14:47) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.016000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (50%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:14:52) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.016000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07900 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03200 (40%) | 200 OK [http://localhost/performance/5/7] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:14:52) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"format"=>"text", "player_id"=>"7", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '7') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 7) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (39%) | 200 OK [http://localhost/performance/5/7.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:23:13) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.015000) SHOW FIELDS FROM `games` +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (49%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:23:39) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.016000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.031000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.76500 (1 reqs/sec) | Rendering: 0.26600 (34%) | DB: 0.04700 (6%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:24:02) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.01500 (24%) | DB: 0.00000 (0%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:24:38) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.016000) SELECT * FROM `players`  + Game Load (0.016000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.01500 (24%) | DB: 0.03200 (51%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:25:09) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.015000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.01600 (25%) | DB: 0.01500 (23%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:25:12) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"Click here to select a game"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 'Click here to select a game') LIMIT 1 + + +RuntimeError (Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id): + /app/controllers/performance_controller.rb:12:in `show' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:25:12) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"Click here to select a player", "action"=>"show", "controller"=>"performance", "game_id"=>"Click here to select a game"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = 'Click here to select a player') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 'Click here to select a game') LIMIT 1 + + +RuntimeError (Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id): + /app/controllers/performance_controller.rb:12:in `show' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:25:14) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"Click here to select a game"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = 'Click here to select a game') LIMIT 1 + + +RuntimeError (Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id): + /app/controllers/performance_controller.rb:12:in `show' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:1168:in `perform_action_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:697:in `call_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:689:in `perform_action_without_benchmark' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/1.8/benchmark.rb:293:in `measure' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/benchmarking.rb:68:in `perform_action_without_rescue' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/rescue.rb:199:in `perform_action_without_caching' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:678:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/connection_adapters/abstract/query_cache.rb:33:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/activerecord-2.0.1/lib/active_record/query_cache.rb:8:in `cache' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/caching.rb:677:in `perform_action' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `send' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:524:in `process_without_filters' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/filters.rb:685:in `process_without_session_management_support' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/session_management.rb:123:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/base.rb:388:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:171:in `handle_request' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:115:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:126:in `dispatch_cgi' + c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/dispatcher.rb:9:in `dispatch' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:78:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `synchronize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/rails.rb:76:in `process' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:618:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:617:in `process_client' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:736:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `initialize' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `new' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel.rb:720:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:271:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `each' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/configurator.rb:270:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:127:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/lib/mongrel/command.rb:211:in `run' + c:/ruby/lib/ruby/gems/1.8/gems/mongrel-1.0.1-mswin32/bin/mongrel_rails:243 + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:489:in `load' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/servers/mongrel.rb:64 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:342:in `new_constants_in' + c:/ruby/lib/ruby/gems/1.8/gems/activesupport-2.0.1/lib/active_support/dependencies.rb:496:in `require' + c:/ruby/lib/ruby/gems/1.8/gems/rails-2.0.1/lib/commands/server.rb:39 + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `gem_original_require' + c:/ruby/lib/ruby/site_ruby/1.8/rubygems/custom_require.rb:27:in `require' + script/server:3 + +Rendering c:/ruby/lib/ruby/gems/1.8/gems/actionpack-2.0.1/lib/action_controller/templates/rescues/layout.erb (internal_server_error) +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:25:16) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.57800 (1 reqs/sec) | Rendering: 0.29700 (51%) | DB: 0.03100 (5%) | 200 OK [http://localhost/performance/5/1] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:25:17) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"format"=>"text", "player_id"=>"1", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '1') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 1) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/1.text] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:25:26) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.016000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.01600 (25%) | DB: 0.01600 (25%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:25:35) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.015000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.06200 (16 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (24%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:26:23) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.000000) SHOW FIELDS FROM `players` + Game Columns (0.016000) SHOW FIELDS FROM `games` +Completed in 0.06300 (15 reqs/sec) | Rendering: 0.01500 (23%) | DB: 0.01600 (25%) | 200 OK [http://localhost/] +HomeController: missing default helper path home_helper + + +Processing HomeController#index (for 127.0.0.1 at 2007-12-17 11:26:43) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"action"=>"index", "controller"=>"home"} + Player Load (0.000000) SELECT * FROM `players`  + Game Load (0.000000) SELECT * FROM `games`  +Rendering template within layouts/application +Rendering home/index + Player Columns (0.016000) SHOW FIELDS FROM `players` + Game Columns (0.000000) SHOW FIELDS FROM `games` +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01500 (19%) | DB: 0.01600 (20%) | 200 OK [http://localhost/] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:34:45) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"2", "action"=>"show", "controller"=>"performance", "game_id"=>"3"} + Player Columns (0.015000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '2') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.015000) SELECT * FROM `games` WHERE (`games`.`id` = '3') LIMIT 1 + Event Load (0.016000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 3 AND plays.player_id= 2) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.04600 (58%) | 200 OK [http://localhost/performance/3/2] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:34:46) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"2", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '2') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 2) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09300 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.01500 (16%) | 200 OK [http://localhost/performance/5/2] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:34:46) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"format"=>"text", "player_id"=>"2", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '2') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 2) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.09400 (10 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/5/2.text] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:34:51) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"6", "action"=>"show", "controller"=>"performance", "game_id"=>"1"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '1') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 1 AND plays.player_id= 6) GROUP BY events.event DESC +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.01500 (19%) | DB: 0.00000 (0%) | 200 OK [http://localhost/performance/1/6] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:34:53) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"player_id"=>"6", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.016000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.000000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC + Event Columns (0.015000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.07800 (12 reqs/sec) | Rendering: 0.00000 (0%) | DB: 0.03100 (39%) | 200 OK [http://localhost/performance/5/6] +PerformanceController: missing default helper path performance_helper + + +Processing PerformanceController#show (for 127.0.0.1 at 2007-12-17 11:34:54) [GET] + Session ID: BAh7BzoMY3NyZl9pZCIlY2M3MDkyMWI3MWJkZTI2ODE0ZWQ1ODc1OTVhNmRk%0AMGYiCmZsYXNoSUM6J0FjdGlvbkNvbnRyb2xsZXI6OkZsYXNoOjpGbGFzaEhh%0Ac2h7AAY6CkB1c2VkewA%3D--c7bc856b2ca206e7c81bce871ace211415b454ca + Parameters: {"format"=>"text", "player_id"=>"6", "action"=>"show", "controller"=>"performance", "game_id"=>"5"} + Player Columns (0.000000) SHOW FIELDS FROM `players` + Player Load (0.000000) SELECT * FROM `players` WHERE (`players`.`id` = '6') LIMIT 1 + Game Columns (0.016000) SHOW FIELDS FROM `games` + Game Load (0.000000) SELECT * FROM `games` WHERE (`games`.`id` = '5') LIMIT 1 + Event Load (0.000000) SELECT event, AVG(time)/1000 as average_time FROM `events` INNER JOIN plays ON events.play_id=plays.id WHERE (plays.game_id = 5 AND plays.player_id= 6) GROUP BY events.event DESC + Event Columns (0.000000) SHOW FIELDS FROM `events` +Rendering performance/show +Completed in 0.25000 (4 reqs/sec) | Rendering: 0.07900 (31%) | DB: 0.01600 (6%) | 200 OK [http://localhost/performance/5/6.text] diff --git a/chapter05/team_performance_web/log/production.log b/chapter05/team_performance_web/log/production.log new file mode 100644 index 0000000..e69de29 diff --git a/chapter05/team_performance_web/log/server.log b/chapter05/team_performance_web/log/server.log new file mode 100644 index 0000000..e69de29 diff --git a/chapter05/team_performance_web/log/test.log b/chapter05/team_performance_web/log/test.log new file mode 100644 index 0000000..e69de29 diff --git a/chapter05/team_performance_web/public/.htaccess b/chapter05/team_performance_web/public/.htaccess new file mode 100644 index 0000000..d9d211c --- /dev/null +++ b/chapter05/team_performance_web/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

    Application error

    Rails application failed to start properly" diff --git a/chapter05/team_performance_web/public/404.html b/chapter05/team_performance_web/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/chapter05/team_performance_web/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
    +

    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/chapter05/team_performance_web/public/422.html b/chapter05/team_performance_web/public/422.html new file mode 100644 index 0000000..b54e4a3 --- /dev/null +++ b/chapter05/team_performance_web/public/422.html @@ -0,0 +1,30 @@ + + + + + + + The change you wanted was rejected (422) + + + + + +
    +

    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/chapter05/team_performance_web/public/500.html b/chapter05/team_performance_web/public/500.html new file mode 100644 index 0000000..0e9c14f --- /dev/null +++ b/chapter05/team_performance_web/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong (500) + + + + + +
    +

    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/chapter05/team_performance_web/public/dispatch.cgi b/chapter05/team_performance_web/public/dispatch.cgi new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter05/team_performance_web/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter05/team_performance_web/public/dispatch.fcgi b/chapter05/team_performance_web/public/dispatch.fcgi new file mode 100644 index 0000000..5d9b8ec --- /dev/null +++ b/chapter05/team_performance_web/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!c:/ruby/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) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/chapter05/team_performance_web/public/dispatch.rb b/chapter05/team_performance_web/public/dispatch.rb new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter05/team_performance_web/public/dispatch.rb @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter05/team_performance_web/public/favicon.ico b/chapter05/team_performance_web/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/chapter05/team_performance_web/public/flash/open-flash-chart.swf b/chapter05/team_performance_web/public/flash/open-flash-chart.swf new file mode 100644 index 0000000..af9f8cf Binary files /dev/null and b/chapter05/team_performance_web/public/flash/open-flash-chart.swf differ diff --git a/chapter05/team_performance_web/public/images/rails.png b/chapter05/team_performance_web/public/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/chapter05/team_performance_web/public/images/rails.png differ diff --git a/chapter05/team_performance_web/public/javascripts/application.js b/chapter05/team_performance_web/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/chapter05/team_performance_web/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/chapter05/team_performance_web/public/javascripts/controls.js b/chapter05/team_performance_web/public/javascripts/controls.js new file mode 100644 index 0000000..fbc4418 --- /dev/null +++ b/chapter05/team_performance_web/public/javascripts/controls.js @@ -0,0 +1,963 @@ +// Copyright (c) 2005-2007 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) +// 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 +// 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, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +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; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + 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); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + 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; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + 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 = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + 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; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + 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++ + 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()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + 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]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + 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 { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + 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) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// 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. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + 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) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + 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)) + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML; + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw 'Server returned an invalid collection representation.'; + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); diff --git a/chapter05/team_performance_web/public/javascripts/dragdrop.js b/chapter05/team_performance_web/public/javascripts/dragdrop.js new file mode 100644 index 0000000..ccf4a1e --- /dev/null +++ b/chapter05/team_performance_web/public/javascripts/dragdrop.js @@ -0,0 +1,972 @@ +// Copyright (c) 2005-2007 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 is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(Object.isUndefined(Effect)) + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || { }); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if(Object.isArray(containment)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + 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; + } 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( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + 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); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + 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); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + 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; + } + }, + + reset: function() { + 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) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + 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); + } 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)]; + // Mozilla-based browsers fire successive mousemove events with + // 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.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) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + 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, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + 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}); + } + }); + + 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 + + this.options = options; + 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)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + 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) + 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); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + 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 ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(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)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this.element._originallyAbsolute) + Position.relativize(this.element); + delete this.element._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + 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') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + 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); + 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]) + }.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)) + } else { + p = p.map( function(v) { + 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); + this.scrollInterval = null; + 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; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + 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) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + 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)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + 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({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // 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] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // 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); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (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.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); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + 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); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, 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); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + 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, + parent: parent, + 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) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + 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); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var 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]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).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) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/chapter05/team_performance_web/public/javascripts/effects.js b/chapter05/team_performance_web/public/javascripts/effects.js new file mode 100644 index 0000000..65aed23 --- /dev/null +++ b/chapter05/team_performance_web/public/javascripts/effects.js @@ -0,0 +1,1120 @@ +// Copyright (c) 2005-2007 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/ + +// 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)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.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()) + ); + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + 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), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + 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 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || { }); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + 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) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + 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); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + 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]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(), + max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1] > max ? max : elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()) } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + 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(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + 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, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ 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(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // 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, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + 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, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + 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' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// 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, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + 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({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +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, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + 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 ) + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + 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] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + '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'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(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]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + 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; + }); + if (!styles.opacity) styles.set('opacity', element.getOpacity()); + return styles; + }; +}; + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element) + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + 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( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); diff --git a/chapter05/team_performance_web/public/javascripts/flashobject.js b/chapter05/team_performance_web/public/javascripts/flashobject.js new file mode 100644 index 0000000..e7edd42 --- /dev/null +++ b/chapter05/team_performance_web/public/javascripts/flashobject.js @@ -0,0 +1,8 @@ +/** + * SWFObject v1.5: Flash Player detection and embed - http://blog.deconcept.com/swfobject/ + * + * SWFObject is (c) 2007 Geoff Stearns and is released under the MIT License: + * http://www.opensource.org/licenses/mit-license.php + * + */ +if(typeof deconcept=="undefined"){var deconcept=new Object();}if(typeof deconcept.util=="undefined"){deconcept.util=new Object();}if(typeof deconcept.SWFObjectUtil=="undefined"){deconcept.SWFObjectUtil=new Object();}deconcept.SWFObject=function(_1,id,w,h,_5,c,_7,_8,_9,_a){if(!document.getElementById){return;}this.DETECT_KEY=_a?_a:"detectflash";this.skipDetect=deconcept.util.getRequestParameter(this.DETECT_KEY);this.params=new Object();this.variables=new Object();this.attributes=new Array();if(_1){this.setAttribute("swf",_1);}if(id){this.setAttribute("id",id);}if(w){this.setAttribute("width",w);}if(h){this.setAttribute("height",h);}if(_5){this.setAttribute("version",new deconcept.PlayerVersion(_5.toString().split(".")));}this.installedVer=deconcept.SWFObjectUtil.getPlayerVersion();if(!window.opera&&document.all&&this.installedVer.major>7){deconcept.SWFObject.doPrepUnload=true;}if(c){this.addParam("bgcolor",c);}var q=_7?_7:"high";this.addParam("quality",q);this.setAttribute("useExpressInstall",false);this.setAttribute("doExpressInstall",false);var _c=(_8)?_8:window.location;this.setAttribute("xiRedirectUrl",_c);this.setAttribute("redirectUrl","");if(_9){this.setAttribute("redirectUrl",_9);}};deconcept.SWFObject.prototype={useExpressInstall:function(_d){this.xiSWFPath=!_d?"expressinstall.swf":_d;this.setAttribute("useExpressInstall",true);},setAttribute:function(_e,_f){this.attributes[_e]=_f;},getAttribute:function(_10){return this.attributes[_10];},addParam:function(_11,_12){this.params[_11]=_12;},getParams:function(){return this.params;},addVariable:function(_13,_14){this.variables[_13]=_14;},getVariable:function(_15){return this.variables[_15];},getVariables:function(){return this.variables;},getVariablePairs:function(){var _16=new Array();var key;var _18=this.getVariables();for(key in _18){_16[_16.length]=key+"="+_18[key];}return _16;},getSWFHTML:function(){var _19="";if(navigator.plugins&&navigator.mimeTypes&&navigator.mimeTypes.length){if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","PlugIn");this.setAttribute("swf",this.xiSWFPath);}_19="0){_19+="flashvars=\""+_1c+"\"";}_19+="/>";}else{if(this.getAttribute("doExpressInstall")){this.addVariable("MMplayerType","ActiveX");this.setAttribute("swf",this.xiSWFPath);}_19="";_19+="";var _1d=this.getParams();for(var key in _1d){_19+="";}var _1f=this.getVariablePairs().join("&");if(_1f.length>0){_19+="";}_19+="";}return _19;},write:function(_20){if(this.getAttribute("useExpressInstall")){var _21=new deconcept.PlayerVersion([6,0,65]);if(this.installedVer.versionIsValid(_21)&&!this.installedVer.versionIsValid(this.getAttribute("version"))){this.setAttribute("doExpressInstall",true);this.addVariable("MMredirectURL",escape(this.getAttribute("xiRedirectUrl")));document.title=document.title.slice(0,47)+" - Flash Player Installation";this.addVariable("MMdoctitle",document.title);}}if(this.skipDetect||this.getAttribute("doExpressInstall")||this.installedVer.versionIsValid(this.getAttribute("version"))){var n=(typeof _20=="string")?document.getElementById(_20):_20;n.innerHTML=this.getSWFHTML();return true;}else{if(this.getAttribute("redirectUrl")!=""){document.location.replace(this.getAttribute("redirectUrl"));}}return false;}};deconcept.SWFObjectUtil.getPlayerVersion=function(){var _23=new deconcept.PlayerVersion([0,0,0]);if(navigator.plugins&&navigator.mimeTypes.length){var x=navigator.plugins["Shockwave Flash"];if(x&&x.description){_23=new deconcept.PlayerVersion(x.description.replace(/([a-zA-Z]|\s)+/,"").replace(/(\s+r|\s+b[0-9]+)/,".").split("."));}}else{if(navigator.userAgent&&navigator.userAgent.indexOf("Windows CE")>=0){var axo=1;var _26=3;while(axo){try{_26++;axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash."+_26);_23=new deconcept.PlayerVersion([_26,0,0]);}catch(e){axo=null;}}}else{try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");}catch(e){try{var axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");_23=new deconcept.PlayerVersion([6,0,21]);axo.AllowScriptAccess="always";}catch(e){if(_23.major==6){return _23;}}try{axo=new ActiveXObject("ShockwaveFlash.ShockwaveFlash");}catch(e){}}if(axo!=null){_23=new deconcept.PlayerVersion(axo.GetVariable("$version").split(" ")[1].split(","));}}}return _23;};deconcept.PlayerVersion=function(_29){this.major=_29[0]!=null?parseInt(_29[0]):0;this.minor=_29[1]!=null?parseInt(_29[1]):0;this.rev=_29[2]!=null?parseInt(_29[2]):0;};deconcept.PlayerVersion.prototype.versionIsValid=function(fv){if(this.majorfv.major){return true;}if(this.minorfv.minor){return true;}if(this.rev=0;i--){_2f[i].style.display="none";for(var x in _2f[i]){if(typeof _2f[i][x]=="function"){_2f[i][x]=function(){};}}}};if(deconcept.SWFObject.doPrepUnload){if(!deconcept.unloadSet){deconcept.SWFObjectUtil.prepUnload=function(){__flash_unloadHandler=function(){};__flash_savedUnloadHandler=function(){};window.attachEvent("onunload",deconcept.SWFObjectUtil.cleanupSWFs);};window.attachEvent("onbeforeunload",deconcept.SWFObjectUtil.prepUnload);deconcept.unloadSet=true;}}if(!document.getElementById&&document.all){document.getElementById=function(id){return document.all[id];};}var getQueryParamValue=deconcept.util.getRequestParameter;var FlashObject=deconcept.SWFObject;var SWFObject=deconcept.SWFObject; \ No newline at end of file diff --git a/chapter05/team_performance_web/public/javascripts/prototype.js b/chapter05/team_performance_web/public/javascripts/prototype.js new file mode 100644 index 0000000..546f9fe --- /dev/null +++ b/chapter05/team_performance_web/public/javascripts/prototype.js @@ -0,0 +1,4225 @@ +/* Prototype JavaScript framework, version 1.6.0.1 + * (c) 2005-2007 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/ + * + *--------------------------------------------------------------------------*/ + +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/) + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + document.createElement('div').__proto__ && + document.createElement('div').__proto__ !== + document.createElement('form').__proto__ + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +/* Based on Alex Arnell's inheritance implementation. */ +var Class = { + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + 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++) + 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); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "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() } + }); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; + +Object.extend = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; + +Object.extend(Object, { + inspect: function(object) { + try { + if (Object.isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } 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(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (Object.isElement(object)) return; + + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (!Object.isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + }, + + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(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; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({ }, object); + }, + + isElement: function(object) { + return object && object.nodeType == 1; + }, + + isArray: function(object) { + return object && object.constructor === Array; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; + } +}); + +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + return names.length == 1 && !names[0] ? [] : names; + }, + + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Function.prototype.defer = Function.prototype.delay.curry(0.01); + +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) { } + } + + return returnValue; + } +}; + +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; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + } finally { + this.currentlyExecuting = false; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.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) { + this.gsub(pattern, iterator); + return String(this); + }, + + truncate: function(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() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + 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) { + 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]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(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; + }, + + capitalize: function() { + 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(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + 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); + }); + 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}'); + }, + + isJSON: function() { + 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); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + 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,'>'); + } +}); + +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); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }.bind(this)); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = { + each: function(iterator, context) { + var index = 0; + iterator = iterator.bind(context); + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var index = -number, slices = [], array = this.toArray(); + 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; + var result = true; + this.each(function(value, index) { + result = result && !!iterator(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function(iterator, context) { + iterator = iterator.bind(context); + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(filter, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator(value, index)); + }); + return results; + }, + + include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(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); + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(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; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator, context) { + iterator = iterator.bind(context); + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + 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 +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length, 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; + } +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(Object.isArray(value) ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + 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]; + }, + + uniq: function(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) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + 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 $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + 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]); + } else { + array.push(arguments[i]); + } + } + 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; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +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)); + } + + 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); + } + }, + + set: function(key, value) { + return this._object[key] = value; + }, + + get: function(key) { + return this._object[key]; + }, + + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, + + toObject: function() { + 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; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return values.map(toQueryPair.curry(key)).join('&'); + } + return toQueryPair(key, values); + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } + } +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); + +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); + +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + 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'; + } + + 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)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +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) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + 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++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + 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); + +Element.cache = { }; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + 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; + }, + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + 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 content, t, range; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + t = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + t.insert(element, content); + continue; + } + + content = Object.toHTML(content); + + range = element.ownerDocument.createRange(); + t.initializeRange(element, range); + t.insert(element, range.createContextualFragment(content.stripScripts())); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + 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(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $(element).getElementsBySelector("*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(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]; + }, + + 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]; + }, + + 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]; + }, + + 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]; + }, + + select: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + 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); + } + + while (element = element.parentNode) + if (element == originalAncestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + 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) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (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); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + 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; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // 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(); + } + + // 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'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Element.Methods.identify.counter = 1; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + + +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) { + switch (style) { + 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()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + 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; + } + ); + }); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + 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(); + }, + title: function(element) { + return element.title; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.clone(Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc').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, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + 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 { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +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) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +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 }); + 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); + } + }, + top: { + adjacency: 'afterBegin', + insert: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + initializeRange: function(element, range) { + range.selectNodeContents(element); + range.collapse(true); + } + }, + bottom: { + adjacency: 'beforeEnd', + insert: 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); + } + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + this.bottom.initializeRange = this.top.initializeRange; + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.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; + } +}; + +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; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName, property, value; + + // 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(); + } + + 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); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = { }; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + 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; + }, + + getHeight: function() { + return this.getDimensions().height; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + 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(); + }, + + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + return false; + + return true; + }, + + compileMatcher: function() { + if (this.shouldUseXPath()) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + 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; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + 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; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + 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); + } + } + } + } + + 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; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}); + +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); + }, + 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); + }, + 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}-')]" + }, + 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 ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + 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 }); + } + } + } + }, + + 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(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return Selector.operators[matches[2]](nodeValue, matches[3]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = undefined; + return nodes; + }, + + // 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++; + } + }, + + // 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); + }, + + // 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); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + 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; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // 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); + }, + + 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); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + 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); + } + 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; + }, + + 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; + }, + + 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); + } + }, + + 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); + }, + + // 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; + }); + }, + + // 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; + }, + + '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; + }, + + '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; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + '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; + } + }, + + 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; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + 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; + } +}); + +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; + }; +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + 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 && + 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); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + 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; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, index) { + if (Object.isUndefined(index)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, value, single = !Object.isArray(index); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + value = this.optionValue(opt); + if (single) { + if (value == index) { + opt.selected = true; + return; + } + } + else opt.selected = index.include(value); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + 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); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + 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); + } +}); + +Event.Methods = (function() { + var isButton; + + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; + + } else if (Prototype.Browser.WebKit) { + 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) { + 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) }, + + element: function(event) { + var node = Event.extend(event).target; + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + }, + + 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); + }, + + 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)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; + } + }; +})(); + +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) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + 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), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + 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; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event) + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + 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"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize() +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer, fired = false; + + function fireContentLoadedEvent() { + if (fired) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + fired = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write(" +EOF + end + end + end + end +end + +ActionView::Base.class_eval do + include ActionView::Helpers::FlashObjectHelper +end + +ActionView::Helpers::AssetTagHelper.register_javascript_include_default 'flashobject' \ No newline at end of file diff --git a/chapter06/get_average_price.rb b/chapter06/get_average_price.rb new file mode 100644 index 0000000..bf2fe88 --- /dev/null +++ b/chapter06/get_average_price.rb @@ -0,0 +1,187 @@ +require 'rexml/document' +require 'net/http' +require 'uri' + +require 'hpricot' + +(puts "usage: #{$0} keyword1,keyword2 seller1,seller2"; exit) unless ARGV.length==2 + +keywords=ARGV.shift.split(',') +sellers=ARGV.shift.split(',') +sellers << nil # This is an "all sellers" line; note that you can delete this line if + # you do not want your output to display an average for all sellers. + +path_to_pdflatex = '' # Insert your path to pdflatex here. + +$ebay_config = { + :ebay_address=> 'rest.api.ebay.com', + :request_token => '', + :user_id => '' } #Insert your request token and user ID here . + +class String + def latex_escape() + + replacements= { '\\' =>'$\backslash$', + '$'=>'\$', + '%'=>'\%', + '&'=>'\&', + '_'=>'\_', + '~'=>'*~*', + '#'=>'\#', + '{'=>'$\{$', + '}'=>'$\}$', + } + self.gsub(/[#{replacements.keys.join('|')}]/) do |match| + replacements[match] + end + + end +end + +class EBaySearch + + def self.get_average_price(keyword, seller_id=nil) + + params = { + # Authorization information... + 'RequestToken' => $ebay_config[:request_token], + 'RequestUserId' => $ebay_config[:user_id], + + # Function name. + 'CallName' => 'GetSearchResults', + + # Search parameters + 'Query'=>URI.escape(keyword), # Note that only + # some parameters are escaped. + # This is because the RequestToken + # is already escaped for URLs, so + # re-URLencoding would cause + # problems; otherwise, we could just + # run all of these values a URI.escaping + # loop. + + 'ItemTypeFilter' => 3, # Search all items, + # including fixed price items. + + 'SearchInDescription'=>0,# Do not search inside of + # description. + + # Return data parameters + 'Schema' =>1, # Use eBay's new style XML schema instead of + # of the old, deprecated one. + 'EntriesPerPage' =>100, # Return at most 100 entries - + # note that for performance reasons, + # this code does not iterate through the pages, + # so it will only calculate the average of + # the first hundred items on eBay. + 'PageNumber' =>1 + + } + + if seller_id # If the caller does not pass a seller id, + # this function will search across all sellers. + + params['IncludeSellers'] = URI.escape(seller_id) + + # eBay usernames are currently limited to alphanumeric characters + # and underscores, so this may not be necessary, but it's escaped + # just in case. + end + + url= "/restapi?"<< params.map{|param, value| "#{param}=#{value}"}.join("&") + + response = Net::HTTP.get_response($ebay_config[:ebay_address], url) + hpricot_doc = Hpricot.XML(response.body) + + total_price= 0.0 + result_count=0 + + (hpricot_doc/:SearchResultItem).each do |item| # Iterate through each SearchResultItem + price_element = (item/:CurrentPrice) # Find the CurrentPrice element for each element + if price_element # If it has a price... + total_price = total_price + price_element.first.innerHTML.to_f #... then pull out the + # inside of the element, + # convert it to a float, + # and add it to the total. + result_count = result_count + 1 + end + end + + if result_count > 0 + average_price = (total_price/result_count) + else + average_price = nil + end + + [result_count, average_price] # Return the number of results and average price as an array. + + end + +end + +temporary_latex_file='average_price_report.tex' # This filename will also control + # the output file name - the file + # will be named average_price_report.pdf + + + + +latex_source=' +\documentclass[8pt]{article} + +\begin{document} + +\huge % Switch to huge size and +\textbf{Competitor Average Price Report} % print a header at the + % top of the page. + +\vspace{0.1in} % Add a small amount of + % whitespace between the + % header and the table + +\normalsize % Switch back to normal size. + +\begin{tabular}{llll} % Start a table with four + % left aligned columns. + +\textbf{Item}& % Four headers, each in bold, +\textbf{Seller}& % with labels for each column. +\textbf{Count}& +\textbf{Average Price}\\\\ + +' + +keywords.each do |keyword| + first=true + sellers.each do |seller| + total_items, average_price = *EBaySearch.get_average_price(keyword, seller) + + latex_source << " + \\textbf{#{first ? keyword.latex_escape : ' '}} & + #{seller ? seller.latex_escape : 'First 100 eBay Results'} & + #{total_items} & + \\#{average_price ? ('$%0.2f' % average_price) : ''} + \\\\ " + + # Note that the character & is the marker for the end of a cell, and + # that the sequence \\\\ is two escaped backslashes, which marks + # the end of the row. + + first=false # This marker controls whether to redisplay the keyword. + # For visual formatting reasons, each keyword is only + # shown once. + end +end + +latex_source << ' +\end{tabular} +\end{document}' + +fh = File.open(temporary_latex_file, 'w') +fh.puts latex_source +fh.close +puts "Searched #{keywords.length} keywords and #{sellers.length} sellers for a total of #{sellers.length*keywords.length} eBay searches." +puts `"#{path_to_pdflatex}" #{temporary_latex_file} --quiet` # Runs PDFLatex with +# the --quiet switch, which eliminates much of the chatter it usually displays. +# It will still display errors, however. +puts "Wrote report to average_price_report.pdf" diff --git a/chapter07/paypal_expense_report.rb b/chapter07/paypal_expense_report.rb new file mode 100644 index 0000000..bf5ea99 --- /dev/null +++ b/chapter07/paypal_expense_report.rb @@ -0,0 +1,124 @@ +require 'active_record' +require 'fastercsv' +require 'yaml' +require 'markaby' + +(puts "usage: #{$0} mysql_hostname mysql_username " << + "mysql_password database_name"; exit) unless ARGV.length==4 + +mysql_hostname, +mysql_username, +mysql_password, +mysql_database= *ARGV + +ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => mysql_hostname, + :username => mysql_username, + :password => mysql_password, + :database => mysql_database) # Establish a connection to the database. + + +class PaypalTransaction < ActiveRecord::Base +end + +first = true +c = {} + + +sql = " +SELECT WEEK(p1.date) + 1 as week_number, + YEAR(p1.date) as year, + COALESCE((SELECT SUM(ABS(p2.gross)) + FROM paypal_transactions as p2 + WHERE (WEEK(p2.date) = WEEK(p1.date) + AND YEAR(p2.date) = YEAR(p1.date) + AND WEEKDAY(p2.date) IN (5,6) + AND p2.gross<0 + AND p2.status='Completed') + ),0) as weekend_amount, + + COALESCE((SELECT SUM(abs(p3.gross)) + FROM paypal_transactions as p3 + WHERE (WEEK(p3.date) = WEEK(p1.date) + AND YEAR(p3.date) = YEAR(p1.date) + AND WEEKDAY(p3.date) NOT IN (5,6) + AND p3.gross<0 + AND p3.status='Completed') + ),0) as weekday_amount + + FROM paypal_transactions as p1 + + GROUP BY YEAR(date) ASC, WEEK(date) ASC; +" + +weeks = [] + +max_gross=0.0 + +PaypalTransaction.find_by_sql(sql).each do |week| + # First, if the weekday is the highest total spending we've seen so far, + # we'll keep that value to calibrate the size of the graph... + + max_gross = week.weekday_amount.to_f if week.weekday_amount.to_f > max_gross + + # ... and if the weekend spending is the highest, we'll use that: + + max_gross = week.weekend_amount.to_f if week.weekend_amount.to_f > max_gross + + # We'll add a hash with the week number ,the year, + # the weekday spending, and the weekend spending to the weeks array: + + weeks << { :week_number=>week.week_number.to_i, + :year=>week.year.to_i, + :weekday_amount=>week.weekday_amount.to_f, + :weekend_amount=>week.weekend_amount.to_f + } +end + +mab = Markaby::Builder.new() do + html do + head do + title 'PayPal Spending Report' + style :type => "text/css" do %[ + .weekday_bar { display:block; background-color: blue; } + .weekend_bar { display:block; background-color: red; } + %] + end + end + body do + h1 '' + table do + weeks.each do |week| + tr do + th :style=>"vertical-align:top;" do + p "Week \##{week[:week_number]}, #{week[:year]}" + end + td do + div :class=>:weekday_bar, + :style=>"width:" << ((week[:weekday_amount] / + max_gross * 199 ) + 1).to_s do + " " + end + span "Week - $#{'%0.2f' % week[:weekday_amount]}" + end + end + tr do + td "" + td do + div :class=>:weekend_bar, + :style=>"width: " << ((week[:weekend_amount] / + max_gross * 199) + 1 ).to_s do + ' ' + end + span "Weekend - $#{'%0.2f' % week[:weekend_amount]}" + end + end + end + end + end + end +end + +puts mab + diff --git a/chapter07/paypal_load_data.rb b/chapter07/paypal_load_data.rb new file mode 100644 index 0000000..88ba1a4 --- /dev/null +++ b/chapter07/paypal_load_data.rb @@ -0,0 +1,82 @@ +require 'active_record' +require 'fastercsv' + +(puts "usage: #{$0} csv_filename"; + exit) unless ARGV.length==1 + +paypal_source_file = ARGV.shift + +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :username => 'root', + :password => '', + :database => 'paypal') + + +class PaypalTransaction < ActiveRecord::Base +end + +class String + def columnize + self.strip.downcase.gsub(/[^a-z0-9_]/, '_') + end +end + +max_gross=0 +date_fields = ['date' ] +float_fields = ['gross', 'fee', 'net'] +cols = {} +weeks = [] + +first = true + +FasterCSV.foreach(paypal_source_file) do |line| + + if first + first=false + line.each_with_index do |field_name, field_position| + next if field_name.strip =='' + + cols[field_name.columnize] = field_position + end + + unless PaypalTransaction.table_exists? + ActiveRecord::Schema.define do + create_table PaypalTransaction.table_name do |t| + cols.each do |col, col_index| + + if date_fields.include?(col) + t.column col, :date + elsif float_fields.include?(col) + t.column col, :float + else + t.column col, :string + end + + end + end + end + end + + else + if PaypalTransaction.count_by_sql("SELECT COUNT(*) + FROM paypal_transactions + WHERE transaction_id + ='" << + line[cols[ + 'Transaction ID'.columnize + ] ] << + "';")==0 + PaypalTransaction.new do |transaction| + cols.each do |field_name, field_position| + + transaction .send("#{field_name }=", line[field_position]) + + end + transaction .save + end + end + end +end + diff --git a/chapter08/calculate_rewards.rb b/chapter08/calculate_rewards.rb new file mode 100644 index 0000000..8102ddc --- /dev/null +++ b/chapter08/calculate_rewards.rb @@ -0,0 +1,79 @@ +require 'active_record' +require 'benchmark' +require 'erubis' + + +ActiveRecord::Base.establish_connection( + :adapter => 'mysql', + :host => 'localhost', + :port => 3307, + :username => 'root', # This is the default username and password + :password => '', # for MySQL, but note that if you have a + # different username and password, + # you should change it. + :database => 'sugarcrm') + + +path_to_ps2pdf = 'C:\Program Files\MiKTeX 2.6\miktex\bin\ps2pdf.bat' #Insert your path to ps2pdf here. +path_to_html2ps = 'C:\Perl\bin\perl.exe" "C:\Program Files\html2ps\html2ps' #Insert your path to html2ps here. + +# Windows can't run perl scripts directly, +# so Windows users should preface their html2ps +# path with their perl path, so that it looks +# something like this: +# path_to_html2ps = 'C:\perl\bin\perl.exe" "c:\path\to\html2ps' +# +# Note the double qoutes after perl.exe and before the script filename; +# this ensures that the string is interpolated like this: +# "c:\perl\bin\perl.exe" "c:\path\to\html2ps" +# +# Without the extra double quotes, Windows will look for an program +# named "C:\perl\bin\perl c:\path\to\html2ps", and since once +# does not exist, it will cause problems. + +class User < ActiveRecord::Base + has_many :meetings, :foreign_key=>:assigned_user_id + def reward + Reward.find(:first, + :conditions=>['meeting_count < ? ', + self.meetings.count], + :order=>'meeting_count DESC', + :limit=>1) + end +end + +class Meeting < ActiveRecord::Base +end + +class Reward < ActiveRecord::Base +end + +users = User.find(:all, + :conditions=>['not is_admin'], + :order=>'last_name ASC, first_name ASC' + ) + +eruby_object= Erubis::Eruby.new(File.read('rewards_report_template.rhtml')) +context = { :users=>users } +html = eruby_object.evaluate(context) + +ps_source = '' + +open('|"'+path_to_html2ps+'"', 'wb+') do |process_handle| + process_handle.puts html + process_handle.close_write + ps_source = process_handle.read +end + +pdf_source = '' + +open('|"' + path_to_ps2pdf +'" - -', 'wb+') do |process_handle| + process_handle.puts ps_source + process_handle.close_write + pdf_source = process_handle.read +end + +File.open('report.pdf','wb+') do |pdf_file_handle| + pdf_file_handle.puts pdf_source +end + diff --git a/chapter09/example_fidelity_file.csv b/chapter09/example_fidelity_file.csv new file mode 100644 index 0000000..edd9a85 --- /dev/null +++ b/chapter09/example_fidelity_file.csv @@ -0,0 +1,5 @@ +Date,Account Number,Symbol,Description,Quantity,Previous Price,Most Recent Price,Previous Value,Most Recent Value,Change $,Change %,Cost ,Change Since Purchase ($),Change Since Purchase (%),Type +09/07/2007,"998877665",GOOG,"GOOGLE",0.500,2.000,2.000,0.40,0.20,n/a,n/a,n/a,n/a,n/a,Cash +09/07/2007,"998877665",JAVA,"SUN MICROSYSTEMS",33.110,54.200,65.100,2061.54,1003.68,-50.06,-2.705,458.33,924.85,104.03,Cash +09/07/2007,"998877665",RHT,"RED HAT",0.400,1.000,1.000,0.20,0.10,n/a,n/a,n/a,n/a,n/a,Cash + diff --git a/chapter09/xml_server.rb b/chapter09/xml_server.rb new file mode 100644 index 0000000..ad33515 --- /dev/null +++ b/chapter09/xml_server.rb @@ -0,0 +1,76 @@ +require 'mongrel' +require 'fastercsv' +require 'remarkably/engines/xml' + +(puts "usage: #{$0} csv_file_1 csv_file_2..."; exit) unless ARGV.length >=1 + +class StocksList + def initialize + @symbols = [] # holds our list of symbols + end + + def load_csv(files) + @symbols = [] + valid_symbol_labels = ['Symbol'] + files.each do |file| + rows = FasterCSV.parse(open(file)) + first_row= rows.shift + symbol_index = nil + + first_row.each_with_index do |label, index| + if(valid_symbol_labels.include?(label)) + symbol_index = index + break + end + end + if symbol_index.nil? + puts "Can't find symbol index on first row in file #{file}." + else + @symbols = @symbols + rows.map { |r| r[symbol_index] + }.delete_if { |s| s.nil? or s =='' } + end + end + end + + include Remarkably::Common + def to_xml + xml do + symbols do + @symbols.each do |s| + symbol s + end + end + end.to_s + end + + +end + + +class StocksXMLHandler < Mongrel::HttpHandler + def initialize(stocks_list) + @stocks_list = stocks_list + super() + end + def process(request, response) + response.start(200) do |headers, output_stream| + headers["Content-Type"] = "text/plain" + + output_stream.write(@stocks_list.to_xml) + end + end +end + + +stocks_list = StocksList.new +stocks_list.load_csv(ARGV) + +interface = '127.0.0.1' +port = '3000' + +mongrel_server = Mongrel::HttpServer.new( interface, port) +mongrel_server.register("/", StocksXMLHandler.new(stocks_list)) +puts "** Fidelity XML server started on #{interface}:#{port}!" +mongrel_server.run.join + + diff --git a/chapter09/xml_ticker.rb b/chapter09/xml_ticker.rb new file mode 100644 index 0000000..f932e30 --- /dev/null +++ b/chapter09/xml_ticker.rb @@ -0,0 +1,84 @@ +require 'net/http' +require 'yahoofinance' +require 'fox16' +require 'xmlsimple' + +(puts 'Usage: ruby xml_ticker.rb HOSTNAME PORT_NUMBER'; exit) unless ARGV.length==2 + +class FXTickerApp + include Fox + def initialize(hostname, port_number, + font_size = 100, quote_frequency=1) + + #quote_frequency is in minutes + + @hostname = hostname + @port_number = port_number + @quote_frequency = quote_frequency + + load_symbols_from_server + + @fox_application=FXApp.new + @main_window=FXMainWindow.new(@fox_application, "Stock Ticker ", + nil, nil, DECOR_ALL | LAYOUT_EXPLICIT) + @tickerlabel = FXLabel.new(@main_window, get_label_text, + nil, 0, LAYOUT_EXPLICIT) + + @tickerlabel.font.setFont "helvetica [bitstream],#{font_size}" + + def scroll_timer(sender, sel, ptr) + self.scroll_label + @fox_application.addTimeout(50, method(:scroll_timer)) + end + @fox_application.addTimeout(50, method(:scroll_timer)) + + def update_label_timer(sender, sel, ptr) + @tickerlabel.text = self.get_label_text + @fox_application.addTimeout(1000*60*@quote_frequency, + method(:update_label_timer)) + end + @fox_application.addTimeout(1000*60*@quote_frequency, + method(:update_label_timer)) + + + @fox_application.create + end + def load_symbols_from_server + + xml_body = Net::HTTP.new(@hostname, @port_number).get('/').body + + xml = XmlSimple.xml_in(xml_body) + + @symbols = xml['symbols'][0]['symbol'] + end + + def scroll_label + if(@tickerlabel.x < -@tickerlabel.width) + @tickerlabel.move(@main_window.width , @tickerlabel.y) + else + @tickerlabel.move(@tickerlabel.x - 3, @tickerlabel.y) + end + end + + def get_label_text + label_text = '' + YahooFinance::get_standard_quotes( @symbols ).each do |symbol, quote| + label_text << "#{symbol}: #{quote.lastTrade} ... " + end + label_text + end + + def go + + @main_window.show( PLACEMENT_SCREEN ) + + @fox_application.run + end +end + +hostname = ARGV.shift +port_number = ARGV.shift + +my_app = FXTickerApp.new(hostname, port_number, 240) +my_app.go + diff --git a/chapter10/apachetracker/README b/chapter10/apachetracker/README new file mode 100644 index 0000000..a1db73c --- /dev/null +++ b/chapter10/apachetracker/README @@ -0,0 +1,203 @@ +== Welcome to Rails + +Rails is a web-application and persistence framework that includes everything +needed to create database-backed web-applications according to the +Model-View-Control pattern of separation. This pattern splits the view (also +called the presentation) into "dumb" templates that are primarily responsible +for inserting pre-built data in between HTML tags. The model contains the +"smart" domain objects (such as Account, Product, Person, Post) that holds all +the business logic and knows how to persist themselves to a database. The +controller handles the incoming requests (such as Save New Account, Update +Product, Show Post) by manipulating the model and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting Started + +1. At the command prompt, start a new Rails application using the rails command + and your application name. Ex: rails myapp + (If you've downloaded Rails in a complete tgz or zip, this step is already done) +2. Change directory into myapp and start the web server: script/server (run with --help for options) +3. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!" +4. Follow the guidelines to start developing your application + + +== Web Servers + +By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise +Rails will use WEBrick, the webserver that ships with Ruby. When you run script/server, +Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures +that you can always get up and running quickly. + +Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is +suitable for development and deployment of Rails applications. If you have Ruby Gems installed, +getting up and running with mongrel is as easy as: gem install mongrel. +More info at: http://mongrel.rubyforge.org + +If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than +Mongrel and WEBrick and also suited for production use, but requires additional +installation and currently only works well on OS X/Unix (Windows users are encouraged +to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from +http://www.lighttpd.net. + +And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby +web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not +for production. + +But of course its also possible to run Rails on any platform that supports FCGI. +Apache, LiteSpeed, IIS are just a few. For more information on FCGI, +please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI + + +== Debugging Rails + +Sometimes your application goes wrong. Fortunately there are a lot of tools that +will help you debug it and get it back on the rails. + +First area to check is the application log files. Have "tail -f" commands running +on the server.log and development.log. Rails will automatically display debugging +and runtime information to these files. Debugging info will also be shown in the +browser on requests from 127.0.0.1. + +You can also log your own messages directly into the log file from your code using +the Ruby logger class from inside your controllers. Example: + + class WeblogController < ActionController::Base + def destroy + @weblog = Weblog.find(params[:id]) + @weblog.destroy + logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!") + end + end + +The result will be a message in your log file along the lines of: + + Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1 + +More information on how to use the logger is at http://www.ruby-doc.org/core/ + +Also, Ruby documentation can be found at http://www.ruby-lang.org/ including: + +* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/ +* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide) + +These two online (and free) books will bring you up to speed on the Ruby language +and also on programming in general. + + +== Debugger + +Debugger support is available through the debugger command when you start your Mongrel or +Webrick server with --debugger. This means that you can break out of execution at any point +in the code, investigate and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find(:all) + debugger + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the server window. Here you can do things like: + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a debugger" + => "hello from a debugger" + +...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you enter "cont" + + +== Console + +You can interact with the domain model by starting the console through script/console. +Here you'll have all parts of the application configured, just like it is when the +application is running. You can inspect domain models, change values, and save to the +database. Starting the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like script/console production. + +To reload your controllers and models after launching the console run reload! + + +== Description of Contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from ApplicationController + which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descend from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblogs/index.erb for the WeblogsController#index action. All views use eRuby + syntax. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the common + header/footer method of wrapping views. In your views, define a layout using the + layout :default and create a file named default.erb. Inside default.erb, + call <% yield %> to render the view using this layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are generated + for you automatically when using script/generate for controllers. Helpers can be used to + wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +db + Contains the database schema in schema.rb. db/migrate contains all + the sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when generated + using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong under controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains subdirectories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. This should be + set as the DOCUMENT_ROOT of your web server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the script/generate scripts, template + test files will be generated for you and placed in this directory. + +vendor + External libraries that the application depends on. Also includes the plugins subdirectory. + This directory is in the load path. diff --git a/chapter10/apachetracker/Rakefile b/chapter10/apachetracker/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/chapter10/apachetracker/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/chapter10/apachetracker/app/controllers/application.rb b/chapter10/apachetracker/app/controllers/application.rb new file mode 100644 index 0000000..e31a47d --- /dev/null +++ b/chapter10/apachetracker/app/controllers/application.rb @@ -0,0 +1,10 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + helper :all # include all helpers, all the time + + # See ActionController::RequestForgeryProtection for details + # Uncomment the :secret if you're not using the cookie session store + protect_from_forgery # :secret => 'dff02b630f882eabd0346dffbcd5fad1' +end diff --git a/chapter10/apachetracker/app/controllers/home_controller.rb b/chapter10/apachetracker/app/controllers/home_controller.rb new file mode 100644 index 0000000..95f2992 --- /dev/null +++ b/chapter10/apachetracker/app/controllers/home_controller.rb @@ -0,0 +1,4 @@ +class HomeController < ApplicationController + def index + end +end diff --git a/chapter10/apachetracker/app/controllers/logs_controller.rb b/chapter10/apachetracker/app/controllers/logs_controller.rb new file mode 100644 index 0000000..3a24227 --- /dev/null +++ b/chapter10/apachetracker/app/controllers/logs_controller.rb @@ -0,0 +1,55 @@ +require 'benchmark' +require 'tempfile' +require 'csv' +require 'ftools' + +class LogsController < ApplicationController + def create + flash[:notice] = "Uploaded new file... \n" + count=0 + if params[:upload_with_active_record] + real_time_elapsed = Benchmark.realtime do + LogParser.new.parse_io_stream(params[:log][:file]) do |l| + Hit.create( + :user_agent => l['HTTP_USER_AGENT'], + :path_info => l['PATH_INFO'], + :remote_addr => l['REMOTE_ADDR'], + :http_referrer => l['HTTP_REFERER'], + :status => l['STATUS'], + :visited_at => l['DATETIME'] + ) + end + count = count + 1 + end + + elsif params[:upload_with_active_record_extensions] + real_time_elapsed = Benchmark.realtime do + columns = [:user_agent, :path_info, :remote_addr, :http_referrer, :status, :visited_at] + values = [] + LogParser.new.parse_io_stream(params[:log][:file]) do |l| + values << + [ + l['HTTP_USER_AGENT'], + l['PATH_INFO'], + l['REMOTE_ADDR'], + l['HTTP_REFERER'], + l['STATUS'], + Date.parse(l['DATETIME']) + ] + count = count + 1 + end + Hit.import columns, values if values.length>0 + end + end + + flash[:notice] << " #{count} uploaded, #{Hit.count} total\n" + flash[:notice] << " #{'%0.2f' % real_time_elapsed} elapsed, #{(count.to_f/real_time_elapsed)*60} records per minute ." + redirect_to :controller=>:home, :action=>:index + + end + def destroy + Hit.delete_all + flash[:notice] = 'Logs cleared!' + redirect_to :controller=>:home, :action=>:index + end +end diff --git a/chapter10/apachetracker/app/controllers/report_controller.rb b/chapter10/apachetracker/app/controllers/report_controller.rb new file mode 100644 index 0000000..7adc2b8 --- /dev/null +++ b/chapter10/apachetracker/app/controllers/report_controller.rb @@ -0,0 +1,101 @@ +require 'tempfile' +require 'gruff' +require 'scruffy' + +class ReportController < ApplicationController + + def show + @rails_pdf_inline = true + + @graph_files ={ 'Graph of per-sale costs'=>get_sale_graph_tempfile, + 'Graph of total visitors from each advertiser'=>get_visitor_graph_tempfile + } + render :action=>:graphs, :layout=>nil + @graph_files.each do |key, filename| + File.unlink filename + end + end + + protected + + def get_tempfile_name(core) + File.join(RAILS_ROOT, "tmp/#{core}_#{request.env['REMOTE_ADDR']}_#{Time.now.to_i}.jpg") + end + + def get_visitor_graph_tempfile + graph_tempfile_name = get_tempfile_name('visitor_graph') + + advertisers = Advertiser.find(:all) + + g = Gruff::Bar.new(1000) + g.title = "Advertisting Traffic Report" + g.legend_font_size = 16 + advertisers.each do |a| + vistor_addresses = Hit.find(:all, + :group=>'remote_addr', + :conditions=>['http_referrer= ? ', + a.referrer_url] + ).map { |h| h.remote_addr } + + sale_count = Hit.count('remote_addr', + :conditions=>['remote_addr IN (?) + AND + path_info LIKE "/cart/checkout%"', + vistor_addresses]) + + g.data(a.company_name, [ vistor_addresses.length, sale_count ] ) + end + + g.labels = {0 => 'Visitors', 1 => 'Visitors With One or More Purchases' } + + g.write(graph_tempfile_name) + graph_tempfile_name + end + + def get_sale_graph_tempfile + graph_tempfile_name = get_tempfile_name('sale_graph_tempfile') + + advertisers = Advertiser.find(:all) + + g = Gruff::Bar.new(1000) + + g.title = "Cost Per Sale Report" + g.legend_font_size = 16 + g.y_axis_label = 'Cost (USD)' + + advertisers.each do |a| + vistor_addresses = Hit.find(:all, + :group=>'remote_addr', + :conditions=>['http_referrer= ? ', + a.referrer_url] + ).map { |h| h.remote_addr } + total_cost = vistor_addresses.length*a.cost_per_click + + sale_count = Hit.count('remote_addr', + :conditions=>['remote_addr IN (?) + AND + path_info LIKE "/cart/checkout%"', + vistor_addresses]) + cost_per_sale = total_cost / sale_count + + g.data(a.company_name, [a.cost_per_click, cost_per_sale ] ) + end + + g.labels = {0 => 'Cost Per Click', 1 => 'Cost Per Sale' } + g.minimum_value = 0 + + g.write(graph_tempfile_name) + graph_tempfile_name + end + + def rescue_action_in_public(exception) + headers.delete("Content-Disposition") + super + end + + def rescue_action_locally(exception) + headers.delete("Content-Disposition") + super + end + +end diff --git a/chapter10/apachetracker/app/helpers/application_helper.rb b/chapter10/apachetracker/app/helpers/application_helper.rb new file mode 100644 index 0000000..22a7940 --- /dev/null +++ b/chapter10/apachetracker/app/helpers/application_helper.rb @@ -0,0 +1,3 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper +end diff --git a/chapter10/apachetracker/app/helpers/frontpage_helper.rb b/chapter10/apachetracker/app/helpers/frontpage_helper.rb new file mode 100644 index 0000000..f86545a --- /dev/null +++ b/chapter10/apachetracker/app/helpers/frontpage_helper.rb @@ -0,0 +1,2 @@ +module FrontpageHelper +end diff --git a/chapter10/apachetracker/app/helpers/logs_helper.rb b/chapter10/apachetracker/app/helpers/logs_helper.rb new file mode 100644 index 0000000..99736f0 --- /dev/null +++ b/chapter10/apachetracker/app/helpers/logs_helper.rb @@ -0,0 +1,2 @@ +module LogsHelper +end diff --git a/chapter10/apachetracker/app/helpers/reports_helper.rb b/chapter10/apachetracker/app/helpers/reports_helper.rb new file mode 100644 index 0000000..cae2f09 --- /dev/null +++ b/chapter10/apachetracker/app/helpers/reports_helper.rb @@ -0,0 +1,2 @@ +module ReportsHelper +end diff --git a/chapter10/apachetracker/app/models/advertiser.rb b/chapter10/apachetracker/app/models/advertiser.rb new file mode 100644 index 0000000..358eee8 --- /dev/null +++ b/chapter10/apachetracker/app/models/advertiser.rb @@ -0,0 +1,2 @@ +class Advertiser < ActiveRecord::Base +end diff --git a/chapter10/apachetracker/app/models/hit.rb b/chapter10/apachetracker/app/models/hit.rb new file mode 100644 index 0000000..98a53b1 --- /dev/null +++ b/chapter10/apachetracker/app/models/hit.rb @@ -0,0 +1,2 @@ +class Hit < ActiveRecord::Base +end diff --git a/chapter10/apachetracker/app/views/home/index.html.erb b/chapter10/apachetracker/app/views/home/index.html.erb new file mode 100644 index 0000000..306a00c --- /dev/null +++ b/chapter10/apachetracker/app/views/home/index.html.erb @@ -0,0 +1,16 @@ +

    Apache Log Analyzer

    + +

    Would you like to...

    +

    ... upload a New Apache Log?

    +<% form_tag( { :controller => :logs } , {:multipart => true} )do %> + <%=file_field 'log', 'file' %> + <%=submit_tag 'Upload with Active Record', :name=>:upload_with_active_record%> + <%=submit_tag 'Upload with Active Record Extensions', :name=>:upload_with_active_record_extensions%> + + <%end%> +

    ... <%=link_to 'clear your old logs?', {:controller=>:logs}, {:method=>:delete} %>

    +

    + +

    ... <%=link_to 'view the cost per sale report?', :controller=>:report, :action=>:show, :format=>'pdf'%>

    +

    + diff --git a/chapter10/apachetracker/app/views/layouts/application.html.erb b/chapter10/apachetracker/app/views/layouts/application.html.erb new file mode 100644 index 0000000..58d4f67 --- /dev/null +++ b/chapter10/apachetracker/app/views/layouts/application.html.erb @@ -0,0 +1,21 @@ + + + + + <%= @page_title || 'Apache Log Tracker'%> + <%= stylesheet_link_tag 'apachetracker' %> + <%= javascript_include_tag 'prototype', 'scriptacolous', 'effects'%> + + + + <%flash.each do |key, text|%> +
    + <%=text.gsub("\n", "\n
    ")%> +
    + + + <% end%> + <%= @content_for_layout %> + + + diff --git a/chapter10/apachetracker/app/views/report/graphs.pdf.rpdf b/chapter10/apachetracker/app/views/report/graphs.pdf.rpdf new file mode 100644 index 0000000..3b9eb5d --- /dev/null +++ b/chapter10/apachetracker/app/views/report/graphs.pdf.rpdf @@ -0,0 +1,10 @@ +pdf.select_font "Times-Roman" + +figure_number = 1 +@graph_files.each do |title, graph| + pdf.image graph + pdf.text "Figure #{figure_number} of #{@graph_files.length } - #{title}", :left=>6, :font_size=>12 + figure_number = figure_number + 1 +end + + diff --git a/chapter10/apachetracker/config/boot.rb b/chapter10/apachetracker/config/boot.rb new file mode 100644 index 0000000..b71c198 --- /dev/null +++ b/chapter10/apachetracker/config/boot.rb @@ -0,0 +1,108 @@ +# Don't change this file! +# Configure your app in config/environment.rb and config/environments/*.rb + +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) + +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.exists?(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" + 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' + + unless rubygems_version >= '0.9.4' + $stderr.puts %(Rails requires RubyGems >= 0.9.4 (you have #{rubygems_version}). Please `gem update --system` and try again.) + exit 1 + end + + rescue LoadError + $stderr.puts %(Rails requires RubyGems >= 0.9.4. 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 + +# All that for this: +Rails.boot! diff --git a/chapter10/apachetracker/config/database.yml b/chapter10/apachetracker/config/database.yml new file mode 100644 index 0000000..b21f262 --- /dev/null +++ b/chapter10/apachetracker/config/database.yml @@ -0,0 +1,36 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install mysql +# On MacOS X: +# gem install mysql -- --include=/usr/local/lib +# On Windows: +# gem install mysql +# Choose the win32 build. +# Install MySQL and put its /bin directory on your path. +# +# 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: apachetracker_development + username: root + password: + host: localhost + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: apachetracker_test + username: root + password: + host: localhost + +production: + adapter: mysql + database: apachetracker_production + username: root + password: + host: localhost diff --git a/chapter10/apachetracker/config/environment.rb b/chapter10/apachetracker/config/environment.rb new file mode 100644 index 0000000..cba179b --- /dev/null +++ b/chapter10/apachetracker/config/environment.rb @@ -0,0 +1,65 @@ +# Be sure to restart your web server when you modify this file. + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Specifies gem version of Rails to use when vendor/rails is not present + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here + + # Skip frameworks you're not going to use (only works if using vendor/rails) + # config.frameworks -= [ :action_web_service, :action_mailer ] + + # Only load the plugins named here, by default all plugins in vendor/plugins are loaded + # config.plugins = %W( exception_notification ssl_requirement ) + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Use the database for sessions instead of the file system + # (create the session table with 'rake db:sessions:create') + # config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # Add new inflection rules using the following format + # (all these examples are active by default): + # Inflector.inflections do |inflect| + # inflect.plural /^(ox)$/i, '\1en' + # inflect.singular /^(ox)en/i, '\1' + # inflect.irregular 'person', 'people' + # inflect.uncountable %w( fish sheep ) + # end + config.action_controller.session = { + :session_key => '_x_session', + :secret => 'a17879c64a0010672156bdd104a152812849a8066fcfeb816723ffd7b89715c4afbc9e9d4d78115cbd1dc0ef23f8c8cc0b600607363f667748e809d1a8e49b2b' + } + + + # See Rails::Configuration for more options +end + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register "application/x-mobile", :mobile + +# Include your application configuration below +# diff --git a/chapter10/apachetracker/config/environments/development.rb b/chapter10/apachetracker/config/environments/development.rb new file mode 100644 index 0000000..09a451f --- /dev/null +++ b/chapter10/apachetracker/config/environments/development.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_view.debug_rjs = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false \ No newline at end of file diff --git a/chapter10/apachetracker/config/environments/production.rb b/chapter10/apachetracker/config/environments/production.rb new file mode 100644 index 0000000..cb295b8 --- /dev/null +++ b/chapter10/apachetracker/config/environments/production.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors, bad email addresses will be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/chapter10/apachetracker/config/environments/test.rb b/chapter10/apachetracker/config/environments/test.rb new file mode 100644 index 0000000..58850a7 --- /dev/null +++ b/chapter10/apachetracker/config/environments/test.rb @@ -0,0 +1,22 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Disable request forgery protection in test environment +config.action_controller.allow_forgery_protection = 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 diff --git a/chapter10/apachetracker/config/initializers/inflections.rb b/chapter10/apachetracker/config/initializers/inflections.rb new file mode 100644 index 0000000..09158b8 --- /dev/null +++ b/chapter10/apachetracker/config/initializers/inflections.rb @@ -0,0 +1,10 @@ +# Be sure to restart your server when you modify this file. + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end diff --git a/chapter10/apachetracker/config/initializers/mime_types.rb b/chapter10/apachetracker/config/initializers/mime_types.rb new file mode 100644 index 0000000..72aca7e --- /dev/null +++ b/chapter10/apachetracker/config/initializers/mime_types.rb @@ -0,0 +1,5 @@ +# Be sure to restart your server when you modify this file. + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register_alias "text/html", :iphone diff --git a/chapter10/apachetracker/config/routes.rb b/chapter10/apachetracker/config/routes.rb new file mode 100644 index 0000000..acdb377 --- /dev/null +++ b/chapter10/apachetracker/config/routes.rb @@ -0,0 +1,20 @@ +ActionController::Routing::Routes.draw do |map| + map.connect '/', :controller=>'home' + + map.connect '/logs/', :controller=>"logs", + :action=>'destroy', + :conditions=>{:method=>:delete} + + + map.connect '/logs/', :controller=>"logs", + :action=>'create', + :conditions=>{:method=>:post} + + map.connect '/traffic_reports.pdf', + :controller=>'report', + :action=>'show', + :format=>'pdf' + + map.connect ':controller/:action/:id.:format' + map.connect ':controller/:action/:id' +end diff --git a/chapter10/apachetracker/db/migrate/001_initial_schema.rb b/chapter10/apachetracker/db/migrate/001_initial_schema.rb new file mode 100644 index 0000000..b3e30cb --- /dev/null +++ b/chapter10/apachetracker/db/migrate/001_initial_schema.rb @@ -0,0 +1,23 @@ +class InitialSchema < ActiveRecord::Migration + def self.up + create_table :hits do |t| + t.string :user_agent + t.string :path_info + t.string :remote_addr + t.string :http_referrer + t.string :status + t.datetime :visited_at + end + create_table :advertisers do |t| + t.string :company_name + t.string :referrer_url + t.decimal :cost_per_click, + :precision => 9, :scale => 2 + end + end + + def self.down + drop_table :hits + drop_table :advertisers + end +end diff --git a/chapter10/apachetracker/db/schema.rb b/chapter10/apachetracker/db/schema.rb new file mode 100644 index 0000000..a43c8f5 --- /dev/null +++ b/chapter10/apachetracker/db/schema.rb @@ -0,0 +1,14 @@ +# This file is auto-generated from the current state of the database. Instead of editing this file, +# please use the migrations feature of ActiveRecord 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 +# 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 => 0) do + +end diff --git a/chapter10/apachetracker/doc/README_FOR_APP b/chapter10/apachetracker/doc/README_FOR_APP new file mode 100644 index 0000000..fe41f5c --- /dev/null +++ b/chapter10/apachetracker/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. diff --git a/chapter10/apachetracker/lib/log_parser.rb b/chapter10/apachetracker/lib/log_parser.rb new file mode 100644 index 0000000..fa440ca --- /dev/null +++ b/chapter10/apachetracker/lib/log_parser.rb @@ -0,0 +1,188 @@ +#!/usr/bin/ruby +# This program parses weblogs in the NCSA Common (access log) format or +# NCSA Combined log format +# +# One line consists of +# host rfc931 username date:time request statuscode bytes +# For example +# 1.2.3.4 - dsmith [10/Oct/1999:21:15:05 +0500] "GET /index.html HTTP/1.0" 200 12 +# [dd/MMM/yyyy:hh:mm:ss +-hhmm] +# Where +# dd is the day of the month +# MMM is the month +# yyy is the year +# :hh is the hour +# :mm is the minute +# :ss is the seconds +# +-hhmm is the time zone +# +# In practice, the day is typically logged in two-digit format even for +# single-digit days. +# For example, the second day of the month would be represented as 02. +# However, some HTTP servers do log a single digit day as a single digit. +# When parsing log records, you should be aware of both possible day +# representations. +# +# Author:: Jan Wikholm [jw@jw.fi] +# License:: MIT + +require 'logger' + +class LogFormat + attr_reader :name, :format, :format_symbols, :format_regex + + # add more format directives here.. + DIRECTIVES = { + # format string char => [:symbol to use, /regex to use when matching against log/] + 'h' => [:ip, /\d+\.\d+\.\d+\.\d+/], + 'l' => [:auth, /.*?/], + 'u' => [:username, /.*?/], + 't' => [:datetime, /\[.*?\]/], + 'r' => [:request, /.*?/], + 's' => [:status, /\d+/], + 'b' => [:bytecount, /-|\d+/], + 'v' => [:domain, /.*?/], + 'i' => [:header_lines, /.*?/], + } + + def initialize(name, format) + @name, @format = name, format + parse_format(format) + end + + # The symbols are used to map the log to the env variables + # The regex is used when checking what format the log is and to extract data + def parse_format(format) + format_directive = /%(.*?)(\{.*?\})?([#{[DIRECTIVES.keys.join('|')]}])([\s\\"]*)/ + + log_format_symbols = [] + format_regex = "" + format.scan(format_directive) do |condition, subdirective, directive_char, ignored| + log_format, match_regex = process_directive(directive_char, subdirective, condition) + ignored.gsub!(/\s/, '\\s') unless ignored.nil? + log_format_symbols << log_format + format_regex << "(#{match_regex})#{ignored}" + end + @format_symbols = log_format_symbols + @format_regex = /^#{format_regex}/ + end + + def process_directive(directive_char, subdirective, condition) + directive = DIRECTIVES[directive_char] + case directive_char + when 'i' + log_format = subdirective[1...-1].downcase.tr('-', '_').to_sym + [log_format, directive[1].source] + else + [directive[0], directive[1].source] + end + end +end + +class LogParser + + LOG_FORMATS = { + :common => '%h %l %u %t \"%r\" %>s %b', + :common_with_virtual => '%v %h %l %u %t \"%r\" %>s %b', + :combined => '%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"', + :combined_with_virtual => '%v %h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\"', + :combined_with_cookies => '%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-agent}i\" \"%{Cookies}i\"', + } + + # add any values that you may return here + STAT_ENV_MAP = { + :referer => "HTTP_REFERER", + :user_agent => "HTTP_USER_AGENT", + :ip => "REMOTE_ADDR", + :page => "PATH_INFO", + :domain => "HTTP_HOST", + :datetime => 'DATETIME', + :status => 'STATUS' + } + + attr_reader :known_formats + + @@log = ActiveRecord::Base.logger + + def initialize() + @log_format = [] + initialize_known_formats + end + + # processes the format string into symbols and test regex + # and saves using LogFormat class + def initialize_known_formats + @known_formats = {} + LOG_FORMATS.each do |name, format| + @known_formats[name] = LogFormat.new(name, format) + end + end + + + # Checks which standard the log file (well one line) is + # Automatigally checks for most complex (longest) regex first.. + def check_format(line) + @known_formats.sort_by { |key, log_format| log_format.format_regex.source.size }.reverse.each { |key, log_format| + #@@log.debug "check format: #{key}" + return key if line.match(log_format.format_regex) + } + return :unknown + end + + # This is where the magic happens + # This is the end-to-end business logic of the class + # + # Call with a block that will be called with each line, as a hash + def parse_io_stream(stream) + stats = [] + lines_parsed = 0 + begin + stream.each do |line| + lines_parsed += 1 + #@@log.debug("parse_io_stream() line: #{line.to_s}") + raw_data = parse_line(line) + #@@log.debug(raw_data.inspect) + #@@log.debug("parse_io_stream() lines parsed: #{lines_parsed}") + yield generate_stats(raw_data) + end + end + end + + # Generate_stats will populate a stats hash one line at a time + # Add extra fields into the STAT_ENV_MAP hash at the top of this file. + def generate_stats(raw_data) + stats = { "PATH_INFO" => get_page(raw_data[:request]) } + STAT_ENV_MAP.each do |stat_name, env_name| + stats[env_name] = raw_data[stat_name] if raw_data.has_key? stat_name + end + #@@log.debug("stats: " + stats.inspect) + stats + end + + def get_page(request) + @@log.debug "get_page: #{request}" + request[/\/.*?\s/].rstrip + end + + def parse_line(line) + @format = check_format(line) + log_format = @known_formats[@format] + raise ArgumentError if log_format.nil? or line !~ log_format.format_regex + data = line.scan(log_format.format_regex).flatten + #@@log.debug "parse_line() scanned data: #{data.inspect}" + parsed_data = {} + log_format.format_symbols.size.times do |i| + #@@log.debug "setting #{log_format.format_symbols[i]} to #{data[i]}" + parsed_data[log_format.format_symbols[i]] = data[i] + end + + #remove [] from time if present + parsed_data[:datetime] = parsed_data[:datetime][1...-1] if parsed_data[:datetime] + # Add ip as domain if we don't have a domain (virtual host) + # Assumes we always have an ip + parsed_data[:domain] = parsed_data[:ip] unless parsed_data[:domain] + parsed_data[:format] = @format + #@@log.debug "parse_line() parsed data: #{parsed_data.inspect}" + parsed_data + end +end diff --git a/chapter10/apachetracker/public/.htaccess b/chapter10/apachetracker/public/.htaccess new file mode 100644 index 0000000..d9d211c --- /dev/null +++ b/chapter10/apachetracker/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

    Application error

    Rails application failed to start properly" diff --git a/chapter10/apachetracker/public/404.html b/chapter10/apachetracker/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/chapter10/apachetracker/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
    +

    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/chapter10/apachetracker/public/422.html b/chapter10/apachetracker/public/422.html new file mode 100644 index 0000000..b54e4a3 --- /dev/null +++ b/chapter10/apachetracker/public/422.html @@ -0,0 +1,30 @@ + + + + + + + The change you wanted was rejected (422) + + + + + +
    +

    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/chapter10/apachetracker/public/500.html b/chapter10/apachetracker/public/500.html new file mode 100644 index 0000000..0e9c14f --- /dev/null +++ b/chapter10/apachetracker/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong (500) + + + + + +
    +

    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/chapter10/apachetracker/public/dispatch.cgi b/chapter10/apachetracker/public/dispatch.cgi new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter10/apachetracker/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter10/apachetracker/public/dispatch.fcgi b/chapter10/apachetracker/public/dispatch.fcgi new file mode 100644 index 0000000..5d9b8ec --- /dev/null +++ b/chapter10/apachetracker/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!c:/ruby/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) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/chapter10/apachetracker/public/dispatch.rb b/chapter10/apachetracker/public/dispatch.rb new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter10/apachetracker/public/dispatch.rb @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter10/apachetracker/public/favicon.ico b/chapter10/apachetracker/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/chapter10/apachetracker/public/images/rails.png b/chapter10/apachetracker/public/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/chapter10/apachetracker/public/images/rails.png differ diff --git a/chapter10/apachetracker/public/javascripts/application.js b/chapter10/apachetracker/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/chapter10/apachetracker/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/chapter10/apachetracker/public/javascripts/controls.js b/chapter10/apachetracker/public/javascripts/controls.js new file mode 100644 index 0000000..fbc4418 --- /dev/null +++ b/chapter10/apachetracker/public/javascripts/controls.js @@ -0,0 +1,963 @@ +// Copyright (c) 2005-2007 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) +// 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 +// 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, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +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; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + 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); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + 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; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + 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 = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + 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; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + 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++ + 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()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + 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]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + 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 { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + 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) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// 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. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + 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) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + 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)) + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML; + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw 'Server returned an invalid collection representation.'; + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); diff --git a/chapter10/apachetracker/public/javascripts/dragdrop.js b/chapter10/apachetracker/public/javascripts/dragdrop.js new file mode 100644 index 0000000..ccf4a1e --- /dev/null +++ b/chapter10/apachetracker/public/javascripts/dragdrop.js @@ -0,0 +1,972 @@ +// Copyright (c) 2005-2007 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 is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(Object.isUndefined(Effect)) + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || { }); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if(Object.isArray(containment)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + 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; + } 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( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + 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); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + 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); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + 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; + } + }, + + reset: function() { + 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) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + 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); + } 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)]; + // Mozilla-based browsers fire successive mousemove events with + // 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.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) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + 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, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + 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}); + } + }); + + 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 + + this.options = options; + 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)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + 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) + 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); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + 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 ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(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)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this.element._originallyAbsolute) + Position.relativize(this.element); + delete this.element._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + 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') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + 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); + 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]) + }.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)) + } else { + p = p.map( function(v) { + 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); + this.scrollInterval = null; + 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; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + 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) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + 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)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + 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({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // 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] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // 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); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (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.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); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + 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); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, 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); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + 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, + parent: parent, + 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) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + 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); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var 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]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).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) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/chapter10/apachetracker/public/javascripts/effects.js b/chapter10/apachetracker/public/javascripts/effects.js new file mode 100644 index 0000000..65aed23 --- /dev/null +++ b/chapter10/apachetracker/public/javascripts/effects.js @@ -0,0 +1,1120 @@ +// Copyright (c) 2005-2007 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/ + +// 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)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.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()) + ); + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + 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), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + 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 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || { }); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + 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) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + 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); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + 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]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(), + max = (window.height || document.body.scrollHeight) - document.viewport.getHeight(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1] > max ? max : elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()) } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + 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(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + 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, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ 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(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // 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, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + 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, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + 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' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// 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, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + 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({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +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, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + 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 ) + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + 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] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + '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'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(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]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + 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; + }); + if (!styles.opacity) styles.set('opacity', element.getOpacity()); + return styles; + }; +}; + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element) + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + 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( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); diff --git a/chapter10/apachetracker/public/javascripts/prototype.js b/chapter10/apachetracker/public/javascripts/prototype.js new file mode 100644 index 0000000..546f9fe --- /dev/null +++ b/chapter10/apachetracker/public/javascripts/prototype.js @@ -0,0 +1,4225 @@ +/* Prototype JavaScript framework, version 1.6.0.1 + * (c) 2005-2007 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/ + * + *--------------------------------------------------------------------------*/ + +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/) + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + document.createElement('div').__proto__ && + document.createElement('div').__proto__ !== + document.createElement('form').__proto__ + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +/* Based on Alex Arnell's inheritance implementation. */ +var Class = { + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + 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++) + 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); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "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() } + }); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; + +Object.extend = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; + +Object.extend(Object, { + inspect: function(object) { + try { + if (Object.isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } 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(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (Object.isElement(object)) return; + + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (!Object.isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + }, + + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(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; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({ }, object); + }, + + isElement: function(object) { + return object && object.nodeType == 1; + }, + + isArray: function(object) { + return object && object.constructor === Array; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; + } +}); + +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\((.*?)\)/)[1].split(",").invoke("strip"); + return names.length == 1 && !names[0] ? [] : names; + }, + + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Function.prototype.defer = Function.prototype.delay.curry(0.01); + +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) { } + } + + return returnValue; + } +}; + +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; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + } finally { + this.currentlyExecuting = false; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.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) { + this.gsub(pattern, iterator); + return String(this); + }, + + truncate: function(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() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + 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) { + 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]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(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; + }, + + capitalize: function() { + 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(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + 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); + }); + 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}'); + }, + + isJSON: function() { + 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); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + 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,'>'); + } +}); + +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); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }.bind(this)); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = { + each: function(iterator, context) { + var index = 0; + iterator = iterator.bind(context); + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var index = -number, slices = [], array = this.toArray(); + 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; + var result = true; + this.each(function(value, index) { + result = result && !!iterator(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator(value, index)); + }); + return results; + }, + + detect: function(iterator, context) { + iterator = iterator.bind(context); + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(filter, iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator(value, index)); + }); + return results; + }, + + include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(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); + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(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; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var result; + this.each(function(value, index) { + value = iterator(value, index); + if (result == null || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator, context) { + iterator = iterator ? iterator.bind(context) : Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator, context) { + iterator = iterator.bind(context); + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator, context) { + iterator = iterator.bind(context); + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + 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 +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length, 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; + } +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(Object.isArray(value) ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + 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]; + }, + + uniq: function(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) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + 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 $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + 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]); + } else { + array.push(arguments[i]); + } + } + 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; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +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)); + } + + 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); + } + }, + + set: function(key, value) { + return this._object[key] = value; + }, + + get: function(key) { + return this._object[key]; + }, + + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, + + toObject: function() { + 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; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.map(function(pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return values.map(toQueryPair.curry(key)).join('&'); + } + return toQueryPair(key, values); + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } + } +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); + +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); + +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + 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'; + } + + 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)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +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) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + 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++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + 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); + +Element.cache = { }; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + 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; + }, + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + 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 content, t, range; + + for (position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + t = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + t.insert(element, content); + continue; + } + + content = Object.toHTML(content); + + range = element.ownerDocument.createRange(); + t.initializeRange(element, range); + t.insert(element, range.createContextualFragment(content.stripScripts())); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + 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(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $(element).getElementsBySelector("*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(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]; + }, + + 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]; + }, + + 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]; + }, + + 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]; + }, + + select: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + 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); + } + + while (element = element.parentNode) + if (element == originalAncestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + 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) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (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); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + 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; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || element.tagName == 'BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // 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(); + } + + // 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'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Element.Methods.identify.counter = 1; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + + +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) { + switch (style) { + 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()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + $w('positionedOffset getOffsetParent viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + 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; + } + ); + }); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + 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(); + }, + title: function(element) { + return element.title; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.clone(Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc').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, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + 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 { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +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) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +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 }); + 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); + } + }, + top: { + adjacency: 'afterBegin', + insert: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + initializeRange: function(element, range) { + range.selectNodeContents(element); + range.collapse(true); + } + }, + bottom: { + adjacency: 'beforeEnd', + insert: 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); + } + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + this.bottom.initializeRange = this.top.initializeRange; + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.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; + } +}; + +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; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName, property, value; + + // 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(); + } + + 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); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = { }; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + 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; + }, + + getHeight: function() { + return this.getDimensions().height; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + 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(); + }, + + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(this.expression)) + return false; + + return true; + }, + + compileMatcher: function() { + if (this.shouldUseXPath()) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + 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; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + 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; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + 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); + } + } + } + } + + 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; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}); + +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); + }, + 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); + }, + 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}-')]" + }, + 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 ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + 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 }); + } + } + } + }, + + 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(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return Selector.operators[matches[2]](nodeValue, matches[3]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = undefined; + return nodes; + }, + + // 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++; + } + }, + + // 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); + }, + + // 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); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + 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; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // 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); + }, + + 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); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + 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); + } + 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; + }, + + 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; + }, + + 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); + } + }, + + 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); + }, + + // 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; + }); + }, + + // 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; + }, + + '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; + }, + + '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; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + '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; + } + }, + + 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; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + 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; + } +}); + +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; + }; +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + 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 && + 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); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + 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; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, index) { + if (Object.isUndefined(index)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, value, single = !Object.isArray(index); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + value = this.optionValue(opt); + if (single) { + if (value == index) { + opt.selected = true; + return; + } + } + else opt.selected = index.include(value); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + 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); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + 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); + } +}); + +Event.Methods = (function() { + var isButton; + + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; + + } else if (Prototype.Browser.WebKit) { + 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) { + 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) }, + + element: function(event) { + var node = Event.extend(event).target; + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); + }, + + 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); + }, + + 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)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; + } + }; +})(); + +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) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + 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), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents").__proto__; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + 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; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event) + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + 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"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize() +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer, fired = false; + + function fireContentLoadedEvent() { + if (fired) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + fired = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write(" + + + + +
    + + +
    + + + + +
    +

    Getting started

    +

    Here’s how to get rolling:

    + +
      +
    1. +

      Create your databases and edit config/database.yml

      +

      Rails needs to know your login and password.

      +
    2. + +
    3. +

      Use script/generate to create your models and controllers

      +

      To see all available options, run it without parameters.

      +
    4. + +
    5. +

      Set up a default route and remove or rename this file

      +

      Routes are setup in config/routes.rb.

      +
    6. +
    +
    +
    + + +
    + + \ No newline at end of file diff --git a/chapter11/newstracker/public/javascripts/application.js b/chapter11/newstracker/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/chapter11/newstracker/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/chapter11/newstracker/public/javascripts/controls.js b/chapter11/newstracker/public/javascripts/controls.js new file mode 100644 index 0000000..8c273f8 --- /dev/null +++ b/chapter11/newstracker/public/javascripts/controls.js @@ -0,0 +1,833 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005, 2006 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 +// 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, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + 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; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + 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; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + 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++ + 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()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + 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 { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// 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. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + 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) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + 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)) + return "
      " + ret.join('') + "
    "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + paramName: "value", + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
    /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
    /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

    /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = this.options.paramName; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = this.options.paramName; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/chapter11/newstracker/public/javascripts/dragdrop.js b/chapter11/newstracker/public/javascripts/dragdrop.js new file mode 100644 index 0000000..c71ddb8 --- /dev/null +++ b/chapter11/newstracker/public/javascripts/dragdrop.js @@ -0,0 +1,942 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// 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/ + +if(typeof Effect == 'undefined') + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + 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; + } 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( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + 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); + }, + + reset: function() { + 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) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + 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); + } 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)]; + // Mozilla-based browsers fire successive mousemove events with + // 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.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) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') + 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}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + 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 + + this.delta = this.currentDelta(); + this.options = options; + 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(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + 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.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); + 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); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + 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 ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + 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); + 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]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*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); + this.scrollInterval = null; + 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; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + 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) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + 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)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + 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({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // 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); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + $(e).down('.'+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); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + 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); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, 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); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + 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, + parent: parent, + 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) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + 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); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var 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]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).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) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/chapter11/newstracker/public/javascripts/effects.js b/chapter11/newstracker/public/javascripts/effects.js new file mode 100644 index 0000000..3b02eda --- /dev/null +++ b/chapter11/newstracker/public/javascripts/effects.js @@ -0,0 +1,1088 @@ +// Copyright (c) 2005, 2006 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/ + +// 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)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + return element; +} + +Element.getOpacity = function(element){ + element = $(element); + var opacity; + if (opacity = element.getStyle('opacity')) + return parseFloat(opacity); + if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + element.setStyle({ opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : 1.0 }); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + element.setStyle({opacity: value}); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle( + { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } + return element; +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + Math.round((pos % (1/pulses)) * pulses) == 0 ? + ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : + 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) + ); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } +}; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + 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), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Event = Class.create(); +Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { + initialize: function() { + var options = Object.extend({ + duration: 0 + }, arguments[0] || {}); + this.start(options); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + 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]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + 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(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + 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, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ 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(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // 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, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + 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' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); + effect.element.down().undoPositioned(); + } + }, arguments[1] || {}) + ); +} + +// 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, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + 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({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +} + +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, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +Effect.Morph = Class.create(); +Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: '' + }, arguments[1] || {}); + 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 ) + }); + } + this.transforms = this.options.style.parseStyle().map(function(property){ + var originalValue = this.element.getStyle(property[0]); + return $H({ + style: property[0], + originalValue: property[1].unit=='color' ? + parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: property[1].unit=='color' ? + parseColor(property[1].value) : property[1].value, + unit: property[1].unit + }); + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ) + }); + }, + update: function(position) { + var style = $H(), value = null; + this.transforms.each(function(transform){ + value = transform.unit=='color' ? + $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(transform.originalValue[i]+ + (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) : + transform.originalValue + Math.round( + ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; + style[transform.style] = value; + }); + this.element.setStyle(style); + } +}); + +Effect.Transform = Class.create(); +Object.extend(Effect.Transform.prototype, { + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || {}; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + var data = $H(track).values().first(); + this.tracks.push($H({ + ids: $H(track).keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var elements = [$(track.ids) || $$(track.ids)].flatten(); + return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage', + 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle', + 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth', + 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor', + 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content', + 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction', + 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', + 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight', + 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight', + 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity', + 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY', + 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore', + 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes', + 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress', + 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top', + 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows', + 'width', 'wordSpacing', 'zIndex']; + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.prototype.parseStyle = function(){ + var element = Element.extend(document.createElement('div')); + element.innerHTML = '

    '; + var style = element.down().style, styleRules = $H(); + + Element.CSS_PROPERTIES.each(function(property){ + if(style[property]) styleRules[property] = style[property]; + }); + + var result = $H(); + + styleRules.each(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if(value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if(Element.CSS_LENGTH.test(value)) + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/), + value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null; + + result[property.underscore().dasherize()] = $H({ value:value, unit:unit }); + }.bind(this)); + + return result; +}; + +Element.morph = function(element, style) { + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); + return element; +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/chapter11/newstracker/public/javascripts/prototype.js b/chapter11/newstracker/public/javascripts/prototype.js new file mode 100644 index 0000000..cedfeee --- /dev/null +++ b/chapter11/newstracker/public/javascripts/prototype.js @@ -0,0 +1,2514 @@ +/* Prototype JavaScript framework, version 1.5.0 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0', + BrowserFeatures: { + XPath: !!document.evaluate + }, + + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + emptyFunction: function() {}, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +String.interpret = function(value){ + return value == null ? '' : String(value); +} + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('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) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var name = decodeURIComponent(pair[0]); + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; + + if (hash[name] !== undefined) { + if (hash[name].constructor != Array) + hash[name] = [hash[name]]; + if (value) hash[name].push(value); + } + else hash[name] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + 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; + }, + + capitalize: function(){ + 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(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.replace(/\\/g, '\\\\'); + if (useDoubleQuotes) + return '"' + escapedString.replace(/"/g, '\\"') + '"'; + else + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function() { + return this.inject([], function(array, value) { + return array.include(value) ? array : array.concat([value]); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string){ + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if(window.opera){ + Array.prototype.concat = function(){ + var array = []; + for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for(var i = 0, length = arguments.length; i < length; i++) { + if(arguments[i].constructor == Array) { + for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(obj) { + Object.extend(this, obj || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + + if (pair.value && pair.value.constructor == Array) { + var values = pair.value.compact(); + if (values.length < 2) pair.value = values.reduce(); + else { + key = encodeURIComponent(pair.key); + values.each(function(value) { + value = value != undefined ? encodeURIComponent(value) : ''; + parts.push(key + '=' + encodeURIComponent(value)); + }); + return; + } + } + if (pair.value == undefined) pair[1] = ''; + parts.push(pair.map(encodeURIComponent).join('=')); + }); + + return parts.join('&'); + } +}); + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + } +}); + +function $H(object) { + if (object && object.constructor == Hash) return object; + return new Hash(object); +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + var params = this.options.parameters, method = this.options.method; + + if (!['get', 'post'].include(method)) { + // simulate other verbs over post + params['_method'] = method; + method = 'post'; + } + + params = Hash.toQueryString(params); + if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_=' + + // when GET, append parameters to URL + if (method == 'get' && params) + this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; + + try { + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + var body = method == 'post' ? (this.options.postBody || params) : null; + + this.transport.send(body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.options.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.getHeader('Content-type') || 'text/javascript').strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? eval('(' + json + ')') : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + 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++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; +} + +document.getElementsByClassName = function(className, parentElement) { + if (Prototype.BrowserFeatures.XPath) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } else { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child; + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + if (Element.hasClassName(child, className)) + elements.push(Element.extend(child)); + } + return elements; + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element || _nativeExtensions || element.nodeType == 3) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; + + if (element.tagName == 'FORM') + Object.extend(methods, Form.Methods); + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) + Object.extend(methods, Form.Element.Methods); + + Object.extend(methods, Element.Methods.Simulated); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + 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(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + return Selector.findElement($(element).ancestors(), expression, index); + }, + + down: function(element, expression, index) { + return Selector.findElement($(element).descendants(), expression, index); + }, + + previous: function(element, expression, index) { + return Selector.findElement($(element).previousSiblings(), expression, index); + }, + + next: function(element, expression, index) { + return Selector.findElement($(element).nextSiblings(), expression, index); + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (document.all && !window.opera) { + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + if(attribute) return attribute.nodeValue; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + if (['float','cssFloat'].include(style)) + style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); + style = style.camelize(); + var value = element.style[style]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } else if (element.currentStyle) { + value = element.currentStyle[style]; + } + } + + if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) + value = element['offset'+style.capitalize()] + 'px'; + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + if(style == 'opacity') { + if(value) return parseFloat(value); + if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) { + var value = style[name]; + if(name == 'opacity') { + if (value == 1) { + value = (/Gecko/.test(navigator.userAgent) && + !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else if(value == '') { + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')'; + } + } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; + element.style[name.camelize()] = value; + } + 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) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); + +Element._attributeTranslations = {}; + +Element._attributeTranslations.names = { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" +}; + +Element._attributeTranslations.values = { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } +}; + +Object.extend(Element._attributeTranslations.values, { + href: Element._attributeTranslations.values._getAttr, + src: Element._attributeTranslations.values._getAttr, + disabled: Element._attributeTranslations.values._flag, + checked: Element._attributeTranslations.values._flag, + readonly: Element._attributeTranslations.values._flag, + multiple: Element._attributeTranslations.values._flag +}); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations; + attribute = t.names[attribute] || attribute; + return $(element).getAttributeNode(attribute).specified; + } +}; + +// IE is missing .innerHTML support for TABLE-related elements +if (document.all && !window.opera){ + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
    '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
    ' + html.stripScripts() + '
    '; + depth = 4; + } + $A(element.childNodes).each(function(node){ + element.removeChild(node) + }); + depth.times(function(){ div = div.firstChild }); + + $A(div.childNodes).each( + function(node){ element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + } +}; + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { + var className = 'HTML' + tag + 'Element'; + if(window[className]) return; + var klass = window[className] = {}; + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; + }); + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + if (typeof HTMLElement != 'undefined') { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Form.Methods, HTMLFormElement.prototype); + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { + copy(Form.Element.Methods, klass.prototype); + }); + _nativeExtensions = true; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
    '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.readAttribute("id") == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0, length = clause.length; i < length; i++) + conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + element = $(element); \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0, length = scope.length; i < length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +Object.extend(Selector, { + matchElements: function(elements, expression) { + var selector = new Selector(expression); + return elements.select(selector.match.bind(selector)).map(Element.extend); + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') index = expression, expression = false; + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + return expressions.map(function(expression) { + return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.inject([], function(elements, result) { + return elements.concat(selector.findElements(result || element)); + }); + }); + }).flatten(); + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != undefined) { + if (result[key]) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + 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; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.blur(); + element.disabled = 'true'; + }); + return form; + }, + + enable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.disabled = ''; + }); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + } +} + +Object.extend(Form, Form.Methods); + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select && ( element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type) ) ) + element.select(); + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.blur(); + element.disabled = false; + return element; + } +} + +Object.extend(Form.Element, Form.Element.Methods); +var Field = Form.Element; +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +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, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (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; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + 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(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(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) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff --git a/chapter11/newstracker/public/robots.txt b/chapter11/newstracker/public/robots.txt new file mode 100644 index 0000000..4ab9e89 --- /dev/null +++ b/chapter11/newstracker/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/chapter11/newstracker/script/about b/chapter11/newstracker/script/about new file mode 100644 index 0000000..7b07d46 --- /dev/null +++ b/chapter11/newstracker/script/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about' \ No newline at end of file diff --git a/chapter11/newstracker/script/breakpointer b/chapter11/newstracker/script/breakpointer new file mode 100644 index 0000000..64af76e --- /dev/null +++ b/chapter11/newstracker/script/breakpointer @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' \ No newline at end of file diff --git a/chapter11/newstracker/script/console b/chapter11/newstracker/script/console new file mode 100644 index 0000000..42f28f7 --- /dev/null +++ b/chapter11/newstracker/script/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' \ No newline at end of file diff --git a/chapter11/newstracker/script/destroy b/chapter11/newstracker/script/destroy new file mode 100644 index 0000000..fa0e6fc --- /dev/null +++ b/chapter11/newstracker/script/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' \ No newline at end of file diff --git a/chapter11/newstracker/script/generate b/chapter11/newstracker/script/generate new file mode 100644 index 0000000..ef976e0 --- /dev/null +++ b/chapter11/newstracker/script/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' \ No newline at end of file diff --git a/chapter11/newstracker/script/performance/benchmarker b/chapter11/newstracker/script/performance/benchmarker new file mode 100644 index 0000000..c842d35 --- /dev/null +++ b/chapter11/newstracker/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/chapter11/newstracker/script/performance/profiler b/chapter11/newstracker/script/performance/profiler new file mode 100644 index 0000000..d855ac8 --- /dev/null +++ b/chapter11/newstracker/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/chapter11/newstracker/script/plugin b/chapter11/newstracker/script/plugin new file mode 100644 index 0000000..26ca64c --- /dev/null +++ b/chapter11/newstracker/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' \ No newline at end of file diff --git a/chapter11/newstracker/script/process/inspector b/chapter11/newstracker/script/process/inspector new file mode 100644 index 0000000..bf25ad8 --- /dev/null +++ b/chapter11/newstracker/script/process/inspector @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/inspector' diff --git a/chapter11/newstracker/script/process/reaper b/chapter11/newstracker/script/process/reaper new file mode 100644 index 0000000..c77f045 --- /dev/null +++ b/chapter11/newstracker/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/chapter11/newstracker/script/process/spawner b/chapter11/newstracker/script/process/spawner new file mode 100644 index 0000000..7118f39 --- /dev/null +++ b/chapter11/newstracker/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/chapter11/newstracker/script/runner b/chapter11/newstracker/script/runner new file mode 100644 index 0000000..ccc30f9 --- /dev/null +++ b/chapter11/newstracker/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/chapter11/newstracker/script/server b/chapter11/newstracker/script/server new file mode 100644 index 0000000..dfabcb8 --- /dev/null +++ b/chapter11/newstracker/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/chapter11/newstracker/test/fixtures/stories.yml b/chapter11/newstracker/test/fixtures/stories.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter11/newstracker/test/fixtures/stories.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter11/newstracker/test/functional/pancake_controller_test.rb b/chapter11/newstracker/test/functional/pancake_controller_test.rb new file mode 100644 index 0000000..e96410c --- /dev/null +++ b/chapter11/newstracker/test/functional/pancake_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'pancake_controller' + +# Re-raise errors caught by the controller. +class PancakeController; def rescue_action(e) raise e end; end + +class PancakeControllerTest < Test::Unit::TestCase + def setup + @controller = PancakeController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter11/newstracker/test/functional/reporter_controller_test.rb b/chapter11/newstracker/test/functional/reporter_controller_test.rb new file mode 100644 index 0000000..3b95be8 --- /dev/null +++ b/chapter11/newstracker/test/functional/reporter_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'reporter_controller' + +# Re-raise errors caught by the controller. +class ReporterController; def rescue_action(e) raise e end; end + +class ReporterControllerTest < Test::Unit::TestCase + def setup + @controller = ReporterController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter11/newstracker/test/test_helper.rb b/chapter11/newstracker/test/test_helper.rb new file mode 100644 index 0000000..a299c7f --- /dev/null +++ b/chapter11/newstracker/test/test_helper.rb @@ -0,0 +1,28 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' + +class Test::Unit::TestCase + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # Add more helper methods to be used by all tests here... +end diff --git a/chapter11/newstracker/test/unit/story_test.rb b/chapter11/newstracker/test/unit/story_test.rb new file mode 100644 index 0000000..0657658 --- /dev/null +++ b/chapter11/newstracker/test/unit/story_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class StoryTest < Test::Unit::TestCase + fixtures :stories + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter11/newstracker/track_news.rb b/chapter11/newstracker/track_news.rb new file mode 100644 index 0000000..f6576b5 --- /dev/null +++ b/chapter11/newstracker/track_news.rb @@ -0,0 +1,93 @@ +require 'feed_tools' +require 'active_record' +require 'uri' + +query = ARGV[0] +query_encoded = URI.encode(query) + +if File.exists?('./config/database.yml') + require 'yaml' + ActiveRecord::Base.establish_connection( + YAML.load(File.read('config/database.yml'))['development']) +else + ActiveRecord::Base.establish_connection( + :adapter => "mysql", + :host => "127.0.0.1", + :username => "root", + :password => "", + :database => "company_pr") +end + +class Stories < ActiveRecord::Base +end + +unless Stories.table_exists? + ActiveRecord::Schema.define do + create_table :stories do |t| + t.column :guid, :string + t.column :title, :string + t.column :source, :string + t.column :url, :string + t.column :published_at, :datetime + t.column :created_at, :datetime + end + create_table :cached_feeds do |t| + t.column :url , :string + t.column :title, :string + t.column :href, :string + t.column :link, :string + t.column :feed_data, :text + t.column :feed_data_type, :string, :length=>25 + t.column :http_headers, :text + t.column :last_retrieved, :datetime + end + + # Without the following line, + # you can't retrieve large results - + # like those we use in this script. + + execute "ALTER TABLE cached_feeds + CHANGE COLUMN feed_data feed_data MEDIUMTEXT;" + end +end + +output_format = 'rss' +per_page = 100 + +feed_url = "http://news.google.com/news" << + "?hl=en&ned=us&ie=UTF-8" << + "&num=" << per_page << + "&output=" << output_format << + "&q=" << query_encoded + +FeedTools.configurations[:feed_cache] = "FeedTools::DatabaseFeedCache" + +feed=FeedTools::Feed.open(feed_url) + +unless feed.live? + # replace the previous line with "unless false" to + # disable the cache. + + puts "feed is cached..." + puts "last retrieved: #{ feed.last_retrieved }" + puts "expires: #{ feed.last_retrieved + feed.time_to_live }" +else + feed.items.each do |feed_story| + if not (Stories.find_by_title(feed_story.title) or + Stories.find_by_url(feed_story.link) or + Stories.find_by_guid(feed_story.guid)) + puts "processing story '#{feed_story.title}' - new" + Stories.new do |new_story| + new_story.title=feed_story.title.gsub(/<[^>]*>/, '') # strip HTML + new_story.guid=feed_story.guid + new_story.sourcename=feed_story.publisher.name if feed_story.publisher.name + new_story.url=feed_story.link + new_story.published_at = feed_story.published + new_story.save + end + else + # do nothing + end + end +end + diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/History.txt b/chapter11/newstracker/vendor/plugins/css_graphs/History.txt new file mode 100644 index 0000000..01c99cf --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/History.txt @@ -0,0 +1,14 @@ +== 0.1.0 + +* Converted to Hoe +* Available as a RubyGem + +== April 13, 2007 + +* Added sizing and multi-graph bar enhancements by Maximiliano Guzman +* Added test suite that generates HTML files in test/output + +== Feb 5, 2007 + +* Enhanced all methods to take an array instead of requiring you to use *array +* bar_graph() normalizes the data when rendering to make it usable for many values. diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/MIT-LICENSE b/chapter11/newstracker/vendor/plugins/css_graphs/MIT-LICENSE new file mode 100644 index 0000000..36cc4b5 --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2005 Geoffrey Grosenbach + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/Manifest.txt b/chapter11/newstracker/vendor/plugins/css_graphs/Manifest.txt new file mode 100644 index 0000000..fbae433 --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/Manifest.txt @@ -0,0 +1,17 @@ +History.txt +MIT-LICENSE +Manifest.txt +README.txt +Rakefile +about.yml +generators/css_graphs/css_graphs_generator.rb +generators/css_graphs/templates/.DS_Store +generators/css_graphs/templates/colorbar.jpg +generators/css_graphs/templates/g_colorbar.jpg +generators/css_graphs/templates/g_colorbar2.jpg +generators/css_graphs/templates/g_marker.gif +images/.DS_Store +images/colorbar.jpg +init.rb +lib/css_graphs.rb +test/test_css_graphs.rb diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/README.txt b/chapter11/newstracker/vendor/plugins/css_graphs/README.txt new file mode 100644 index 0000000..656363f --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/README.txt @@ -0,0 +1,23 @@ += css_graphs + +Makes graphs using pure CSS. + +Includes itself in the ActionView::Helpers::TextHelper module, so no extra include is required. + +A generator is also available for copying relevant images to the public directory. + + ./script/generate css_graphs + +== Resources + +Subversion + +* http://topfunky.net/svn/plugins/css_graphs + +Blog + +* http://nubyonrails.com/pages/css_graphs + +Author + +* Geoffrey Grosenbach boss [at] topfunky [dot] com diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/Rakefile b/chapter11/newstracker/vendor/plugins/css_graphs/Rakefile new file mode 100644 index 0000000..2859a96 --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/Rakefile @@ -0,0 +1,14 @@ +require 'rake' +require 'hoe' +require 'lib/css_graphs' + +Hoe.new('css_graphs', CssGraphs::VERSION) do |p| + p.rubyforge_name = 'seattlerb' + p.author = 'Geoffrey Grosenbach' + p.email = 'boss AT topfunky.com' + p.summary = 'Generates a configurable, CSS-tagged HTML calendar.' + p.description = "A simple method to create an HTML calendar for a single month. Can be styled with CSS. Usable with Ruby on Rails." + p.url = "http://rubyforge.org/projects/seattlerb" + p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n") + p.clean_globs = ['test/output', 'email.txt'] +end diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/about.yml b/chapter11/newstracker/vendor/plugins/css_graphs/about.yml new file mode 100644 index 0000000..399e225 --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/about.yml @@ -0,0 +1,7 @@ +author: topfunky +summary: A helper to make graphs with CSS and XHTML. Includes a generator for copying relevant images to the public directory. +homepage: http://nubyonrails.com/pages/css_graphs +plugin: http://topfunky.net/svn/plugins/css_graphs +license: MIT +version: 0.2 +rails_version: 1.0+ diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/css_graphs_generator.rb b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/css_graphs_generator.rb new file mode 100644 index 0000000..ba386ae --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/css_graphs_generator.rb @@ -0,0 +1,15 @@ +class CssGraphsGenerator < Rails::Generator::Base + + def manifest + record do |m| + css_graphs_dir = File.join("public", "images", "css_graphs") + m.directory css_graphs_dir + + Dir.open(File.join(File.dirname(__FILE__), "templates")).entries.each do |file| + next if File.directory?(file) + m.file file, File.join(css_graphs_dir, file) + end + + end + end +end diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/colorbar.jpg b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/colorbar.jpg new file mode 100644 index 0000000..e504ec4 Binary files /dev/null and b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/colorbar.jpg differ diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_colorbar.jpg b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_colorbar.jpg new file mode 100644 index 0000000..f7fd6fd Binary files /dev/null and b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_colorbar.jpg differ diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_colorbar2.jpg b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_colorbar2.jpg new file mode 100644 index 0000000..1d81bfd Binary files /dev/null and b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_colorbar2.jpg differ diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_marker.gif b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_marker.gif new file mode 100644 index 0000000..68f1e5d Binary files /dev/null and b/chapter11/newstracker/vendor/plugins/css_graphs/generators/css_graphs/templates/g_marker.gif differ diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/images/colorbar.jpg b/chapter11/newstracker/vendor/plugins/css_graphs/images/colorbar.jpg new file mode 100644 index 0000000..e504ec4 Binary files /dev/null and b/chapter11/newstracker/vendor/plugins/css_graphs/images/colorbar.jpg differ diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/init.rb b/chapter11/newstracker/vendor/plugins/css_graphs/init.rb new file mode 100644 index 0000000..f441df1 --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/init.rb @@ -0,0 +1,2 @@ + +ActionView::Base.send :include, CssGraphs diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/lib/css_graphs.rb b/chapter11/newstracker/vendor/plugins/css_graphs/lib/css_graphs.rb new file mode 100644 index 0000000..816e009 --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/lib/css_graphs.rb @@ -0,0 +1,371 @@ +module CssGraphs + + VERSION='0.1.0' + + # Makes a vertical bar graph. + # + # bar_graph [["Stout", 10], ["IPA", 80], ["Pale Ale", 50], ["Milkshake", 30]] + # + # NOTE: Updated to take an array instead of forcing you to use *array. + # NOTE: Normalizes data to fit in the viewable area instead of being fixed to 100. + + def bar_graph(data=[]) + width = 378 + height = 150 + colors = %w(#ce494a #efba29 #efe708 #5a7dd6 #73a25a) + floor_cutoff = 24 # Pixels beneath which values will not be drawn in graph + data_max = data.inject(0) { |memo, array| array.last > memo ? array.last : memo } + + + html = <<-"HTML" + +
    +
    + HTML + + data.each_with_index do |d, index| + scaled_value = scale_graph_value(d.last, data_max, 100) + html += <<-"HTML" +
    #{d[0].to_s.humanize}
    +
    #{scaled_value < floor_cutoff ? '' : d.last} + HTML + end + + html += <<-"HTML" +
    +
    + HTML + + html + end + + + # Makes a vertical bar graph with several sets of bars. + # + # NOTE: Normalizes data to fit in the viewable area instead of being fixed to 100. + # + # Example: + ## <% @data_for_graph = [[['January',10],['February',25],['March',45]],[['January',34],['February',29],['March',80]]] %> + ## <%= bar_graph (@data_for_graph,{:width => 640,:height => 480}) do |index,variable| + ## url_for( :action => 'report', :month => index) + ## end + ## %> + # + # alldata should be an array of variables, each one an array itself, of the form: + ## [['label1',value1],['label2',value2]] + # + # options hash: + #* :display_value_on_bar if set to true, will display the value on top each bar, default behavior is not to show the value + #* :colors is an array of colors in hex format: '#EEC2D2' if you don't set them, default colors will be used + #* :color_by can be set to 'dimension' or 'index' + #* :width and :height set the dimensions, wich default to 378x150 + # + # url_creator_block: + # + ## the url_creator_block receives two parameters, index and variable, that are used to build the bars links. + ## index is the position for this bar's that in its variable array, while variable is the variable this bar represents + + def multi_bar_graph(alldata=[], options={}, &url_creator) + graph_id = (rand*10000000000000000000).to_i.to_s #we need a unique id for each graph on the page so we can have distinct styles for each of them + if !options.nil? && options[:width] && options[:height] + width,height=options[:width].to_i,options[:height].to_i + else + width,height = 378,150 + end + colors = (%w(#ce494a #efba29 #efe708 #5a7dd6 #73a25a))*10 unless colors=options[:colors] + floor_cutoff = 24 # Pixels beneath which values will not be drawn in graph + data_max = (alldata.map { |data| data.max{ |a,b| a.last <=> b.last }.last } ).max + dimensions=alldata.size + size = alldata.map{ |data| data.size }.max + bar_offset = 24 #originally set to 24 + bar_group_width=(width-bar_offset)/size #originally set to 48px + bar_increment = bar_group_width #originally set to 75 + bar_width=(bar_group_width-bar_offset)/dimensions #originally set to 28px + bar_image_offset = bar_offset+4 #originally set to 28 + bar_padding = 2 + + #p "dimensions = #{dimensions}" + #p "bar_group_width =#{bar_group_width}" + #p "bar_width = #{bar_width}" + #p "bar_increment = #{bar_increment}" + + html = <<-"HTML" + +
    +
    + HTML + alldata.each_with_index do |data,dimension| +# data_max = data.inject(0) { |memo, array| array.last > memo ? array.last : memo } + data.each_with_index do |d, index| + scaled_value = scale_graph_value(d.last, data_max, height-50) + if (options[:display_value_on_bar]) + bar_text=(scaled_value < floor_cutoff ? '' : d.last).to_s #text on top of the bar + else + bar_text='' + end + + if dimension==0 + html += <<-"HTML" + +
    #{d[0].to_s}
    + HTML + end + @url = url_creator.call(index,dimension) if !url_creator.nil? + html += <<-"HTML" + + +
    + +
    #{bar_text}
    +
    + HTML + end + end + html += <<-"HTML" +
    +
    + HTML + + html + end + + # Make a horizontal graph that only shows percentages. + # + # The label will be set as the title of the bar element. + # + # horizontal_bar_graph [["Stout", 10], ["IPA", 80], ["Pale Ale", 50], ["Milkshake", 30]] + # + # NOTE: Updated to take an array instead of forcing you to use *array. + # NOTE: Does not normalize data yet...TODO + + def horizontal_bar_graph(data) + html = <<-"HTML" + + HTML + + data.each do |d| + html += <<-"HTML" +
    + #{d[1]}% +
    + HTML + end + return html + end + + # Makes a multi-colored bar graph with a bar down the middle, representing the value. + # + # complex_bar_graph [["Stout", 10], ["IPA", 80], ["Pale Ale", 50], ["Milkshake", 30]] + # + # NOTE: Updated to take an array instead of forcing you to use *array. + # NOTE: Does not normalize data yet...TODO + + def complex_bar_graph(data) + html = <<-"HTML" + +
    +
    + HTML + + data.each do |d| + html += <<-"HTML" +
    #{d[0].to_s.humanize}
    +
    +
    #{d[1]}%
    +
    + HTML + end + + html += "
    \n
    " + return html + end + + ## + # Scale values within a +max+. The +max+ will usually be the height of the graph. + + def scale_graph_value(data_value, data_max, max) + ((data_value.to_f / data_max.to_f) * max).round + end + +end diff --git a/chapter11/newstracker/vendor/plugins/css_graphs/test/test_css_graphs.rb b/chapter11/newstracker/vendor/plugins/css_graphs/test/test_css_graphs.rb new file mode 100644 index 0000000..4d13745 --- /dev/null +++ b/chapter11/newstracker/vendor/plugins/css_graphs/test/test_css_graphs.rb @@ -0,0 +1,65 @@ +require 'rubygems' +require 'test/unit' +require 'fileutils' +require 'active_support/core_ext/string/inflections' +require File.dirname(__FILE__) + "/../lib/css_graphs" + +# Manually insert this so the helper runs apart from Rails. +class String + include ActiveSupport::CoreExtensions::String::Inflections +end + +class TestCSSGraphs < Test::Unit::TestCase + + include CssGraphs + + def test_bar_graph + output = "" + data_for_graph = [['January',10],['February',25],['March',45]] + + output += bar_graph(data_for_graph) + output += bar_graph(data_for_graph) + output += bar_graph(data_for_graph) + + write(output) + end + + def test_multi_bar_graph + output = "" + data_for_graph = [['January',10],['February',25],['March',45]], [['January',34],['February',29],['March',80]] + + output += multi_bar_graph_for_size(data_for_graph, 640, 480) + output += multi_bar_graph_for_size(data_for_graph, 300, 100) + output += multi_bar_graph_for_size(data_for_graph, 200, 600) + + write(output) + end + + def test_horizontal_bar_graph + output = horizontal_bar_graph [["Stout", 10], ["IPA", 80], ["Pale Ale", 50], ["Milkshake", 30]] + write(output) + end + + def test_complex_bar_graph + output = complex_bar_graph [["Stout", 10], ["IPA", 80], ["Pale Ale", 50], ["Milkshake", 30]] + write(output) + end + + private + + def write(data) + # Clean up calling method name and use as filename. + filename = caller.first.gsub(/\S+\s`/, '').gsub(/'/, '') + FileUtils.mkdir_p "test/output" + File.open(File.dirname(__FILE__) + "/output/#{filename}.html", "w") do |f| + f.write data + end + end + + def multi_bar_graph_for_size(data, width=640, height=480) + multi_bar_graph(data, { :width => width, :height => height }) do |index, variable| + "/report/#{index}" + end + end + +end diff --git a/chapter12/create_word_document.rb b/chapter12/create_word_document.rb new file mode 100644 index 0000000..48f7c1d --- /dev/null +++ b/chapter12/create_word_document.rb @@ -0,0 +1,15 @@ +require 'win32ole' + +word_app = WIN32OLE.new('Word.Application') +word_app.visible = true +word_document = word_app.Documents.add +current_selection = word_app.selection +current_selection.TypeText "Dear Mr Executive, \n" +current_selection.TypeText "I hereby resign my post as chief programmer. " +current_selection.TypeText "\n\n" +current_selection.TypeText "Sincerely,\n" +current_selection.TypeText "Mr. T. Tom\n" + +word_document.SaveAs 'resignation_letter.doc' +word_app.PrintOut + diff --git a/chapter12/create_word_document_from_template.rb b/chapter12/create_word_document_from_template.rb new file mode 100644 index 0000000..dc25d73 --- /dev/null +++ b/chapter12/create_word_document_from_template.rb @@ -0,0 +1,14 @@ +require 'win32ole' + +word_app = WIN32OLE.new('Word.Application') +word_app.visible = true +word_document = word_app.Documents.Open(File.join(Dir.pwd,"resignation_letter_template.doc")) +current_selection = word_app.selection +find_object = current_selection.Find +find_object.execute('%%BOSS%%', :replaceWith=>'jon jonnerson') + +#Function Execute([FindText], [MatchCase], [MatchWholeWord], [MatchWildcards], [MatchSoundsLike], [MatchAllWordForms], [Forward], [Wrap], [Format], [ReplaceWith], [Replace], [MatchKashida], [MatchDiacritics], [MatchAlefHamza], [MatchControl]) As Boolean + +#word_document.SaveAs 'resignation_letter.doc' +#word_app.PrintOut + diff --git a/chapter12/training/Rakefile b/chapter12/training/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/chapter12/training/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/chapter12/training/app/controllers/application.rb b/chapter12/training/app/controllers/application.rb new file mode 100644 index 0000000..187f6df --- /dev/null +++ b/chapter12/training/app/controllers/application.rb @@ -0,0 +1,7 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + # Pick a unique cookie name to distinguish our session data from others' + session :session_key => '_training_session_id' +end diff --git a/chapter12/training/app/controllers/homepage_controller.rb b/chapter12/training/app/controllers/homepage_controller.rb new file mode 100644 index 0000000..73c66a4 --- /dev/null +++ b/chapter12/training/app/controllers/homepage_controller.rb @@ -0,0 +1,2 @@ +class HomepageController < ApplicationController +end diff --git a/chapter12/training/app/controllers/log_controller.rb b/chapter12/training/app/controllers/log_controller.rb new file mode 100644 index 0000000..04ce7d1 --- /dev/null +++ b/chapter12/training/app/controllers/log_controller.rb @@ -0,0 +1,23 @@ +class LogController < ApplicationController + def upload + if params[:commit] + count = 0 + training_class = TrainingClass.find_by_id(params[:training_class_id]) + training_class_date = Date.parse(params[:training_class_date]) + params[:trainee].each do |index, t| + next if t[:name]=='' + + salesperson = Salesperson.find_or_create_by_name_and_employer( t[:name], t[:employer]) + + g = Grade.create(:salesperson=>salesperson, :percentage_grade => t[:grade], :training_class=>training_class, :took_class_at=>training_class_date) + g.save + count = count +1 + end + flash[:notice]="#{count} Entries Uploaded!" + end + end + def all + @grades = Grade.find(:all) + render :layout=>false + end +end diff --git a/chapter12/training/app/helpers/application_helper.rb b/chapter12/training/app/helpers/application_helper.rb new file mode 100644 index 0000000..22a7940 --- /dev/null +++ b/chapter12/training/app/helpers/application_helper.rb @@ -0,0 +1,3 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper +end diff --git a/chapter12/training/app/helpers/homepage_helper.rb b/chapter12/training/app/helpers/homepage_helper.rb new file mode 100644 index 0000000..c5bbfe5 --- /dev/null +++ b/chapter12/training/app/helpers/homepage_helper.rb @@ -0,0 +1,2 @@ +module HomepageHelper +end diff --git a/chapter12/training/app/helpers/uploader_helper.rb b/chapter12/training/app/helpers/uploader_helper.rb new file mode 100644 index 0000000..b29f719 --- /dev/null +++ b/chapter12/training/app/helpers/uploader_helper.rb @@ -0,0 +1,2 @@ +module UploaderHelper +end diff --git a/chapter12/training/app/models/grade.rb b/chapter12/training/app/models/grade.rb new file mode 100644 index 0000000..307956c --- /dev/null +++ b/chapter12/training/app/models/grade.rb @@ -0,0 +1,4 @@ +class Grade < ActiveRecord::Base + belongs_to :salesperson + belongs_to :training_class +end diff --git a/chapter12/training/app/models/log.rb b/chapter12/training/app/models/log.rb new file mode 100644 index 0000000..19ff756 --- /dev/null +++ b/chapter12/training/app/models/log.rb @@ -0,0 +1,2 @@ +class Log < ActiveRecord::Base +end diff --git a/chapter12/training/app/models/salesperson.rb b/chapter12/training/app/models/salesperson.rb new file mode 100644 index 0000000..9106064 --- /dev/null +++ b/chapter12/training/app/models/salesperson.rb @@ -0,0 +1,4 @@ +class Salesperson < ActiveRecord::Base + has_many :grade + #has_many :passing_grade, :class=>:grade, :conditions=>'percentage_grade>80', :group=>'class' +end diff --git a/chapter12/training/app/models/training_class.rb b/chapter12/training/app/models/training_class.rb new file mode 100644 index 0000000..d1e60b8 --- /dev/null +++ b/chapter12/training/app/models/training_class.rb @@ -0,0 +1,3 @@ +class TrainingClass < ActiveRecord::Base + has_many :grades +end diff --git a/chapter12/training/app/views/homepage/index.rhtml b/chapter12/training/app/views/homepage/index.rhtml new file mode 100644 index 0000000..bf7f589 --- /dev/null +++ b/chapter12/training/app/views/homepage/index.rhtml @@ -0,0 +1,6 @@ +

    Training Log Application

    + + +

    Actions:

    + +
      <%=link_to 'Upload Data', :action=>:upload, :controller=>:log %> diff --git a/chapter12/training/app/views/layouts/application.rhtml b/chapter12/training/app/views/layouts/application.rhtml new file mode 100644 index 0000000..0129584 --- /dev/null +++ b/chapter12/training/app/views/layouts/application.rhtml @@ -0,0 +1,17 @@ + + + Training Uploader Application <%=@title || ''%> + <%= stylesheet_link_tag 'training.css'%> + <%= javascript_include_tag :defaults %> + <%= calendar_date_select_includes "silver"%> + + +

      <%=@title%>

      + <%flash.each do |type,msg|%> +
      + <%=msg%> +
      + <%end%> + <%=yield%> + + diff --git a/chapter12/training/app/views/log/all.rxml b/chapter12/training/app/views/log/all.rxml new file mode 100644 index 0000000..95645f2 --- /dev/null +++ b/chapter12/training/app/views/log/all.rxml @@ -0,0 +1,17 @@ +xml.instruct! :xml, :version=>"1.0" +xml.instruct! 'xml-stylesheet', :href=>'/stylesheets/log.css' + +xml.grades do + xml.css :href=>'/stylesheets/log.css' + #,:rel=>'Stylesheet',:media=>'screen' + @grades.each do |grade| + xml.grade do + xml.student grade.salesperson.name + xml.id grade.id + xml.employer grade.salesperson.employer + xml.class grade.training_class.name + xml.grade grade.percentage_grade + xml.took_class_at grade.took_class_at + end + end +end diff --git a/chapter12/training/app/views/log/upload.rhtml b/chapter12/training/app/views/log/upload.rhtml new file mode 100644 index 0000000..c993f68 --- /dev/null +++ b/chapter12/training/app/views/log/upload.rhtml @@ -0,0 +1,20 @@ +<%@title='Upload Training Log' + @number_of_elements = 10 +%> +<% form_tag do %> +

      Class: <%=select 'training_class_id', nil, TrainingClass.find(:all).map { |c| [c.name, c.id] } %> Date: <%= calendar_date_select_tag "training_class_date", Date.today.strftime('%B %d, %Y') %>

      + + + + <%1.upto(@number_of_elements) do |i|%> + + + + + + <%end%> +
      Trainee Name Trainee Employer Grade
      <%=text_field "trainee", 'name',:index=>i %> + <%=text_field "trainee", 'employer', :index=>i %><%=text_field "trainee", 'grade', :index=>i, :size=>3, :value=>'0'%>%
      <%=submit_tag 'Upload', :class=>'submit_button'%> + +
      +<%end%> diff --git a/chapter12/training/config/boot.rb b/chapter12/training/config/boot.rb new file mode 100644 index 0000000..0f034f0 --- /dev/null +++ b/chapter12/training/config/boot.rb @@ -0,0 +1,39 @@ +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb + +RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT) + +unless defined?(Rails::Initializer) + if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + else + require 'rubygems' + + rails_gem_version = + if defined? RAILS_GEM_VERSION + RAILS_GEM_VERSION + else + File.read("#{File.dirname(__FILE__)}/environment.rb") =~ /^[^#]*RAILS_GEM_VERSION\s+=\s+'([\d.]+)'/ + $1 + end + + if rails_gem_version + rails_gem = Gem.cache.search('rails', "=#{rails_gem_version}.0").sort_by { |g| g.version.version }.last + + if rails_gem + gem "rails", "=#{rails_gem.version.version}" + require rails_gem.full_gem_path + '/lib/initializer' + else + STDERR.puts %(Cannot find gem for Rails =#{rails_gem_version}.0: + Install the missing gem with 'gem install -v=#{rails_gem_version} rails', or + change environment.rb to define RAILS_GEM_VERSION with your desired version. + ) + exit 1 + end + else + gem "rails" + require 'initializer' + end + end + + Rails::Initializer.run(:set_load_path) +end diff --git a/chapter12/training/config/database.yml b/chapter12/training/config/database.yml new file mode 100644 index 0000000..4e0efeb --- /dev/null +++ b/chapter12/training/config/database.yml @@ -0,0 +1,36 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install mysql +# On MacOS X: +# gem install mysql -- --include=/usr/local/lib +# On Windows: +# gem install mysql +# Choose the win32 build. +# Install MySQL and put its /bin directory on your path. +# +# 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: training_development + username: root + password: + host: localhost + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: training_test + username: root + password: + host: localhost + +production: + adapter: mysql + database: training_production + username: root + password: + host: localhost diff --git a/chapter12/training/config/environment.rb b/chapter12/training/config/environment.rb new file mode 100644 index 0000000..4233311 --- /dev/null +++ b/chapter12/training/config/environment.rb @@ -0,0 +1,60 @@ +# Be sure to restart your web server when you modify this file. + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Specifies gem version of Rails to use when vendor/rails is not present +RAILS_GEM_VERSION = '1.2.5' unless defined? RAILS_GEM_VERSION + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here + + # Skip frameworks you're not going to use (only works if using vendor/rails) + # config.frameworks -= [ :action_web_service, :action_mailer ] + + # Only load the plugins named here, by default all plugins in vendor/plugins are loaded + # config.plugins = %W( exception_notification ssl_requirement ) + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Use the database for sessions instead of the file system + # (create the session table with 'rake db:sessions:create') + # config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # Add new inflection rules using the following format + # (all these examples are active by default): + # Inflector.inflections do |inflect| + # inflect.plural /^(ox)$/i, '\1en' + # inflect.singular /^(ox)en/i, '\1' + # inflect.irregular 'person', 'people' + # inflect.uncountable %w( fish sheep ) + # end + + # See Rails::Configuration for more options +end + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register "application/x-mobile", :mobile + +# Include your application configuration below diff --git a/chapter12/training/config/environments/development.rb b/chapter12/training/config/environments/development.rb new file mode 100644 index 0000000..0589aa9 --- /dev/null +++ b/chapter12/training/config/environments/development.rb @@ -0,0 +1,21 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false +config.action_view.debug_rjs = true + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false diff --git a/chapter12/training/config/environments/production.rb b/chapter12/training/config/environments/production.rb new file mode 100644 index 0000000..cb295b8 --- /dev/null +++ b/chapter12/training/config/environments/production.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors, bad email addresses will be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/chapter12/training/config/environments/test.rb b/chapter12/training/config/environments/test.rb new file mode 100644 index 0000000..f0689b9 --- /dev/null +++ b/chapter12/training/config/environments/test.rb @@ -0,0 +1,19 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Tell ActionMailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test \ No newline at end of file diff --git a/chapter12/training/config/routes.rb b/chapter12/training/config/routes.rb new file mode 100644 index 0000000..4327972 --- /dev/null +++ b/chapter12/training/config/routes.rb @@ -0,0 +1,6 @@ +ActionController::Routing::Routes.draw do |map| + map.connect '', :controller => "homepage" + + map.connect ':controller/:action/:id.:format' + map.connect ':controller/:action/:id' +end diff --git a/chapter12/training/db/migrate/001_create_salespeople.rb b/chapter12/training/db/migrate/001_create_salespeople.rb new file mode 100644 index 0000000..30497ca --- /dev/null +++ b/chapter12/training/db/migrate/001_create_salespeople.rb @@ -0,0 +1,12 @@ +class CreateSalespeople < ActiveRecord::Migration + def self.up + create_table :salespeople do |t| + t.column :name, :string, :limit=>45 + t.column :employer, :string, :limit=>45 + end + end + + def self.down + drop_table :salespeople + end +end diff --git a/chapter12/training/db/migrate/002_create_grades.rb b/chapter12/training/db/migrate/002_create_grades.rb new file mode 100644 index 0000000..6f8e618 --- /dev/null +++ b/chapter12/training/db/migrate/002_create_grades.rb @@ -0,0 +1,15 @@ +class CreateGrades < ActiveRecord::Migration + def self.up + create_table :grades do |t| + t.column :salesperson_id, :integer + t.column :class_id, :integer + t.column :percentage_grade, :integer + t.column :took_class_at, :datetime + end + end + + def self.down + drop_table :grades + end +end + diff --git a/chapter12/training/db/migrate/003_create_training_classes.rb b/chapter12/training/db/migrate/003_create_training_classes.rb new file mode 100644 index 0000000..170b542 --- /dev/null +++ b/chapter12/training/db/migrate/003_create_training_classes.rb @@ -0,0 +1,11 @@ +class CreateTrainingClasses < ActiveRecord::Migration + def self.up + create_table :training_classes do |t| + t.column :name, :string, :limit=>45 + end + end + + def self.down + drop_table :training_classes + end +end diff --git a/chapter12/training/db/schema.rb b/chapter12/training/db/schema.rb new file mode 100644 index 0000000..f517681 --- /dev/null +++ b/chapter12/training/db/schema.rb @@ -0,0 +1,23 @@ +# This file is autogenerated. Instead of editing this file, please use the +# migrations feature of ActiveRecord to incrementally modify your database, and +# then regenerate this schema definition. + +ActiveRecord::Schema.define(:version => 3) do + + create_table "grades", :force => true do |t| + t.column "salesperson_id", :integer + t.column "training_class_id", :integer + t.column "percentage_grade", :integer + t.column "took_class_at", :datetime + end + + create_table "salespeople", :force => true do |t| + t.column "name", :string, :limit => 45 + t.column "employer", :string, :limit => 45 + end + + create_table "training_classes", :force => true do |t| + t.column "name", :string, :limit => 45 + end + +end diff --git a/chapter12/training/doc/README_FOR_APP b/chapter12/training/doc/README_FOR_APP new file mode 100644 index 0000000..ac6c149 --- /dev/null +++ b/chapter12/training/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake appdoc" to generate API documentation for your models and controllers. \ No newline at end of file diff --git a/chapter12/training/public/.htaccess b/chapter12/training/public/.htaccess new file mode 100644 index 0000000..d3c9983 --- /dev/null +++ b/chapter12/training/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

      Application error

      Rails application failed to start properly" \ No newline at end of file diff --git a/chapter12/training/public/404.html b/chapter12/training/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/chapter12/training/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
      +

      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/chapter12/training/public/500.html b/chapter12/training/public/500.html new file mode 100644 index 0000000..f0aee0e --- /dev/null +++ b/chapter12/training/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong + + + + + +
      +

      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/chapter12/training/public/dispatch.cgi b/chapter12/training/public/dispatch.cgi new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter12/training/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter12/training/public/dispatch.fcgi b/chapter12/training/public/dispatch.fcgi new file mode 100644 index 0000000..5d9b8ec --- /dev/null +++ b/chapter12/training/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!c:/ruby/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) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/chapter12/training/public/dispatch.rb b/chapter12/training/public/dispatch.rb new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter12/training/public/dispatch.rb @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter12/training/public/favicon.ico b/chapter12/training/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/chapter12/training/public/images/calendar_date_select/calendar.gif b/chapter12/training/public/images/calendar_date_select/calendar.gif new file mode 100644 index 0000000..6b7b7ca Binary files /dev/null and b/chapter12/training/public/images/calendar_date_select/calendar.gif differ diff --git a/chapter12/training/public/images/rails.png b/chapter12/training/public/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/chapter12/training/public/images/rails.png differ diff --git a/chapter12/training/public/javascripts/application.js b/chapter12/training/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/chapter12/training/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/chapter12/training/public/javascripts/calendar_date_select/calendar_date_select.js b/chapter12/training/public/javascripts/calendar_date_select/calendar_date_select.js new file mode 100644 index 0000000..e5c87fc --- /dev/null +++ b/chapter12/training/public/javascripts/calendar_date_select/calendar_date_select.js @@ -0,0 +1,399 @@ +// CalendarDateSelect version 1.8.1 - a small prototype based date picker +// Questions, comments, bugs? - email the Author - Tim Harper <"timseeharper@gmail.seeom".gsub("see", "c")> +if (typeof Prototype == 'undefined') + alert("CalendarDateSelect Error: Prototype could not be found. Please make sure that your application's layout includes prototype.js (e.g. <%= javascript_include_tag :defaults %>) *before* it includes calendar_date_select.js (e.g. <%= calendar_date_select_includes %>)."); + +Element.addMethods({ + purgeChildren: function(element) { $A(element.childNodes).each(function(e){$(e).remove();}); }, + build: function(element, type, options, style) { + newElement = Element.build(type, options, style); + element.appendChild(newElement); + return newElement; + } +}); + +Element.build = function(type, options, style) +{ + e = $(document.createElement(type)); + $H(options).each(function(pair) { eval("e." + pair.key + " = pair.value" ); }); + if (style) + $H(style).each(function(pair) { eval("e.style." + pair.key + " = pair.value" ); }); + return e; +}; +nil=null; + +Date.one_day = 24*60*60*1000; +Date.weekdays = $w("S M T W T F S"); +Date.first_day_of_week = 0; +Date.months = $w("January February March April May June July August September October November December" ); +Date.padded2 = function(hour) { padded2 = hour.toString(); if (parseInt(hour) < 10) padded2="0" + padded2; return padded2; } +Date.prototype.getPaddedMinutes = function() { return Date.padded2(this.getMinutes()); } +Date.prototype.getAMPMHour = function() { hour=this.getHours(); return (hour == 0) ? 12 : (hour > 12 ? hour - 12 : hour ) } +Date.prototype.getAMPM = function() { return (this.getHours() < 12) ? "AM" : "PM"; } +Date.prototype.stripTime = function() { return new Date(this.getFullYear(), this.getMonth(), this.getDate());}; +Date.prototype.daysDistance = function(compare_date) { return Math.round((compare_date - this) / Date.one_day); }; +Date.prototype.toFormattedString = function(include_time){ + str = Date.months[this.getMonth()] + " " + this.getDate() + ", " + this.getFullYear(); + + if (include_time) { hour=this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM() } + return str; +} +Date.parseFormattedString = function(string) { return new Date(string);} +Math.floor_to_interval = function(n, i) { return Math.floor(n/i) * i;} +window.f_height = function() { return( [window.innerHeight ? window.innerHeight : null, document.documentElement ? document.documentElement.clientHeight : null, document.body ? document.body.clientHeight : null].select(function(x){return x>0}).first()||0); } +window.f_scrollTop = function() { return ([window.pageYOffset ? window.pageYOffset : null, document.documentElement ? document.documentElement.scrollTop : null, document.body ? document.body.scrollTop : null].select(function(x){return x>0}).first()||0 ); } + +_translations = { + "OK": "OK", + "Now": "Now", + "Today": "Today" +} +SelectBox = Class.create(); +SelectBox.prototype = { + initialize: function(parent_element, values, html_options, style_options) { + this.element = $(parent_element).build("select", html_options, style_options); + this.populate(values); + }, + populate: function(values) { + this.element.purgeChildren(); + that=this; $A(values).each(function(pair) { if (typeof(pair)!="object") {pair = [pair, pair]}; that.element.build("option", { value: pair[1], innerHTML: pair[0]}) }); + }, + setValue: function(value, callback) { + e = this.element; + matched=false; + $R(0, e.options.length - 1 ).each(function(i) { if(e.options[i].value==value.toString()) {e.selectedIndex = i; matched=true;}; } ); + return matched; + }, + getValue: function() { return $F(this.element)} +} +CalendarDateSelect = Class.create(); +CalendarDateSelect.prototype = { + initialize: function(target_element, options) { + this.target_element = $(target_element); // make sure it's an element, not a string + this.target_element.calendar_date_select = this; + // initialize the date control + this.options = $H({ + embedded: false, + popup: nil, + time: false, + buttons: true, + year_range: 10, + calendar_div: nil, + close_on_click: nil, + minute_interval: 5, + popup_by: target_element, + month_year: "dropdowns", + onchange: this.target_element.onchange + }).merge(options || {}); + + this.selection_made = $F(this.target_element).strip()!==""; + this.use_time = this.options.time; + + this.callback("before_show") + this.calendar_div = $(this.options.calendar_div); + if (!this.target_element) { alert("Target element " + target_element + " not found!"); return false;} + + this.parseDate(); + + // by default, stick it by the target element (if embedded, that's where we'll want it to show up) + if (this.calendar_div == nil) { this.calendar_div = $( this.options.embedded ? this.target_element.parentNode : document.body ).build('div'); } + if (!this.options.embedded) { + this.calendar_div.setStyle( { position:"absolute", visibility: "hidden" } ) + this.positionCalendarDiv(); + } + + this.calendar_div.addClassName("calendar_date_select"); + + if (this.options["embedded"]) this.options["close_on_click"]=false; + // logic for close on click + if (this.options['close_on_click']===nil ) + { + if (this.options['time']) + this.options["close_on_click"] = false; + else + this.options['close_on_click'] = true; + } + + // set the click handler to check if a user has clicked away from the document + if(!this.options["embedded"]) Event.observe(document.body, "mousedown", this.bodyClick_handler=this.bodyClick.bindAsEventListener(this)); + + this.initFrame(); + if(!this.options["embedded"]) { this.positionCalendarDiv(true) }//.bindAsEventListener(this), 1); + this.callback("after_show") + }, + positionCalendarDiv: function(post_painted) { + above=false; + c_pos = Position.cumulativeOffset(this.calendar_div); c_left = c_pos[0]; c_top = c_pos[1]; c_dim = this.calendar_div.getDimensions(); c_height = c_dim.height; c_width = c_dim.width; + w_top = window.f_scrollTop(); w_height = window.f_height(); + e_dim = Position.cumulativeOffset($(this.options.popup_by)); e_top = e_dim[1]; e_left = e_dim[0]; + + if ( (post_painted) && (( c_top + c_height ) > (w_top + w_height)) && ( c_top - c_height > w_top )) above=true; + left_px = e_left.toString() + "px"; + top_px = (above ? (e_top - c_height ) : ( e_top + $(this.options.popup_by).getDimensions().height )).toString() + "px"; + + this.calendar_div.style.left = left_px; this.calendar_div.style.top = top_px; + + // draw an iframe behind the calendar -- ugly hack to make IE 6 happy + if (post_painted) + { + this.iframe = $(document.body).build("iframe", {}, { position:"absolute", left: left_px, top: top_px, height: c_height.toString()+"px", width: c_width.toString()+"px", border: "0px"}) + this.calendar_div.setStyle({visibility:""}); + } + }, + initFrame: function() { + that=this; + // create the divs + $w("top header body buttons footer bottom").each(function(name) { + eval(name + "_div = that." + name + "_div = that.calendar_div.build('div', { className: 'cds_"+name+"' }, { clear: 'left'} ); "); + }); + + this.initButtonsDiv(); + this.updateFooter(" "); + // make the header buttons + this.prev_month_button = header_div.build("a", { innerHTML: "<", href:"#", onclick:function(){return false;}, className: "prev" }); + this.next_month_button = header_div.build("a", { innerHTML: ">", href:"#", onclick:function(){return false;}, className: "next" }); + Event.observe(this.prev_month_button, 'mousedown', function () { this.navMonth(this.date.getMonth() - 1 ) }.bindAsEventListener(this)); + Event.observe(this.next_month_button, 'mousedown', function () { this.navMonth(this.date.getMonth() + 1 ) }.bindAsEventListener(this)); + + if (this.options.month_year=="dropdowns") { + this.month_select = new SelectBox(header_div, $R(0,11).map(function(m){return [Date.months[m], m]}), {className: "month", onchange: function () { this.navMonth(this.month_select.getValue()) }.bindAsEventListener(this)}); + this.year_select = new SelectBox(header_div, [], {className: "year", onchange: function () { this.navYear(this.year_select.getValue()) }.bindAsEventListener(this)}); + } else { + this.month_year_label = header_div.build("span") + } + + // build the calendar day grid + this.calendar_day_grid = []; + days_table = body_div.build("table", { cellPadding: "0px", cellSpacing: "0px", width: "100%" }) + // make the weekdays! + weekdays_row = days_table.build("thead").build("tr"); + Date.weekdays.each( function(weekday) { + weekdays_row.build("th", {innerHTML: weekday}); + }); + + days_tbody = days_table.build("tbody") + // Make the days! + row_number=0 + for(cell_index=0; cell_index<42; cell_index++) + { + weekday=(cell_index+Date.first_day_of_week ) % 7; + if ( cell_index % 7==0 ) days_row = days_tbody.build("tr", {className: 'row_'+row_number++}); + (this.calendar_day_grid[cell_index] = days_row.build("td", { + calendar_date_select: this, + onmouseover: function () { this.calendar_date_select.dayHover(this); }, + onmouseout: function () { this.calendar_date_select.dayHoverOut(this) }, + onclick: function() { this.calendar_date_select.updateSelectedDate(this); }, + className: (weekday==0) || (weekday==6) ? " weekend" : "" //clear the class + }, + { cursor: "pointer" } + )).build("div"); + this.calendar_day_grid[cell_index]; + } + this.refresh(); + }, + initButtonsDiv: function() + { + buttons_div = this.buttons_div; + // make the time div + if (this.options["time"]) + { + blank_time = $A(this.options.time=="mixed" ? [[" - ", ""]] : []); + buttons_div.build("span", {innerHTML:"@", className: "at_sign"}); + + t=new Date(); + this.hour_select = new SelectBox(buttons_div, + blank_time.concat($R(0,23).map(function(x) {t.setHours(x); return $A([t.getAMPMHour()+ " " + t.getAMPM(),x])} )), + { + calendar_date_select: this, + onchange: function() { this.calendar_date_select.updateSelectedDate( { hour: this.value });}, + className: "hour" + } + ); + buttons_div.build("span", {innerHTML:":", className: "seperator"}); + that=this; + this.minute_select = new SelectBox(buttons_div, + blank_time.concat($R(0,59).select(function(x){return (x % that.options.minute_interval==0)}).map(function(x){ return $A([ Date.padded2(x), x]); } ) ), + { + calendar_date_select: this, + onchange: function() { this.calendar_date_select.updateSelectedDate( {minute: this.value }) }, + className: "minute" + } + ); + } else if (! this.options.buttons) buttons_div.remove(); + + if (this.options.buttons) { + buttons_div.build("span", {innerHTML: " "}); + // Build Today button + if (this.options.time=="mixed" || !this.options.time) b=buttons_div.build("a", { + innerHTML: _translations["Today"], + href: "#", + onclick: function() {this.today(false); return false;}.bindAsEventListener(this) + }); + + if (this.options.time=="mixed") buttons_div.build("span", {innerHTML: " | ", className:"button_seperator"}) + + if (this.options.time) b = buttons_div.build("a", { + innerHTML: _translations["Now"], + href: "#", + onclick: function() {this.today(true); return false}.bindAsEventListener(this) + }); + } + }, + dateString: function() { + return (this.selection_made) ? this.selected_date.toFormattedString(this.use_time) : " "; + }, + navMonth: function(month) { + prev_day = this.date.getDate(); + this.date.setMonth(month); + + this.refresh(); + this.callback("after_navigate", this.date); + }, + navYear: function(year) { + this.date.setYear(year); + this.refresh(); + this.callback("after_navigate", this.date); + }, + refresh: function () + { + m=this.date.getMonth() + y=this.date.getFullYear(); + // set the month + if (this.options.month_year == "dropdowns") + { + this.month_select.setValue(m, false); + + e=this.year_select.element; + if ( !(this.year_select.setValue(y)) || e.selectedIndex <= 1 || e.selectedIndex >= e.options.length - 2 ) { + this.year_select.populate( $R( y - this.options.year_range, y+this.options.year_range ).toArray() ); + this.year_select.setValue(y) + } + } else { + this.month_year_label.update( Date.months[m] + " " + y.toString() ); + } + + // populate the calendar_day_grid + this.beginning_date = new Date(this.date).stripTime(); + this.beginning_date.setDate(1); + pre_days = this.beginning_date.getDay() // draw some days before the fact + if (pre_days < 3) pre_days+=7; + this.beginning_date.setDate(1 - pre_days + Date.first_day_of_week); + + iterator = new Date(this.beginning_date); + + today = new Date().stripTime(); + this_month = this.date.getMonth(); + for (cell_index=0;cell_index<42; cell_index++) + { + day = iterator.getDate(); month = iterator.getMonth(); + cell = this.calendar_day_grid[cell_index]; + Element.remove(cell.childNodes[0]); div = cell.build("div", {innerHTML:day}); + if (month!=this_month) div.className = "other"; + cell.day=day; cell.month = month; cell.year = iterator.getFullYear(); + iterator.setDate( day + 1); + } + + if (this.today_cell) this.today_cell.removeClassName("today"); + + if ( $R( 0, 42 ).include(days_until = this.beginning_date.daysDistance(today)) ) { + this.today_cell = this.calendar_day_grid[days_until]; + this.today_cell.addClassName("today"); + } + + // set the time + this.setUseTime(this.use_time); + + this.setSelectedClass(); + this.updateFooter(); + }, + dayHover: function(element) { + element.addClassName("hover"); + hover_date = new Date(this.selected_date); + hover_date.setYear(element.year); hover_date.setMonth(element.month); hover_date.setDate(element.day); + this.updateFooter(hover_date.toFormattedString(this.use_time)); + }, + dayHoverOut: function(element) { element.removeClassName("hover"); this.updateFooter(); }, + setSelectedClass: function() { + if (!this.selection_made) return; + + // clear selection + if (this.selected_cell) this.selected_cell.removeClassName("selected"); + + if ($R(0,42).include( days_until = this.beginning_date.daysDistance(this.selected_date.stripTime()) )) { + this.selected_cell = this.calendar_day_grid[days_until]; + this.selected_cell.addClassName("selected"); + } + }, + reparse: function() { this.parseDate(); this.refresh(); }, + parseDate: function() + { + value = $F(this.target_element).strip() + this.date = value=="" ? NaN : Date.parseFormattedString(this.options['date'] || value); + if (isNaN(this.date)) this.date = new Date(); + this.selected_date = new Date(this.date); + this.use_time = /[0-9]:[0-9]{2}/.exec(value) ? true : false; + this.date.setDate(1); + }, + updateFooter:function(text) { if (!text) text=this.dateString(); this.footer_div.purgeChildren(); this.footer_div.build("span", {innerHTML: text }); }, + updateSelectedDate:function(parts) { + if ((this.target_element.disabled || this.target_element.readOnly) && this.options.popup!="force") return false; + if (parts.day) { + this.selection_made = true; + for (x=0; x<=1; x++) { + this.selected_date.setDate(parts.day); + this.selected_date.setMonth(parts.month); + this.selected_date.setYear(parts.year);} + } + + if (!isNaN(parts.hour)) this.selected_date.setHours(parts.hour); + if (!isNaN(parts.minute)) this.selected_date.setMinutes( Math.floor_to_interval(parts.minute, this.options.minute_interval) ); + if (parts.hour === "" || parts.minute === "") + this.setUseTime(false); + else if (!isNaN(parts.hour) || !isNaN(parts.minute)) + this.setUseTime(true); + + this.updateFooter(); + this.setSelectedClass(); + + if (this.selection_made) this.updateValue(); + if (this.options.close_on_click) { this.close(); } + }, + setUseTime: function(turn_on) { + this.use_time = this.options.time && (this.options.time=="mixed" ? turn_on : true) // force use_time to true if time==true && time!="mixed" + if (this.use_time && this.selected_date) { // only set hour/minute if a date is already selected + minute = Math.floor_to_interval(this.selected_date.getMinutes(), this.options.minute_interval); + hour = this.selected_date.getHours(); + + this.hour_select.setValue(hour); + this.minute_select.setValue(minute) + } else if (this.options.time=="mixed") { + this.hour_select.setValue(""); this.minute_select.setValue(""); + } + }, + updateValue: function() { + last_value = this.target_element.value; + this.target_element.value = this.dateString(); + if (last_value!=this.target_element.value) this.callback("onchange"); + }, + today: function(now) { + d=new Date(); this.date = new Date(); + o = $H({ day: d.getDate(), month: d.getMonth(), year: d.getFullYear(), hour: d.getHours(), minute: d.getMinutes()}); + if ( ! now ) o = o.merge({hour: "", minute:""}); + this.updateSelectedDate(o); + this.refresh(); + }, + close: function() { + if (this.closed) return false; + this.callback("before_close"); + this.target_element.calendar_date_select = nil; + Event.stopObserving(document.body, "mousedown", this.bodyClick_handler); + this.calendar_div.remove(); this.closed=true; + if (this.iframe) this.iframe.remove(); + this.target_element.focus(); + this.callback("after_close"); + }, + bodyClick: function(e) { // checks to see if somewhere other than calendar date select grid was clicked. in which case, close + if (! $(Event.element(e)).descendantOf(this.calendar_div) ) this.close(); + }, + callback: function(name, param) { if (this.options[name]) { this.options[name].bind(this.target_element)(param); } } +} \ No newline at end of file diff --git a/chapter12/training/public/javascripts/calendar_date_select/format_finnish.js b/chapter12/training/public/javascripts/calendar_date_select/format_finnish.js new file mode 100644 index 0000000..67d453e --- /dev/null +++ b/chapter12/training/public/javascripts/calendar_date_select/format_finnish.js @@ -0,0 +1,24 @@ +Date.padded2 = function(hour) { padded2 = hour.toString(); if ((parseInt(hour) < 10) || (parseInt(hour) == null)) padded2="0" + padded2; return padded2; } +Date.prototype.getAMPMHour = function() { hour=Date.padded2(this.getHours()); return (hour == null) ? 00 : (hour > 24 ? hour - 24 : hour ) } +Date.prototype.getAMPM = function() { return (this.getHours() < 12) ? "" : ""; } + +Date.prototype.toFormattedString = function(include_time){ + str = this.getDate() + "." + (this.getMonth() + 1) + "." + this.getFullYear(); + if (include_time) { hour=this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() } + return str; +} +Date.parseFormattedString = function (string) { + var regexp = '([0-9]{1,2})\.(([0-9]{1,2})\.(([0-9]{4})( ([0-9]{1,2}):([0-9]{2})? *)?)?)?'; + var d = string.match(new RegExp(regexp, "i")); + if (d==null) return Date.parse(string); // at least give javascript a crack at it. + var offset = 0; + var date = new Date(d[5], 0, 1); + if (d[3]) { date.setMonth(d[3] - 1); } + if (d[5]) { date.setDate(d[1]); } + if (d[7]) { + date.setHours(parseInt(d[7])); + } + if (d[8]) { date.setMinutes(d[8]); } + if (d[10]) { date.setSeconds(d[10]); } + return date; +} diff --git a/chapter12/training/public/javascripts/calendar_date_select/format_hyphen_ampm.js b/chapter12/training/public/javascripts/calendar_date_select/format_hyphen_ampm.js new file mode 100644 index 0000000..0a3d7ee --- /dev/null +++ b/chapter12/training/public/javascripts/calendar_date_select/format_hyphen_ampm.js @@ -0,0 +1,36 @@ +Date.prototype.toFormattedString = function(include_time){ + str = this.getFullYear() + "-" + Date.padded2(this.getMonth() + 1) + "-" +Date.padded2(this.getDate()); + +if (include_time) { hour=this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM() } +return str; +} + +Date.parseFormattedString = function (string) { + var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + + "( ([0-9]{1,2}):([0-9]{2})? *(pm|am)" + + "?)?)?)?"; + var d = string.match(new RegExp(regexp, "i")); + if (d==null) return Date.parse(string); // at least give javascript a crack at it. + var offset = 0; + var date = new Date(d[1], 0, 1); + if (d[3]) { date.setMonth(d[3] - 1); } + if (d[5]) { date.setDate(d[5]); } + if (d[7]) { + hours = parseInt(d[7]); + offset=0; + is_pm = (d[9].toLowerCase()=="pm") + if (is_pm && hours <= 11) hours+=12; + if (!is_pm && hours == 12) hours=0; + date.setHours(hours); + + } + if (d[8]) { date.setMinutes(d[8]); } + if (d[10]) { date.setSeconds(d[10]); } + if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); } + if (d[14]) { + offset = (Number(d[16]) * 60) + Number(d[17]); + offset *= ((d[15] == '-') ? 1 : -1); + } + + return date; +} \ No newline at end of file diff --git a/chapter12/training/public/javascripts/controls.js b/chapter12/training/public/javascripts/controls.js new file mode 100644 index 0000000..8c273f8 --- /dev/null +++ b/chapter12/training/public/javascripts/controls.js @@ -0,0 +1,833 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005, 2006 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 +// 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, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + 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; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + 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; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + 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++ + 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()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + 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 { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// 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. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + 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) + "" + + elem.substr(entry.length) + "
    • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
    • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
    • "); + break; + } + } + + 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)) + return "
        " + ret.join('') + "
      "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + paramName: "value", + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
      /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
      /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

      /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = this.options.paramName; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = this.options.paramName; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/chapter12/training/public/javascripts/dragdrop.js b/chapter12/training/public/javascripts/dragdrop.js new file mode 100644 index 0000000..c71ddb8 --- /dev/null +++ b/chapter12/training/public/javascripts/dragdrop.js @@ -0,0 +1,942 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// 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/ + +if(typeof Effect == 'undefined') + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + 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; + } 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( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + 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); + }, + + reset: function() { + 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) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + 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); + } 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)]; + // Mozilla-based browsers fire successive mousemove events with + // 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.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) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') + 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}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + 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 + + this.delta = this.currentDelta(); + this.options = options; + 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(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + 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.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); + 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); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + 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 ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + 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); + 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]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*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); + this.scrollInterval = null; + 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; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + 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) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + 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)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + 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({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // 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); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + $(e).down('.'+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); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + 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); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, 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); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + 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, + parent: parent, + 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) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + 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); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var 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]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).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) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/chapter12/training/public/javascripts/effects.js b/chapter12/training/public/javascripts/effects.js new file mode 100644 index 0000000..3b02eda --- /dev/null +++ b/chapter12/training/public/javascripts/effects.js @@ -0,0 +1,1088 @@ +// Copyright (c) 2005, 2006 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/ + +// 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)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + return element; +} + +Element.getOpacity = function(element){ + element = $(element); + var opacity; + if (opacity = element.getStyle('opacity')) + return parseFloat(opacity); + if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + element.setStyle({ opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : 1.0 }); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + element.setStyle({opacity: value}); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle( + { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } + return element; +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + Math.round((pos % (1/pulses)) * pulses) == 0 ? + ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : + 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) + ); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } +}; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + 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), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Event = Class.create(); +Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { + initialize: function() { + var options = Object.extend({ + duration: 0 + }, arguments[0] || {}); + this.start(options); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + 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]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + 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(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + 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, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ 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(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // 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, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + 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' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); + effect.element.down().undoPositioned(); + } + }, arguments[1] || {}) + ); +} + +// 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, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + 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({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +} + +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, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +Effect.Morph = Class.create(); +Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: '' + }, arguments[1] || {}); + 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 ) + }); + } + this.transforms = this.options.style.parseStyle().map(function(property){ + var originalValue = this.element.getStyle(property[0]); + return $H({ + style: property[0], + originalValue: property[1].unit=='color' ? + parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: property[1].unit=='color' ? + parseColor(property[1].value) : property[1].value, + unit: property[1].unit + }); + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ) + }); + }, + update: function(position) { + var style = $H(), value = null; + this.transforms.each(function(transform){ + value = transform.unit=='color' ? + $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(transform.originalValue[i]+ + (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) : + transform.originalValue + Math.round( + ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; + style[transform.style] = value; + }); + this.element.setStyle(style); + } +}); + +Effect.Transform = Class.create(); +Object.extend(Effect.Transform.prototype, { + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || {}; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + var data = $H(track).values().first(); + this.tracks.push($H({ + ids: $H(track).keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var elements = [$(track.ids) || $$(track.ids)].flatten(); + return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage', + 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle', + 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth', + 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor', + 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content', + 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction', + 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', + 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight', + 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight', + 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity', + 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY', + 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore', + 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes', + 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress', + 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top', + 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows', + 'width', 'wordSpacing', 'zIndex']; + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.prototype.parseStyle = function(){ + var element = Element.extend(document.createElement('div')); + element.innerHTML = '

      '; + var style = element.down().style, styleRules = $H(); + + Element.CSS_PROPERTIES.each(function(property){ + if(style[property]) styleRules[property] = style[property]; + }); + + var result = $H(); + + styleRules.each(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if(value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if(Element.CSS_LENGTH.test(value)) + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/), + value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null; + + result[property.underscore().dasherize()] = $H({ value:value, unit:unit }); + }.bind(this)); + + return result; +}; + +Element.morph = function(element, style) { + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); + return element; +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/chapter12/training/public/javascripts/prototype.js b/chapter12/training/public/javascripts/prototype.js new file mode 100644 index 0000000..5058221 --- /dev/null +++ b/chapter12/training/public/javascripts/prototype.js @@ -0,0 +1,2515 @@ +/* Prototype JavaScript framework, version 1.5.0 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0', + BrowserFeatures: { + XPath: !!document.evaluate + }, + + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + emptyFunction: function() {}, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +String.interpret = function(value){ + return value == null ? '' : String(value); +} + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('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) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var name = decodeURIComponent(pair[0]); + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; + + if (hash[name] !== undefined) { + if (hash[name].constructor != Array) + hash[name] = [hash[name]]; + if (value) hash[name].push(value); + } + else hash[name] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + 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; + }, + + capitalize: function(){ + 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(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.replace(/\\/g, '\\\\'); + if (useDoubleQuotes) + return '"' + escapedString.replace(/"/g, '\\"') + '"'; + else + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function() { + return this.inject([], function(array, value) { + return array.include(value) ? array : array.concat([value]); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string){ + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if(window.opera){ + Array.prototype.concat = function(){ + var array = []; + for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for(var i = 0, length = arguments.length; i < length; i++) { + if(arguments[i].constructor == Array) { + for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(obj) { + Object.extend(this, obj || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + + if (pair.value && pair.value.constructor == Array) { + var values = pair.value.compact(); + if (values.length < 2) pair.value = values.reduce(); + else { + key = encodeURIComponent(pair.key); + values.each(function(value) { + value = value != undefined ? encodeURIComponent(value) : ''; + parts.push(key + '=' + encodeURIComponent(value)); + }); + return; + } + } + if (pair.value == undefined) pair[1] = ''; + parts.push(pair.map(encodeURIComponent).join('=')); + }); + + return parts.join('&'); + } +}); + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + } +}); + +function $H(object) { + if (object && object.constructor == Hash) return object; + return new Hash(object); +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = this.options.parameters; + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + params = Hash.toQueryString(params); + if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_=' + + // when GET, append parameters to URL + if (this.method == 'get' && params) + this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; + + try { + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + var body = this.method == 'post' ? (this.options.postBody || params) : null; + + this.transport.send(body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.getHeader('Content-type') || 'text/javascript').strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? eval('(' + json + ')') : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + 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++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; +} + +document.getElementsByClassName = function(className, parentElement) { + if (Prototype.BrowserFeatures.XPath) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } else { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child; + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + if (Element.hasClassName(child, className)) + elements.push(Element.extend(child)); + } + return elements; + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element || _nativeExtensions || element.nodeType == 3) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; + + if (element.tagName == 'FORM') + Object.extend(methods, Form.Methods); + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) + Object.extend(methods, Form.Element.Methods); + + Object.extend(methods, Element.Methods.Simulated); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + 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(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + return Selector.findElement($(element).ancestors(), expression, index); + }, + + down: function(element, expression, index) { + return Selector.findElement($(element).descendants(), expression, index); + }, + + previous: function(element, expression, index) { + return Selector.findElement($(element).previousSiblings(), expression, index); + }, + + next: function(element, expression, index) { + return Selector.findElement($(element).nextSiblings(), expression, index); + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (document.all && !window.opera) { + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + if(attribute) return attribute.nodeValue; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + if (['float','cssFloat'].include(style)) + style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); + style = style.camelize(); + var value = element.style[style]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } else if (element.currentStyle) { + value = element.currentStyle[style]; + } + } + + if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) + value = element['offset'+style.capitalize()] + 'px'; + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + if(style == 'opacity') { + if(value) return parseFloat(value); + if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) { + var value = style[name]; + if(name == 'opacity') { + if (value == 1) { + value = (/Gecko/.test(navigator.userAgent) && + !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else if(value == '') { + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')'; + } + } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; + element.style[name.camelize()] = value; + } + 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) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); + +Element._attributeTranslations = {}; + +Element._attributeTranslations.names = { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" +}; + +Element._attributeTranslations.values = { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } +}; + +Object.extend(Element._attributeTranslations.values, { + href: Element._attributeTranslations.values._getAttr, + src: Element._attributeTranslations.values._getAttr, + disabled: Element._attributeTranslations.values._flag, + checked: Element._attributeTranslations.values._flag, + readonly: Element._attributeTranslations.values._flag, + multiple: Element._attributeTranslations.values._flag +}); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations; + attribute = t.names[attribute] || attribute; + return $(element).getAttributeNode(attribute).specified; + } +}; + +// IE is missing .innerHTML support for TABLE-related elements +if (document.all && !window.opera){ + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
      '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
      '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
      ' + html.stripScripts() + '
      '; + depth = 4; + } + $A(element.childNodes).each(function(node){ + element.removeChild(node) + }); + depth.times(function(){ div = div.firstChild }); + + $A(div.childNodes).each( + function(node){ element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + } +}; + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { + var className = 'HTML' + tag + 'Element'; + if(window[className]) return; + var klass = window[className] = {}; + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; + }); + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + if (typeof HTMLElement != 'undefined') { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Form.Methods, HTMLFormElement.prototype); + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { + copy(Form.Element.Methods, klass.prototype); + }); + _nativeExtensions = true; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
      '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.readAttribute("id") == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0, length = clause.length; i < length; i++) + conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + element = $(element); \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0, length = scope.length; i < length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +Object.extend(Selector, { + matchElements: function(elements, expression) { + var selector = new Selector(expression); + return elements.select(selector.match.bind(selector)).map(Element.extend); + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') index = expression, expression = false; + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + return expressions.map(function(expression) { + return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.inject([], function(elements, result) { + return elements.concat(selector.findElements(result || element)); + }); + }); + }).flatten(); + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != undefined) { + if (result[key]) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + 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; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.blur(); + element.disabled = 'true'; + }); + return form; + }, + + enable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.disabled = ''; + }); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + } +} + +Object.extend(Form, Form.Methods); + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select && ( element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type) ) ) + element.select(); + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.blur(); + element.disabled = false; + return element; + } +} + +Object.extend(Form.Element, Form.Element.Methods); +var Field = Form.Element; +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +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, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (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; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + 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(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(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) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff --git a/chapter12/training/public/robots.txt b/chapter12/training/public/robots.txt new file mode 100644 index 0000000..4ab9e89 --- /dev/null +++ b/chapter12/training/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/chapter12/training/public/stylesheets/calendar_date_select/blue.css b/chapter12/training/public/stylesheets/calendar_date_select/blue.css new file mode 100644 index 0000000..f3639d3 --- /dev/null +++ b/chapter12/training/public/stylesheets/calendar_date_select/blue.css @@ -0,0 +1,112 @@ +.calendar_date_select { + color:white; + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; +} + +.calendar_date_select thead th { + font-weight:bold; + background-color: #000; + border-top:1px solid #777; + border-bottom:2px solid #334; + color: white !important; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; + background-color: #000055; +} + +.calendar_date_select .cds_footer { + background-color: black; + padding:3px; + font-size:12px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + background-color: #ccc; + border-bottom: 2px solid #aaa; + text-align:center; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} + +.calendar_date_select .cds_header a.next { + float:right; +} + +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} + +.calendar_date_select .cds_buttons a { + color: white; + font-size: 9px; +} + +.calendar_date_select td { + background-color: #000066; + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; +} +.calendar_date_select td.weekend { + background-color: #00005a; +} + +.calendar_date_select td div.other { + color: #4C5593; +} + +.calendar_date_select tbody td { + border-bottom: 1px solid #000044; +} +.calendar_date_select td.selected { + background-color:white; + color:black; +} + +.calendar_date_select td.hover { + background-color:#ccc; +} + +.calendar_date_select td.today { + border: 1px dashed blue; +} \ No newline at end of file diff --git a/chapter12/training/public/stylesheets/calendar_date_select/default.css b/chapter12/training/public/stylesheets/calendar_date_select/default.css new file mode 100644 index 0000000..00b340d --- /dev/null +++ b/chapter12/training/public/stylesheets/calendar_date_select/default.css @@ -0,0 +1,116 @@ +.calendar_date_select { + color:white; + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; +} + +.calendar_date_select thead th { + font-weight:bold; + background-color: #aaa; + border-top:1px solid #777; + border-bottom:1px solid #777; + color: white !important; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; + background-color: #555; +} + +.calendar_date_select .cds_footer { + background-color: black; + padding:3px; + font-size:12px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + background-color: #ccc; + border-bottom: 2px solid #aaa; + text-align:center; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} +.calendar_date_select .cds_header a.next { + float:right; +} +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} +.calendar_date_select .cds_buttons a { + color: white; + font-size: 9px; +} + +.calendar_date_select td { + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; + background-color: #fff; +} +.calendar_date_select td.weekend { + background-color: #eee; + border-left:1px solid #ddd; + border-right:1px solid #ddd; +} + +.calendar_date_select td div { + color: #000; +} +.calendar_date_select td div.other { + color: #ccc; +} +.calendar_date_select td.selected div { + color:white; +} + +.calendar_date_select tbody td { + border-bottom: 1px solid #ddd; +} +.calendar_date_select td.selected { + background-color:#777; +} + +.calendar_date_select td.hover { + background-color:#ccc; +} + +.calendar_date_select td.today { + border: 1px dashed #999; +} \ No newline at end of file diff --git a/chapter12/training/public/stylesheets/calendar_date_select/plain.css b/chapter12/training/public/stylesheets/calendar_date_select/plain.css new file mode 100644 index 0000000..0c1b34a --- /dev/null +++ b/chapter12/training/public/stylesheets/calendar_date_select/plain.css @@ -0,0 +1,110 @@ +.calendar_date_select { + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; + background-color:white; +} + +.calendar_date_select thead th { + color: black !important; + font-weight:bold; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; +} + +.calendar_date_select .cds_footer { + padding:3px; + font-size:10px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + text-align:center; +} + +.calendar_date_select .cds_header * { + border:0px; + background-color:white; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} +.calendar_date_select .cds_header a.next { + float:right; +} +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} + +.calendar_date_select .cds_buttons a { + color: black; + font-size: 9px; +} +.calendar_date_select td { + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; + background-color: #fff; +} +.calendar_date_select td.weekend { +} + +.calendar_date_select td div { + color: #000; +} +.calendar_date_select td div.other { + color: #ccc; +} +.calendar_date_select td.selected div { + color:white; +} + +.calendar_date_select tbody td { +} +.calendar_date_select td.selected { + background-color:#777; +} + +.calendar_date_select td.hover { + background-color:#ccc; +} + +.calendar_date_select td.today { + border: 1px dashed #999; +} \ No newline at end of file diff --git a/chapter12/training/public/stylesheets/calendar_date_select/red.css b/chapter12/training/public/stylesheets/calendar_date_select/red.css new file mode 100644 index 0000000..eb7ee4c --- /dev/null +++ b/chapter12/training/public/stylesheets/calendar_date_select/red.css @@ -0,0 +1,119 @@ +.calendar_date_select { + color:white; + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; +} + +.calendar_date_select thead th { + font-weight:bold; + background-color: #E7E8E8; + border-bottom:2px solid black; + color: black !important; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; + background-color: #5f0000; + +} + +.calendar_date_select .cds_footer { + background-color: black; + padding:3px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + background-color: #ccc; + border-bottom: 2px solid #aaa; + text-align:center; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} +.calendar_date_select .cds_header a.next { + float:right; +} + +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} + +.calendar_date_select .cds_buttons a { + color: white; + font-size: 9px; +} +.calendar_date_select td { + background-color: #660000; + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; +} +.calendar_date_select td.weekend { + background-color: #5a0000; +} + +.calendar_date_select td div { + color:#fff; +} +.calendar_date_select td div.other { + color: #93554C; +} +.calendar_date_select td.selected div { + color:black; +} + + +.calendar_date_select tbody td { + border-bottom: 1px solid #550000; +} +.calendar_date_select tbody td.selected { + background-color:white; + color:black; +} + +.calendar_date_select tbody td.hover { + background-color:#ccc; +} + +.calendar_date_select tbody td.today { + border: 1px dashed red; +} + +/* adjust bordered cells to have slightly smaller inner areas so they look the same as the rest of the elements */ + \ No newline at end of file diff --git a/chapter12/training/public/stylesheets/calendar_date_select/silver.css b/chapter12/training/public/stylesheets/calendar_date_select/silver.css new file mode 100644 index 0000000..0a44924 --- /dev/null +++ b/chapter12/training/public/stylesheets/calendar_date_select/silver.css @@ -0,0 +1,114 @@ +.calendar_date_select { + color:white; + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; +} + +.calendar_date_select thead th { + font-weight:bold; + background-color: #000; + border-top:1px solid #777; + border-bottom:2px solid #333; + color: white !important; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; + background-color: #555; +} + +.calendar_date_select .cds_footer { + background-color: black; + padding:3px; + font-size:12px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + background-color: #ccc; + border-bottom: 2px solid #aaa; + text-align:center; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} +.calendar_date_select .cds_header a.next { + float:right; +} +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} + +.calendar_date_select .cds_buttons a { + color: white; + font-size: 9px; +} +.calendar_date_select td { + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; + background-color: #666666; +} +.calendar_date_select td.weekend { + background-color: #606060; +} + +.calendar_date_select td div { + color: #fff; +} +.calendar_date_select td div.other { + color: #888; +} +.calendar_date_select td.selected div { + color:black; +} + +.calendar_date_select tbody td { + border-bottom: 1px solid #555; +} +.calendar_date_select td.selected { + background-color:white; +} + +.calendar_date_select td.hover { + background-color:#ccc; +} + +.calendar_date_select td.today { + border: 1px dashed #999; +} \ No newline at end of file diff --git a/chapter12/training/public/stylesheets/log.css b/chapter12/training/public/stylesheets/log.css new file mode 100644 index 0000000..b94e75a --- /dev/null +++ b/chapter12/training/public/stylesheets/log.css @@ -0,0 +1,13 @@ +* { + display:block; + font-family: helvetica, verdana, sans-serif; +} +grades { + padding:1em; +} + +salesperson { + font-weight: bold; + margin-top:1em; +} + diff --git a/chapter12/training/public/stylesheets/training.css b/chapter12/training/public/stylesheets/training.css new file mode 100644 index 0000000..f47973e --- /dev/null +++ b/chapter12/training/public/stylesheets/training.css @@ -0,0 +1,8 @@ +* { font-family: helvetica, verdana, sans-serif; } + +div.flash_notice { padding:1em; border: 2px dashed #cecece; margin: 1em 0;} + +input.submit_button { + width:120px; + height:30px; +} diff --git a/chapter12/training/script/about b/chapter12/training/script/about new file mode 100644 index 0000000..7b07d46 --- /dev/null +++ b/chapter12/training/script/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about' \ No newline at end of file diff --git a/chapter12/training/script/breakpointer b/chapter12/training/script/breakpointer new file mode 100644 index 0000000..64af76e --- /dev/null +++ b/chapter12/training/script/breakpointer @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' \ No newline at end of file diff --git a/chapter12/training/script/console b/chapter12/training/script/console new file mode 100644 index 0000000..42f28f7 --- /dev/null +++ b/chapter12/training/script/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' \ No newline at end of file diff --git a/chapter12/training/script/destroy b/chapter12/training/script/destroy new file mode 100644 index 0000000..fa0e6fc --- /dev/null +++ b/chapter12/training/script/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' \ No newline at end of file diff --git a/chapter12/training/script/generate b/chapter12/training/script/generate new file mode 100644 index 0000000..ef976e0 --- /dev/null +++ b/chapter12/training/script/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' \ No newline at end of file diff --git a/chapter12/training/script/performance/benchmarker b/chapter12/training/script/performance/benchmarker new file mode 100644 index 0000000..c842d35 --- /dev/null +++ b/chapter12/training/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/chapter12/training/script/performance/profiler b/chapter12/training/script/performance/profiler new file mode 100644 index 0000000..d855ac8 --- /dev/null +++ b/chapter12/training/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/chapter12/training/script/plugin b/chapter12/training/script/plugin new file mode 100644 index 0000000..26ca64c --- /dev/null +++ b/chapter12/training/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' \ No newline at end of file diff --git a/chapter12/training/script/process/inspector b/chapter12/training/script/process/inspector new file mode 100644 index 0000000..bf25ad8 --- /dev/null +++ b/chapter12/training/script/process/inspector @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/inspector' diff --git a/chapter12/training/script/process/reaper b/chapter12/training/script/process/reaper new file mode 100644 index 0000000..c77f045 --- /dev/null +++ b/chapter12/training/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/chapter12/training/script/process/spawner b/chapter12/training/script/process/spawner new file mode 100644 index 0000000..7118f39 --- /dev/null +++ b/chapter12/training/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/chapter12/training/script/runner b/chapter12/training/script/runner new file mode 100644 index 0000000..ccc30f9 --- /dev/null +++ b/chapter12/training/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/chapter12/training/script/server b/chapter12/training/script/server new file mode 100644 index 0000000..dfabcb8 --- /dev/null +++ b/chapter12/training/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/chapter12/training/test/fixtures/grades.yml b/chapter12/training/test/fixtures/grades.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter12/training/test/fixtures/grades.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter12/training/test/fixtures/logs.yml b/chapter12/training/test/fixtures/logs.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter12/training/test/fixtures/logs.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter12/training/test/fixtures/salespeople.yml b/chapter12/training/test/fixtures/salespeople.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter12/training/test/fixtures/salespeople.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter12/training/test/fixtures/training_classes.yml b/chapter12/training/test/fixtures/training_classes.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter12/training/test/fixtures/training_classes.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter12/training/test/functional/homepage_controller_test.rb b/chapter12/training/test/functional/homepage_controller_test.rb new file mode 100644 index 0000000..2ee4597 --- /dev/null +++ b/chapter12/training/test/functional/homepage_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'homepage_controller' + +# Re-raise errors caught by the controller. +class HomepageController; def rescue_action(e) raise e end; end + +class HomepageControllerTest < Test::Unit::TestCase + def setup + @controller = HomepageController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter12/training/test/functional/uploader_controller_test.rb b/chapter12/training/test/functional/uploader_controller_test.rb new file mode 100644 index 0000000..e5672a2 --- /dev/null +++ b/chapter12/training/test/functional/uploader_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'uploader_controller' + +# Re-raise errors caught by the controller. +class UploaderController; def rescue_action(e) raise e end; end + +class UploaderControllerTest < Test::Unit::TestCase + def setup + @controller = UploaderController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter12/training/test/test_helper.rb b/chapter12/training/test/test_helper.rb new file mode 100644 index 0000000..a299c7f --- /dev/null +++ b/chapter12/training/test/test_helper.rb @@ -0,0 +1,28 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' + +class Test::Unit::TestCase + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # Add more helper methods to be used by all tests here... +end diff --git a/chapter12/training/test/unit/grade_test.rb b/chapter12/training/test/unit/grade_test.rb new file mode 100644 index 0000000..a9170cf --- /dev/null +++ b/chapter12/training/test/unit/grade_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class GradeTest < Test::Unit::TestCase + fixtures :grades + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter12/training/test/unit/log_test.rb b/chapter12/training/test/unit/log_test.rb new file mode 100644 index 0000000..b147340 --- /dev/null +++ b/chapter12/training/test/unit/log_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class LogTest < Test::Unit::TestCase + fixtures :logs + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter12/training/test/unit/salesperson_test.rb b/chapter12/training/test/unit/salesperson_test.rb new file mode 100644 index 0000000..6cebf8d --- /dev/null +++ b/chapter12/training/test/unit/salesperson_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class SalespersonTest < Test::Unit::TestCase + fixtures :salespeople + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter12/training/test/unit/training_class_test.rb b/chapter12/training/test/unit/training_class_test.rb new file mode 100644 index 0000000..f5724f0 --- /dev/null +++ b/chapter12/training/test/unit/training_class_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class TrainingClassTest < Test::Unit::TestCase + fixtures :training_classes + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter12/training/vendor/plugins/calendar_date_select/MIT-LICENSE b/chapter12/training/vendor/plugins/calendar_date_select/MIT-LICENSE new file mode 100644 index 0000000..104e782 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/MIT-LICENSE @@ -0,0 +1,20 @@ +All portions Copyright (c) 2007 Tim Harper + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/chapter12/training/vendor/plugins/calendar_date_select/README b/chapter12/training/vendor/plugins/calendar_date_select/README new file mode 100644 index 0000000..51abf4c --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/README @@ -0,0 +1,10 @@ +::CalendarDateSelect:: +Author: Tim Harper ( 'tim_see_harperATgmail._see_om'.gsub('_see_', 'c').gsub('AT', '@') ) + +::Examples:: +see demo here -- http://restatesman.com/static/calendar + +::Project Site:: +http://code.google.com/p/calendardateselect/ + +It works! \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/init.rb b/chapter12/training/vendor/plugins/calendar_date_select/init.rb new file mode 100644 index 0000000..57e1744 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/init.rb @@ -0,0 +1,15 @@ +%w[calendar_date_select includes_helper].each { |file| + require File.join( File.dirname(__FILE__), "lib",file) +} + +ActionView::Helpers::FormHelper.send(:include, CalendarDateSelect::FormHelper) +ActionView::Base.send(:include, CalendarDateSelect::FormHelper) +ActionView::Base.send(:include, CalendarDateSelect::IncludesHelper) + +# install files +['/public/javascripts/calendar_date_select', '/public/stylesheets/calendar_date_select', '/public/images/calendar_date_select'].each{|dir| + source = File.join(directory,dir) + dest = RAILS_ROOT + dir + FileUtils.mkdir_p(dest) + FileUtils.cp_r(Dir.glob(source+'/*.*'), dest) +} unless File.exists?(RAILS_ROOT + '/public/javascripts/calendar_date_select/calendar_date_select.js') diff --git a/chapter12/training/vendor/plugins/calendar_date_select/js_test/functional/cds_test.html b/chapter12/training/vendor/plugins/calendar_date_select/js_test/functional/cds_test.html new file mode 100644 index 0000000..45e45d3 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/js_test/functional/cds_test.html @@ -0,0 +1,166 @@ + + + + + Calendar Date Select Test Cases + + + + + + + + + + + +
      + + + + + + + + \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/js_test/prototype.js b/chapter12/training/vendor/plugins/calendar_date_select/js_test/prototype.js new file mode 100644 index 0000000..a3f21ac --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/js_test/prototype.js @@ -0,0 +1,3277 @@ +/* Prototype JavaScript framework, version 1.5.1.1 + * (c) 2005-2007 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/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.1.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 + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + (document.createElement('div').__proto__ !== + document.createElement('form').__proto__) + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } 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(); + } + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (object.ownerDocument === document) return; + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (value !== undefined) + results.push(property.toJSON() + ': ' + value); + } + return '{' + results.join(', ') + '}'; + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } +} + +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; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +Date.prototype.toJSON = function() { + return '"' + this.getFullYear() + '-' + + (this.getMonth() + 1).toPaddedString(2) + '-' + + this.getDate().toPaddedString(2) + 'T' + + this.getHours().toPaddedString(2) + ':' + + this.getMinutes().toPaddedString(2) + ':' + + this.getSeconds().toPaddedString(2) + '"'; +}; + +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; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('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) { + 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]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (hash[key].constructor != Array) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + var result = ''; + for (var i = 0; i < count; i++) result += this; + return result; + }, + + 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; + }, + + capitalize: function() { + 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(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + 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); + }); + 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}'); + }, + + isJSON: function() { + var str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + } +}); + +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,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +with (String.prototype.escapeHTML) div.appendChild(text); + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = {}, $continue = new Error('"throw $continue" is deprecated, use "return" instead'); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + iterator(value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +if (Prototype.Browser.WebKit) { + $A = Array.from = function(iterable) { + if (!iterable) return []; + if (!(typeof iterable == 'function' && iterable == '[object NodeList]') && + iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (value !== undefined) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (arguments[i].constructor == Array) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(object) { + if (object instanceof Hash) this.merge(object); + else Object.extend(this, object || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + parts.add = arguments.callee.addPair; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + var value = pair.value; + + if (value && typeof value == 'object') { + if (value.constructor == Array) value.each(function(value) { + parts.add(pair.key, value); + }); + return; + } + parts.add(pair.key, value); + }); + + return parts.join('&'); + }, + + toJSON: function(object) { + var results = []; + this.prototype._each.call(object, function(pair) { + var value = Object.toJSON(pair.value); + if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value); + }); + return '{' + results.join(', ') + '}'; + } +}); + +Hash.toQueryString.addPair = function(key, value, prefix) { + key = encodeURIComponent(key); + if (value === undefined) this.push(key); + else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value))); +} + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Hash.toJSON(this); + } +}); + +function $H(object) { + if (object instanceof Hash) return object; + return new Hash(object); +}; + +// Safari iterates over shadowed properties +if (function() { + var i = 0, Test = function(value) { this.key = value }; + Test.prototype.key = 'foo'; + for (var property in new Test('bar')) i++; + return i > 1; +}()) Hash.prototype._each = function(iterator) { + var cache = []; + for (var key in this) { + var value = this[key]; + if ((value && value == Hash.prototype[key]) || cache.include(key)) continue; + cache.push(key); + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + 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'; + } + + this.parameters = params; + + if (params = Hash.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)) + params += '&_='; + } + + try { + if (this.options.onCreate) this.options.onCreate(this.transport); + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + var contentType = this.getHeader('Content-type'); + if (contentType && contentType.strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? json.evalJSON() : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + 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++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; + + document.getElementsByClassName = function(className, parentElement) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } + +} else document.getElementsByClassName = function(className, parentElement) { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child, pattern = new RegExp("(^|\\s)" + className + "(\\s|$)"); + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + var elementClassName = child.className; + if (elementClassName.length == 0) continue; + if (elementClassName == className || elementClassName.match(pattern)) + elements.push(Element.extend(child)); + } + return elements; +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) var Element = {}; + +Element.extend = function(element) { + var F = Prototype.BrowserFeatures; + if (!element || !element.tagName || element.nodeType == 3 || + element._extended || F.SpecificElementExtensions || element == window) + return element; + + var methods = {}, tagName = element.tagName, cache = Element.extend.cache, + T = Element.Methods.ByTag; + + // extend methods for all tags (Safari doesn't need this) + if (!F.ElementExtensions) { + Object.extend(methods, Element.Methods), + Object.extend(methods, Element.Methods.Simulated); + } + + // extend methods for specific tags + if (T[tagName]) Object.extend(methods, T[tagName]); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + + element._extended = Prototype.emptyFunction; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + 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(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')).each(Element.extend); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(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]; + }, + + 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]; + }, + + 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]; + }, + + 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]; + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + if (!element.attributes) return null; + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + return attribute ? attribute.nodeValue : null; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles, camelized) { + element = $(element); + var elementStyle = element.style; + + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]) + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (elementStyle.styleFloat === undefined ? 'cssFloat' : 'styleFloat') : + (camelized ? property : property.camelize())] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + 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) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, { + childOf: Element.Methods.descendantOf, + childElements: Element.Methods.immediateDescendants +}); + +if (Prototype.Browser.Opera) { + Element.Methods._getStyle = Element.Methods.getStyle; + Element.Methods.getStyle = function(element, style) { + switch(style) { + case 'left': + case 'top': + case 'right': + case 'bottom': + if (Element._getStyle(element, 'position') == 'static') return null; + default: return Element._getStyle(element, style); + } + }; +} +else if (Prototype.Browser.IE) { + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset'+style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + element = $(element); + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + style.filter = filter.replace(/alpha\([^\)]*\)/gi,''); + return element; + } else if (value < 0.00001) value = 0; + style.filter = filter.replace(/alpha\([^\)]*\)/gi, '') + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + // IE is missing .innerHTML support for TABLE-related elements + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
      '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
      '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
      ' + html.stripScripts() + '
      '; + depth = 4; + } + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + depth.times(function() { div = div.firstChild }); + $A(div.childNodes).each(function(node) { element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() { html.evalScripts() }, 10); + return element; + } +} +else if (Prototype.Browser.Gecko) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +Element._attributeTranslations = { + names: { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } + } +}; + +(function() { + Object.extend(this, { + href: this._getAttr, + src: this._getAttr, + type: this._getAttr, + disabled: this._flag, + checked: this._flag, + readonly: this._flag, + multiple: this._flag + }); +}).call(Element._attributeTranslations.values); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations, node; + attribute = t.names[attribute] || attribute; + node = $(element).getAttributeNode(attribute); + return node && node.specified; + } +}; + +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; +} + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || {}); + else { + if (tagName.constructor == Array) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = {}; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = {}; + window[klass].prototype = document.createElement(tagName).__proto__; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (typeof klass == "undefined") continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; +}; + +var Toggle = { display: Element.toggle }; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
      '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +/* 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(); + +Selector.prototype = { + initialize: function(expression) { + this.expression = expression.strip(); + this.compileMatcher(); + }, + + compileMatcher: function() { + // Selectors with namespaced attributes can't use the XPath version + if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) + return this.compileXPathMatcher(); + + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; return; + } + 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(typeof c[i] == 'function' ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + 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(typeof x[i] == 'function' ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + if (this.xpath) return document._getElementsByXPath(this.xpath, root); + return this.matcher(root); + }, + + match: function(element) { + return this.findElements(document).include(element); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}; + +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: "[@#{1}]", + attr: function(m) { + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (typeof h === 'function') return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + 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}-')]" + }, + 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, m, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = typeof x[i] == 'function' ? 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 ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + 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 }); + } + } + } + }, + + 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|([^'"][^\]]*?)))?\]/ + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._counted = undefined; + return nodes; + }, + + // 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--) { + 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++; + } + }, + + // 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); + }, + + // 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, children = [], child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + 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; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // 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); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!nodes && root == document) return targetNode ? [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); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + 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); + } + return results; + }, + + attrPresence: function(nodes, root, attr) { + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + 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; + }, + + 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); + } + }, + + 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); + }, + + // 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; + }); + }, + + // 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; + }, + + '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; + }, + + '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; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled) results.push(node); + return results; + }, + + '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; + } + }, + + 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; + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + 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 $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != null) { + if (key in result) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + 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; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || {}); + + var params = options.parameters; + options.parameters = form.serialize(true); + + if (params) { + if (typeof params == 'string') params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(form.readAttribute('action'), options); + } +} + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) {} + return element; + }, + + disable: function(element) { + element = $(element); + element.blur(); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +} + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +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, + + element: function(event) { + return $(event.target || event.srcElement); + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (Prototype.Browser.WebKit || element.attachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (Prototype.Browser.IE) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (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; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + 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(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(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) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (Prototype.Browser.WebKit) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/js_test/test.css b/chapter12/training/vendor/plugins/calendar_date_select/js_test/test.css new file mode 100644 index 0000000..8ba4fed --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/js_test/test.css @@ -0,0 +1,40 @@ +body, div, p, h1, h2, h3, ul, ol, span, a, table, td, form, img, li { + font-family: sans-serif; +} + +body { + font-size:0.8em; +} + +#log { + padding-bottom: 1em; + border-bottom: 2px solid #000; + margin-bottom: 2em; +} + +#logsummary { + margin-bottom: 1em; + padding: 1ex; + border: 1px solid #000; + font-weight: bold; +} + +#logtable { + width:100%; + border-collapse: collapse; + border: 1px dotted #666; +} + +#logtable td, #logtable th { + text-align: left; + padding: 3px 8px; + border: 1px dotted #666; +} + +#logtable .passed { + background-color: #cfc; +} + +#logtable .failed, #logtable .error { + background-color: #fcc; +} \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/js_test/unit/cds_helper_methods.html b/chapter12/training/vendor/plugins/calendar_date_select/js_test/unit/cds_helper_methods.html new file mode 100644 index 0000000..adc4581 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/js_test/unit/cds_helper_methods.html @@ -0,0 +1,41 @@ + + + + + Calendar Date Select Test Cases + + + + + + + + + + + +
      + + + + + + + + \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/js_test/unittest.js b/chapter12/training/vendor/plugins/calendar_date_select/js_test/unittest.js new file mode 100644 index 0000000..5d46547 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/js_test/unittest.js @@ -0,0 +1,564 @@ +// script.aculo.us unittest.js v1.7.0, Fri Jan 19 19:16:36 CET 2007 + +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Jon Tirsen (http://www.tirsen.com) +// (c) 2005, 2006 Michael Schuerig (http://www.schuerig.de/michael/) +// +// 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/ + +// experimental, Firefox-only +Event.simulateMouse = function(element, eventName) { + var options = Object.extend({ + pointerX: 0, + pointerY: 0, + buttons: 0, + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false + }, arguments[2] || {}); + var oEvent = document.createEvent("MouseEvents"); + oEvent.initMouseEvent(eventName, true, true, document.defaultView, + options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, 0, $(element)); + + if(this.mark) Element.remove(this.mark); + this.mark = document.createElement('div'); + this.mark.appendChild(document.createTextNode(" ")); + document.body.appendChild(this.mark); + this.mark.style.position = 'absolute'; + this.mark.style.top = options.pointerY + "px"; + this.mark.style.left = options.pointerX + "px"; + this.mark.style.width = "5px"; + this.mark.style.height = "5px;"; + this.mark.style.borderTop = "1px solid red;" + this.mark.style.borderLeft = "1px solid red;" + + if(this.step) + alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options)); + + $(element).dispatchEvent(oEvent); +}; + +// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2. +// You need to downgrade to 1.0.4 for now to get this working +// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much +Event.simulateKey = function(element, eventName) { + var options = Object.extend({ + ctrlKey: false, + altKey: false, + shiftKey: false, + metaKey: false, + keyCode: 0, + charCode: 0 + }, arguments[2] || {}); + + var oEvent = document.createEvent("KeyEvents"); + oEvent.initKeyEvent(eventName, true, true, window, + options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, + options.keyCode, options.charCode ); + $(element).dispatchEvent(oEvent); +}; + +Event.simulateKeys = function(element, command) { + for(var i=0; i' + + '' + + '' + + '' + + '
      StatusTestMessage
      '; + this.logsummary = $('logsummary') + this.loglines = $('loglines'); + }, + _toHTML: function(txt) { + return txt.escapeHTML().replace(/\n/g,"
      "); + }, + addLinksToResults: function(){ + $$("tr.failed .nameCell").each( function(td){ // todo: limit to children of this.log + td.title = "Run only this test" + Event.observe(td, 'click', function(){ window.location.search = "?tests=" + td.innerHTML;}); + }); + $$("tr.passed .nameCell").each( function(td){ // todo: limit to children of this.log + td.title = "Run all tests" + Event.observe(td, 'click', function(){ window.location.search = "";}); + }); + } +} + +Test.Unit.Runner = Class.create(); +Test.Unit.Runner.prototype = { + initialize: function(testcases) { + this.options = Object.extend({ + testLog: 'testlog' + }, arguments[1] || {}); + this.options.resultsURL = this.parseResultsURLQueryParameter(); + this.options.tests = this.parseTestsQueryParameter(); + if (this.options.testLog) { + this.options.testLog = $(this.options.testLog) || null; + } + if(this.options.tests) { + this.tests = []; + for(var i = 0; i < this.options.tests.length; i++) { + if(/^test/.test(this.options.tests[i])) { + this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"])); + } + } + } else { + if (this.options.test) { + this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])]; + } else { + this.tests = []; + for(var testcase in testcases) { + if(/^test/.test(testcase)) { + this.tests.push( + new Test.Unit.Testcase( + this.options.context ? ' -> ' + this.options.titles[testcase] : testcase, + testcases[testcase], testcases["setup"], testcases["teardown"] + )); + } + } + } + } + this.currentTest = 0; + this.logger = new Test.Unit.Logger(this.options.testLog); + setTimeout(this.runTests.bind(this), 1000); + }, + parseResultsURLQueryParameter: function() { + return window.location.search.parseQuery()["resultsURL"]; + }, + parseTestsQueryParameter: function(){ + if (window.location.search.parseQuery()["tests"]){ + return window.location.search.parseQuery()["tests"].split(','); + }; + }, + // Returns: + // "ERROR" if there was an error, + // "FAILURE" if there was a failure, or + // "SUCCESS" if there was neither + getResult: function() { + var hasFailure = false; + for(var i=0;i 0) { + return "ERROR"; + } + if (this.tests[i].failures > 0) { + hasFailure = true; + } + } + if (hasFailure) { + return "FAILURE"; + } else { + return "SUCCESS"; + } + }, + postResults: function() { + if (this.options.resultsURL) { + new Ajax.Request(this.options.resultsURL, + { method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false }); + } + }, + runTests: function() { + var test = this.tests[this.currentTest]; + if (!test) { + // finished! + this.postResults(); + this.logger.summary(this.summary()); + return; + } + if(!test.isWaiting) { + this.logger.start(test.name); + } + test.run(); + if(test.isWaiting) { + this.logger.message("Waiting for " + test.timeToWait + "ms"); + setTimeout(this.runTests.bind(this), test.timeToWait || 1000); + } else { + this.logger.finish(test.status(), test.summary()); + this.currentTest++; + // tail recursive, hopefully the browser will skip the stackframe + this.runTests(); + } + }, + summary: function() { + var assertions = 0; + var failures = 0; + var errors = 0; + var messages = []; + for(var i=0;i 0) return 'failed'; + if (this.errors > 0) return 'error'; + return 'passed'; + }, + assert: function(expression) { + var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"'; + try { expression ? this.pass() : + this.fail(message); } + catch(e) { this.error(e); } + }, + assertEqual: function(expected, actual, message) { + var message = arguments[2] || "assertEqual"; + try { (expected == actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"' + (message||"")); } + catch(e) { this.error(e); } + }, + assertInspect: function(expected, actual) { + var message = arguments[2] || "assertInspect"; + try { (expected == actual.inspect()) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertEnumEqual: function(expected, actual) { + var message = arguments[2] || "assertEnumEqual"; + try { $A(expected).length == $A(actual).length && + expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? + this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + + ', actual ' + Test.Unit.inspect(actual)); } + catch(e) { this.error(e); } + }, + assertNotEqual: function(expected, actual) { + var message = arguments[2] || "assertNotEqual"; + try { (expected != actual) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertIdentical: function(expected, actual) { + var message = arguments[2] || "assertIdentical"; + try { (expected === actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNotIdentical: function(expected, actual) { + var message = arguments[2] || "assertNotIdentical"; + try { !(expected === actual) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertNull: function(obj) { + var message = arguments[1] || 'assertNull' + try { (obj==null) ? this.pass() : + this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); } + catch(e) { this.error(e); } + }, + assertMatch: function(expected, actual) { + var message = arguments[2] || 'assertMatch'; + var regex = new RegExp(expected); + try { (regex.exec(actual)) ? this.pass() : + this.fail(message + ' : regex: "' + Test.Unit.inspect(expected) + ' did not match: ' + Test.Unit.inspect(actual) + '"'); } + catch(e) { this.error(e); } + }, + assertHidden: function(element) { + var message = arguments[1] || 'assertHidden'; + this.assertEqual("none", element.style.display, message); + }, + assertNotNull: function(object) { + var message = arguments[1] || 'assertNotNull'; + this.assert(object != null, message); + }, + assertType: function(expected, actual) { + var message = arguments[2] || 'assertType'; + try { + (actual.constructor == expected) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + (actual.constructor) + '"'); } + catch(e) { this.error(e); } + }, + assertNotOfType: function(expected, actual) { + var message = arguments[2] || 'assertNotOfType'; + try { + (actual.constructor != expected) ? this.pass() : + this.fail(message + ': expected "' + Test.Unit.inspect(expected) + + '", actual "' + (actual.constructor) + '"'); } + catch(e) { this.error(e); } + }, + assertInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertInstanceOf'; + try { + (actual instanceof expected) ? this.pass() : + this.fail(message + ": object was not an instance of the expected type"); } + catch(e) { this.error(e); } + }, + assertNotInstanceOf: function(expected, actual) { + var message = arguments[2] || 'assertNotInstanceOf'; + try { + !(actual instanceof expected) ? this.pass() : + this.fail(message + ": object was an instance of the not expected type"); } + catch(e) { this.error(e); } + }, + assertRespondsTo: function(method, obj) { + var message = arguments[2] || 'assertRespondsTo'; + try { + (obj[method] && typeof obj[method] == 'function') ? this.pass() : + this.fail(message + ": object doesn't respond to [" + method + "]"); } + catch(e) { this.error(e); } + }, + assertReturnsTrue: function(method, obj) { + var message = arguments[2] || 'assertReturnsTrue'; + try { + var m = obj[method]; + if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; + m() ? this.pass() : + this.fail(message + ": method returned false"); } + catch(e) { this.error(e); } + }, + assertReturnsFalse: function(method, obj) { + var message = arguments[2] || 'assertReturnsFalse'; + try { + var m = obj[method]; + if(!m) m = obj['is'+method.charAt(0).toUpperCase()+method.slice(1)]; + !m() ? this.pass() : + this.fail(message + ": method returned true"); } + catch(e) { this.error(e); } + }, + assertRaise: function(exceptionName, method) { + var message = arguments[2] || 'assertRaise'; + try { + method(); + this.fail(message + ": exception expected but none was raised"); } + catch(e) { + ((exceptionName == null) || (e.name==exceptionName)) ? this.pass() : this.error(e); + } + }, + assertElementsMatch: function() { + var expressions = $A(arguments), elements = $A(expressions.shift()); + if (elements.length != expressions.length) { + this.fail('assertElementsMatch: size mismatch: ' + elements.length + ' elements, ' + expressions.length + ' expressions'); + return false; + } + elements.zip(expressions).all(function(pair, index) { + var element = $(pair.first()), expression = pair.last(); + if (element.match(expression)) return true; + this.fail('assertElementsMatch: (in index ' + index + ') expected ' + expression.inspect() + ' but got ' + element.inspect()); + }.bind(this)) && this.pass(); + }, + assertElementMatches: function(element, expression) { + this.assertElementsMatch([element], expression); + }, + benchmark: function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; + }, + _isVisible: function(element) { + element = $(element); + if(!element.parentNode) return true; + this.assertNotNull(element); + if(element.style && Element.getStyle(element, 'display') == 'none') + return false; + + return this._isVisible(element.parentNode); + }, + assertNotVisible: function(element) { + this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1])); + }, + assertVisible: function(element) { + this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1])); + }, + benchmark: function(operation, iterations) { + var startAt = new Date(); + (iterations || 1).times(operation); + var timeTaken = ((new Date())-startAt); + this.info((arguments[2] || 'Operation') + ' finished ' + + iterations + ' iterations in ' + (timeTaken/1000)+'s' ); + return timeTaken; + } +} + +Test.Unit.Testcase = Class.create(); +Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), { + initialize: function(name, test, setup, teardown) { + Test.Unit.Assertions.prototype.initialize.bind(this)(); + this.name = name; + + if(typeof test == 'string') { + test = test.gsub(/(\.should[^\(]+\()/,'#{0}this,'); + test = test.gsub(/(\.should[^\(]+)\(this,\)/,'#{1}(this)'); + this.test = function() { + eval('with(this){'+test+'}'); + } + } else { + this.test = test || function() {}; + } + + this.setup = setup || function() {}; + this.teardown = teardown || function() {}; + this.isWaiting = false; + this.timeToWait = 1000; + }, + wait: function(time, nextPart) { + this.isWaiting = true; + this.test = nextPart; + this.timeToWait = time; + }, + run: function() { + try { + try { + if (!this.isWaiting) this.setup.bind(this)(); + this.isWaiting = false; + this.test.bind(this)(); + } finally { + if(!this.isWaiting) { + this.teardown.bind(this)(); + } + } + } + catch(e) { this.error(e); } + } +}); + +// *EXPERIMENTAL* BDD-style testing to please non-technical folk +// This draws many ideas from RSpec http://rspec.rubyforge.org/ + +Test.setupBDDExtensionMethods = function(){ + var METHODMAP = { + shouldEqual: 'assertEqual', + shouldNotEqual: 'assertNotEqual', + shouldEqualEnum: 'assertEnumEqual', + shouldBeA: 'assertType', + shouldNotBeA: 'assertNotOfType', + shouldBeAn: 'assertType', + shouldNotBeAn: 'assertNotOfType', + shouldBeNull: 'assertNull', + shouldNotBeNull: 'assertNotNull', + + shouldBe: 'assertReturnsTrue', + shouldNotBe: 'assertReturnsFalse', + shouldRespondTo: 'assertRespondsTo' + }; + Test.BDDMethods = {}; + for(m in METHODMAP) { + Test.BDDMethods[m] = eval( + 'function(){'+ + 'var args = $A(arguments);'+ + 'var scope = args.shift();'+ + 'scope.'+METHODMAP[m]+'.apply(scope,(args || []).concat([this])); }'); + } + [Array.prototype, String.prototype, Number.prototype].each( + function(p){ Object.extend(p, Test.BDDMethods) } + ); +} + +Test.context = function(name, spec, log){ + Test.setupBDDExtensionMethods(); + + var compiledSpec = {}; + var titles = {}; + for(specName in spec) { + switch(specName){ + case "setup": + case "teardown": + compiledSpec[specName] = spec[specName]; + break; + default: + var testName = 'test'+specName.gsub(/\s+/,'-').camelize(); + var body = spec[specName].toString().split('\n').slice(1); + if(/^\{/.test(body[0])) body = body.slice(1); + body.pop(); + body = body.map(function(statement){ + return statement.strip() + }); + compiledSpec[testName] = body.join('\n'); + titles[testName] = specName; + } + } + new Test.Unit.Runner(compiledSpec, { titles: titles, testLog: log || 'testlog', context: name }); +}; \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/lib/calendar_date_select.rb b/chapter12/training/vendor/plugins/calendar_date_select/lib/calendar_date_select.rb new file mode 100644 index 0000000..50dd6ea --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/lib/calendar_date_select.rb @@ -0,0 +1,159 @@ +class CalendarDateSelect + FORMATS = { + :natural => { + :date => "%B %d, %Y", + :time => " %I:%M %p" + }, + :hyphen_ampm => { + :date => "%Y-%m-%d", + :time => " %I:%M %p", + :javascript_include => "format_hyphen_ampm" + }, + :finnish => { + :date => "%d.%m.%Y", + :time => " %H:%M", + :javascript_include => "format_finnish" + } + } + + cattr_accessor :image + @@image = "calendar_date_select/calendar.gif" + + cattr_reader :format + @@format = FORMATS[:natural] + + class << self + def format=(format) + raise "CalendarDateSelect: Unrecognized format specification: #{format}" unless FORMATS.has_key?(format) + @@format = FORMATS[format] + end + + def javascript_format_include + @@format[:javascript_include] && "calendar_date_select/#{@@format[:javascript_include]}" + end + + def date_format_string(time=false) + @@format[:date] + ( time ? @@format[:time] : "" ) + end + + def format_date(date) + if Date===date + date.strftime(date_format_string(false)) + else + date.strftime(date_format_string(true)) + end + end + + def has_time?(value) + /[0-9]:[0-9]{2}/.match(value.to_s) + end + end + + module FormHelper + def calendar_date_select_tag( name, value = nil, options = {}) + calendar_options = calendar_date_select_process_options(options) + value = (value.strftime(calendar_options[:format]) rescue value) if (value.respond_to?("strftime")) + + calendar_options.delete(:format) + + options[:id] ||= name + tag = calendar_options[:hidden] || calendar_options[:embedded] ? + hidden_field_tag(name, value, options) : + text_field_tag(name, value, options) + + calendar_date_select_output(tag, calendar_options) + end + + # extracts any options passed into calendar date select, appropriating them to either the Javascript call or the html tag. + def calendar_date_select_process_options(options) + calendar_options = {} + callbacks = [:before_show, :before_close, :after_show, :after_close, :after_navigate] + for key in [:time, :embedded, :buttons, :format, :year_range, :month_year, :popup, :hidden] + callbacks + calendar_options[key] = options.delete(key) if options.has_key?(key) + end + + # if passing in mixed, pad it with single quotes + calendar_options[:time] = "'mixed'" if calendar_options[:time].to_s=="mixed" + calendar_options[:month_year] = "'#{calendar_options[:month_year]}'" if calendar_options[:month_year] + + # if we are forcing the popup, automatically set the readonly property on the input control. + if calendar_options[:popup].to_s == "force" + calendar_options[:popup] = "'force'" + options[:readonly] = true + end + + calendar_options[:popup_by] ||= "this" if calendar_options[:hidden] + + # surround any callbacks with a function, if not already done so + for key in callbacks + calendar_options[key] = "function(param) { #{calendar_options[key]} }" unless calendar_options[key].include?("function") if calendar_options[key] + end + + calendar_options[:year_range] ||= 10 + calendar_options + end + + def calendar_date_select(object, method, options={}) + obj = instance_eval("@#{object}") || options[:object] + + if !options.include?(:time) && obj.class.respond_to?("columns_hash") + column_type = (obj.class.columns_hash[method.to_s].type rescue nil) + options[:time] = true if column_type == :datetime + end + + use_time = options[:time] + + if options[:time].to_s=="mixed" + use_time = false if Date===obj.send(method) + end + + calendar_options = calendar_date_select_process_options(options) + + options[:value] ||= if obj.respond_to?(method) && obj.send(method).respond_to?(:strftime) + obj.send(method).strftime(CalendarDateSelect.date_format_string(use_time)) + elsif obj.respond_to?("#{method}_before_type_cast") + obj.send("#{method}_before_type_cast") + elsif obj.respond_to?(method) + obj.send(method).to_s + else + nil + end + + tag = ActionView::Helpers::InstanceTag.new(object, method, self, nil, options.delete(:object)) + calendar_date_select_output( + tag.to_input_field_tag( (calendar_options[:hidden] || calendar_options[:embedded]) ? "hidden" : "text", options), + calendar_options + ) + end + + def calendar_date_select_output(input, calendar_options = {}) + out = input + if calendar_options[:embedded] + uniq_id = "cds_placeholder_#{(rand*100000).to_i}" + # we need to be able to locate the target input element, so lets stick an invisible span tag here we can easily locate + out << content_tag(:span, nil, :style => "display: none; position: absolute;", :id => uniq_id) + + out << javascript_tag("new CalendarDateSelect( $('#{uniq_id}').previous(), #{options_for_javascript(calendar_options)} ); ") + else + out << " " + + out << image_tag(CalendarDateSelect.image, + :onclick => "new CalendarDateSelect( $(this).previous(), #{options_for_javascript(calendar_options)} );", + :style => 'border:0px; cursor:pointer;') + end + + out + end + end +end + + +module ActionView + module Helpers + class FormBuilder + def calendar_date_select(method, options = {}) + @template.calendar_date_select(@object_name, method, options.merge(:object => @object)) + end + end + end +end diff --git a/chapter12/training/vendor/plugins/calendar_date_select/lib/includes_helper.rb b/chapter12/training/vendor/plugins/calendar_date_select/lib/includes_helper.rb new file mode 100644 index 0000000..768a727 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/lib/includes_helper.rb @@ -0,0 +1,19 @@ +class CalendarDateSelect + module IncludesHelper + def calendar_date_select_includes(*args) + options = (Hash === args.last) ? args.pop : {} + options.assert_valid_keys(:style, :format, :locale) + + style = options[:style] || args.shift + locale = options[:locale] + cds_css_file = style ? "calendar_date_select/#{style}" : "calendar_date_select/default" + + output = [] + output << javascript_include_tag("calendar_date_select/calendar_date_select") + output << javascript_include_tag("calendar_date_select/locale/#{locale}") if locale + output << javascript_include_tag(CalendarDateSelect.javascript_format_include) if CalendarDateSelect.javascript_format_include + output << stylesheet_link_tag(cds_css_file) + output * "\n" + end + end +end diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/images/calendar_date_select/calendar.gif b/chapter12/training/vendor/plugins/calendar_date_select/public/images/calendar_date_select/calendar.gif new file mode 100644 index 0000000..6b7b7ca Binary files /dev/null and b/chapter12/training/vendor/plugins/calendar_date_select/public/images/calendar_date_select/calendar.gif differ diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/calendar_date_select.js b/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/calendar_date_select.js new file mode 100644 index 0000000..e5c87fc --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/calendar_date_select.js @@ -0,0 +1,399 @@ +// CalendarDateSelect version 1.8.1 - a small prototype based date picker +// Questions, comments, bugs? - email the Author - Tim Harper <"timseeharper@gmail.seeom".gsub("see", "c")> +if (typeof Prototype == 'undefined') + alert("CalendarDateSelect Error: Prototype could not be found. Please make sure that your application's layout includes prototype.js (e.g. <%= javascript_include_tag :defaults %>) *before* it includes calendar_date_select.js (e.g. <%= calendar_date_select_includes %>)."); + +Element.addMethods({ + purgeChildren: function(element) { $A(element.childNodes).each(function(e){$(e).remove();}); }, + build: function(element, type, options, style) { + newElement = Element.build(type, options, style); + element.appendChild(newElement); + return newElement; + } +}); + +Element.build = function(type, options, style) +{ + e = $(document.createElement(type)); + $H(options).each(function(pair) { eval("e." + pair.key + " = pair.value" ); }); + if (style) + $H(style).each(function(pair) { eval("e.style." + pair.key + " = pair.value" ); }); + return e; +}; +nil=null; + +Date.one_day = 24*60*60*1000; +Date.weekdays = $w("S M T W T F S"); +Date.first_day_of_week = 0; +Date.months = $w("January February March April May June July August September October November December" ); +Date.padded2 = function(hour) { padded2 = hour.toString(); if (parseInt(hour) < 10) padded2="0" + padded2; return padded2; } +Date.prototype.getPaddedMinutes = function() { return Date.padded2(this.getMinutes()); } +Date.prototype.getAMPMHour = function() { hour=this.getHours(); return (hour == 0) ? 12 : (hour > 12 ? hour - 12 : hour ) } +Date.prototype.getAMPM = function() { return (this.getHours() < 12) ? "AM" : "PM"; } +Date.prototype.stripTime = function() { return new Date(this.getFullYear(), this.getMonth(), this.getDate());}; +Date.prototype.daysDistance = function(compare_date) { return Math.round((compare_date - this) / Date.one_day); }; +Date.prototype.toFormattedString = function(include_time){ + str = Date.months[this.getMonth()] + " " + this.getDate() + ", " + this.getFullYear(); + + if (include_time) { hour=this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM() } + return str; +} +Date.parseFormattedString = function(string) { return new Date(string);} +Math.floor_to_interval = function(n, i) { return Math.floor(n/i) * i;} +window.f_height = function() { return( [window.innerHeight ? window.innerHeight : null, document.documentElement ? document.documentElement.clientHeight : null, document.body ? document.body.clientHeight : null].select(function(x){return x>0}).first()||0); } +window.f_scrollTop = function() { return ([window.pageYOffset ? window.pageYOffset : null, document.documentElement ? document.documentElement.scrollTop : null, document.body ? document.body.scrollTop : null].select(function(x){return x>0}).first()||0 ); } + +_translations = { + "OK": "OK", + "Now": "Now", + "Today": "Today" +} +SelectBox = Class.create(); +SelectBox.prototype = { + initialize: function(parent_element, values, html_options, style_options) { + this.element = $(parent_element).build("select", html_options, style_options); + this.populate(values); + }, + populate: function(values) { + this.element.purgeChildren(); + that=this; $A(values).each(function(pair) { if (typeof(pair)!="object") {pair = [pair, pair]}; that.element.build("option", { value: pair[1], innerHTML: pair[0]}) }); + }, + setValue: function(value, callback) { + e = this.element; + matched=false; + $R(0, e.options.length - 1 ).each(function(i) { if(e.options[i].value==value.toString()) {e.selectedIndex = i; matched=true;}; } ); + return matched; + }, + getValue: function() { return $F(this.element)} +} +CalendarDateSelect = Class.create(); +CalendarDateSelect.prototype = { + initialize: function(target_element, options) { + this.target_element = $(target_element); // make sure it's an element, not a string + this.target_element.calendar_date_select = this; + // initialize the date control + this.options = $H({ + embedded: false, + popup: nil, + time: false, + buttons: true, + year_range: 10, + calendar_div: nil, + close_on_click: nil, + minute_interval: 5, + popup_by: target_element, + month_year: "dropdowns", + onchange: this.target_element.onchange + }).merge(options || {}); + + this.selection_made = $F(this.target_element).strip()!==""; + this.use_time = this.options.time; + + this.callback("before_show") + this.calendar_div = $(this.options.calendar_div); + if (!this.target_element) { alert("Target element " + target_element + " not found!"); return false;} + + this.parseDate(); + + // by default, stick it by the target element (if embedded, that's where we'll want it to show up) + if (this.calendar_div == nil) { this.calendar_div = $( this.options.embedded ? this.target_element.parentNode : document.body ).build('div'); } + if (!this.options.embedded) { + this.calendar_div.setStyle( { position:"absolute", visibility: "hidden" } ) + this.positionCalendarDiv(); + } + + this.calendar_div.addClassName("calendar_date_select"); + + if (this.options["embedded"]) this.options["close_on_click"]=false; + // logic for close on click + if (this.options['close_on_click']===nil ) + { + if (this.options['time']) + this.options["close_on_click"] = false; + else + this.options['close_on_click'] = true; + } + + // set the click handler to check if a user has clicked away from the document + if(!this.options["embedded"]) Event.observe(document.body, "mousedown", this.bodyClick_handler=this.bodyClick.bindAsEventListener(this)); + + this.initFrame(); + if(!this.options["embedded"]) { this.positionCalendarDiv(true) }//.bindAsEventListener(this), 1); + this.callback("after_show") + }, + positionCalendarDiv: function(post_painted) { + above=false; + c_pos = Position.cumulativeOffset(this.calendar_div); c_left = c_pos[0]; c_top = c_pos[1]; c_dim = this.calendar_div.getDimensions(); c_height = c_dim.height; c_width = c_dim.width; + w_top = window.f_scrollTop(); w_height = window.f_height(); + e_dim = Position.cumulativeOffset($(this.options.popup_by)); e_top = e_dim[1]; e_left = e_dim[0]; + + if ( (post_painted) && (( c_top + c_height ) > (w_top + w_height)) && ( c_top - c_height > w_top )) above=true; + left_px = e_left.toString() + "px"; + top_px = (above ? (e_top - c_height ) : ( e_top + $(this.options.popup_by).getDimensions().height )).toString() + "px"; + + this.calendar_div.style.left = left_px; this.calendar_div.style.top = top_px; + + // draw an iframe behind the calendar -- ugly hack to make IE 6 happy + if (post_painted) + { + this.iframe = $(document.body).build("iframe", {}, { position:"absolute", left: left_px, top: top_px, height: c_height.toString()+"px", width: c_width.toString()+"px", border: "0px"}) + this.calendar_div.setStyle({visibility:""}); + } + }, + initFrame: function() { + that=this; + // create the divs + $w("top header body buttons footer bottom").each(function(name) { + eval(name + "_div = that." + name + "_div = that.calendar_div.build('div', { className: 'cds_"+name+"' }, { clear: 'left'} ); "); + }); + + this.initButtonsDiv(); + this.updateFooter(" "); + // make the header buttons + this.prev_month_button = header_div.build("a", { innerHTML: "<", href:"#", onclick:function(){return false;}, className: "prev" }); + this.next_month_button = header_div.build("a", { innerHTML: ">", href:"#", onclick:function(){return false;}, className: "next" }); + Event.observe(this.prev_month_button, 'mousedown', function () { this.navMonth(this.date.getMonth() - 1 ) }.bindAsEventListener(this)); + Event.observe(this.next_month_button, 'mousedown', function () { this.navMonth(this.date.getMonth() + 1 ) }.bindAsEventListener(this)); + + if (this.options.month_year=="dropdowns") { + this.month_select = new SelectBox(header_div, $R(0,11).map(function(m){return [Date.months[m], m]}), {className: "month", onchange: function () { this.navMonth(this.month_select.getValue()) }.bindAsEventListener(this)}); + this.year_select = new SelectBox(header_div, [], {className: "year", onchange: function () { this.navYear(this.year_select.getValue()) }.bindAsEventListener(this)}); + } else { + this.month_year_label = header_div.build("span") + } + + // build the calendar day grid + this.calendar_day_grid = []; + days_table = body_div.build("table", { cellPadding: "0px", cellSpacing: "0px", width: "100%" }) + // make the weekdays! + weekdays_row = days_table.build("thead").build("tr"); + Date.weekdays.each( function(weekday) { + weekdays_row.build("th", {innerHTML: weekday}); + }); + + days_tbody = days_table.build("tbody") + // Make the days! + row_number=0 + for(cell_index=0; cell_index<42; cell_index++) + { + weekday=(cell_index+Date.first_day_of_week ) % 7; + if ( cell_index % 7==0 ) days_row = days_tbody.build("tr", {className: 'row_'+row_number++}); + (this.calendar_day_grid[cell_index] = days_row.build("td", { + calendar_date_select: this, + onmouseover: function () { this.calendar_date_select.dayHover(this); }, + onmouseout: function () { this.calendar_date_select.dayHoverOut(this) }, + onclick: function() { this.calendar_date_select.updateSelectedDate(this); }, + className: (weekday==0) || (weekday==6) ? " weekend" : "" //clear the class + }, + { cursor: "pointer" } + )).build("div"); + this.calendar_day_grid[cell_index]; + } + this.refresh(); + }, + initButtonsDiv: function() + { + buttons_div = this.buttons_div; + // make the time div + if (this.options["time"]) + { + blank_time = $A(this.options.time=="mixed" ? [[" - ", ""]] : []); + buttons_div.build("span", {innerHTML:"@", className: "at_sign"}); + + t=new Date(); + this.hour_select = new SelectBox(buttons_div, + blank_time.concat($R(0,23).map(function(x) {t.setHours(x); return $A([t.getAMPMHour()+ " " + t.getAMPM(),x])} )), + { + calendar_date_select: this, + onchange: function() { this.calendar_date_select.updateSelectedDate( { hour: this.value });}, + className: "hour" + } + ); + buttons_div.build("span", {innerHTML:":", className: "seperator"}); + that=this; + this.minute_select = new SelectBox(buttons_div, + blank_time.concat($R(0,59).select(function(x){return (x % that.options.minute_interval==0)}).map(function(x){ return $A([ Date.padded2(x), x]); } ) ), + { + calendar_date_select: this, + onchange: function() { this.calendar_date_select.updateSelectedDate( {minute: this.value }) }, + className: "minute" + } + ); + } else if (! this.options.buttons) buttons_div.remove(); + + if (this.options.buttons) { + buttons_div.build("span", {innerHTML: " "}); + // Build Today button + if (this.options.time=="mixed" || !this.options.time) b=buttons_div.build("a", { + innerHTML: _translations["Today"], + href: "#", + onclick: function() {this.today(false); return false;}.bindAsEventListener(this) + }); + + if (this.options.time=="mixed") buttons_div.build("span", {innerHTML: " | ", className:"button_seperator"}) + + if (this.options.time) b = buttons_div.build("a", { + innerHTML: _translations["Now"], + href: "#", + onclick: function() {this.today(true); return false}.bindAsEventListener(this) + }); + } + }, + dateString: function() { + return (this.selection_made) ? this.selected_date.toFormattedString(this.use_time) : " "; + }, + navMonth: function(month) { + prev_day = this.date.getDate(); + this.date.setMonth(month); + + this.refresh(); + this.callback("after_navigate", this.date); + }, + navYear: function(year) { + this.date.setYear(year); + this.refresh(); + this.callback("after_navigate", this.date); + }, + refresh: function () + { + m=this.date.getMonth() + y=this.date.getFullYear(); + // set the month + if (this.options.month_year == "dropdowns") + { + this.month_select.setValue(m, false); + + e=this.year_select.element; + if ( !(this.year_select.setValue(y)) || e.selectedIndex <= 1 || e.selectedIndex >= e.options.length - 2 ) { + this.year_select.populate( $R( y - this.options.year_range, y+this.options.year_range ).toArray() ); + this.year_select.setValue(y) + } + } else { + this.month_year_label.update( Date.months[m] + " " + y.toString() ); + } + + // populate the calendar_day_grid + this.beginning_date = new Date(this.date).stripTime(); + this.beginning_date.setDate(1); + pre_days = this.beginning_date.getDay() // draw some days before the fact + if (pre_days < 3) pre_days+=7; + this.beginning_date.setDate(1 - pre_days + Date.first_day_of_week); + + iterator = new Date(this.beginning_date); + + today = new Date().stripTime(); + this_month = this.date.getMonth(); + for (cell_index=0;cell_index<42; cell_index++) + { + day = iterator.getDate(); month = iterator.getMonth(); + cell = this.calendar_day_grid[cell_index]; + Element.remove(cell.childNodes[0]); div = cell.build("div", {innerHTML:day}); + if (month!=this_month) div.className = "other"; + cell.day=day; cell.month = month; cell.year = iterator.getFullYear(); + iterator.setDate( day + 1); + } + + if (this.today_cell) this.today_cell.removeClassName("today"); + + if ( $R( 0, 42 ).include(days_until = this.beginning_date.daysDistance(today)) ) { + this.today_cell = this.calendar_day_grid[days_until]; + this.today_cell.addClassName("today"); + } + + // set the time + this.setUseTime(this.use_time); + + this.setSelectedClass(); + this.updateFooter(); + }, + dayHover: function(element) { + element.addClassName("hover"); + hover_date = new Date(this.selected_date); + hover_date.setYear(element.year); hover_date.setMonth(element.month); hover_date.setDate(element.day); + this.updateFooter(hover_date.toFormattedString(this.use_time)); + }, + dayHoverOut: function(element) { element.removeClassName("hover"); this.updateFooter(); }, + setSelectedClass: function() { + if (!this.selection_made) return; + + // clear selection + if (this.selected_cell) this.selected_cell.removeClassName("selected"); + + if ($R(0,42).include( days_until = this.beginning_date.daysDistance(this.selected_date.stripTime()) )) { + this.selected_cell = this.calendar_day_grid[days_until]; + this.selected_cell.addClassName("selected"); + } + }, + reparse: function() { this.parseDate(); this.refresh(); }, + parseDate: function() + { + value = $F(this.target_element).strip() + this.date = value=="" ? NaN : Date.parseFormattedString(this.options['date'] || value); + if (isNaN(this.date)) this.date = new Date(); + this.selected_date = new Date(this.date); + this.use_time = /[0-9]:[0-9]{2}/.exec(value) ? true : false; + this.date.setDate(1); + }, + updateFooter:function(text) { if (!text) text=this.dateString(); this.footer_div.purgeChildren(); this.footer_div.build("span", {innerHTML: text }); }, + updateSelectedDate:function(parts) { + if ((this.target_element.disabled || this.target_element.readOnly) && this.options.popup!="force") return false; + if (parts.day) { + this.selection_made = true; + for (x=0; x<=1; x++) { + this.selected_date.setDate(parts.day); + this.selected_date.setMonth(parts.month); + this.selected_date.setYear(parts.year);} + } + + if (!isNaN(parts.hour)) this.selected_date.setHours(parts.hour); + if (!isNaN(parts.minute)) this.selected_date.setMinutes( Math.floor_to_interval(parts.minute, this.options.minute_interval) ); + if (parts.hour === "" || parts.minute === "") + this.setUseTime(false); + else if (!isNaN(parts.hour) || !isNaN(parts.minute)) + this.setUseTime(true); + + this.updateFooter(); + this.setSelectedClass(); + + if (this.selection_made) this.updateValue(); + if (this.options.close_on_click) { this.close(); } + }, + setUseTime: function(turn_on) { + this.use_time = this.options.time && (this.options.time=="mixed" ? turn_on : true) // force use_time to true if time==true && time!="mixed" + if (this.use_time && this.selected_date) { // only set hour/minute if a date is already selected + minute = Math.floor_to_interval(this.selected_date.getMinutes(), this.options.minute_interval); + hour = this.selected_date.getHours(); + + this.hour_select.setValue(hour); + this.minute_select.setValue(minute) + } else if (this.options.time=="mixed") { + this.hour_select.setValue(""); this.minute_select.setValue(""); + } + }, + updateValue: function() { + last_value = this.target_element.value; + this.target_element.value = this.dateString(); + if (last_value!=this.target_element.value) this.callback("onchange"); + }, + today: function(now) { + d=new Date(); this.date = new Date(); + o = $H({ day: d.getDate(), month: d.getMonth(), year: d.getFullYear(), hour: d.getHours(), minute: d.getMinutes()}); + if ( ! now ) o = o.merge({hour: "", minute:""}); + this.updateSelectedDate(o); + this.refresh(); + }, + close: function() { + if (this.closed) return false; + this.callback("before_close"); + this.target_element.calendar_date_select = nil; + Event.stopObserving(document.body, "mousedown", this.bodyClick_handler); + this.calendar_div.remove(); this.closed=true; + if (this.iframe) this.iframe.remove(); + this.target_element.focus(); + this.callback("after_close"); + }, + bodyClick: function(e) { // checks to see if somewhere other than calendar date select grid was clicked. in which case, close + if (! $(Event.element(e)).descendantOf(this.calendar_div) ) this.close(); + }, + callback: function(name, param) { if (this.options[name]) { this.options[name].bind(this.target_element)(param); } } +} \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/format_finnish.js b/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/format_finnish.js new file mode 100644 index 0000000..67d453e --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/format_finnish.js @@ -0,0 +1,24 @@ +Date.padded2 = function(hour) { padded2 = hour.toString(); if ((parseInt(hour) < 10) || (parseInt(hour) == null)) padded2="0" + padded2; return padded2; } +Date.prototype.getAMPMHour = function() { hour=Date.padded2(this.getHours()); return (hour == null) ? 00 : (hour > 24 ? hour - 24 : hour ) } +Date.prototype.getAMPM = function() { return (this.getHours() < 12) ? "" : ""; } + +Date.prototype.toFormattedString = function(include_time){ + str = this.getDate() + "." + (this.getMonth() + 1) + "." + this.getFullYear(); + if (include_time) { hour=this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() } + return str; +} +Date.parseFormattedString = function (string) { + var regexp = '([0-9]{1,2})\.(([0-9]{1,2})\.(([0-9]{4})( ([0-9]{1,2}):([0-9]{2})? *)?)?)?'; + var d = string.match(new RegExp(regexp, "i")); + if (d==null) return Date.parse(string); // at least give javascript a crack at it. + var offset = 0; + var date = new Date(d[5], 0, 1); + if (d[3]) { date.setMonth(d[3] - 1); } + if (d[5]) { date.setDate(d[1]); } + if (d[7]) { + date.setHours(parseInt(d[7])); + } + if (d[8]) { date.setMinutes(d[8]); } + if (d[10]) { date.setSeconds(d[10]); } + return date; +} diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/format_hyphen_ampm.js b/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/format_hyphen_ampm.js new file mode 100644 index 0000000..0a3d7ee --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/format_hyphen_ampm.js @@ -0,0 +1,36 @@ +Date.prototype.toFormattedString = function(include_time){ + str = this.getFullYear() + "-" + Date.padded2(this.getMonth() + 1) + "-" +Date.padded2(this.getDate()); + +if (include_time) { hour=this.getHours(); str += " " + this.getAMPMHour() + ":" + this.getPaddedMinutes() + " " + this.getAMPM() } +return str; +} + +Date.parseFormattedString = function (string) { + var regexp = "([0-9]{4})(-([0-9]{2})(-([0-9]{2})" + + "( ([0-9]{1,2}):([0-9]{2})? *(pm|am)" + + "?)?)?)?"; + var d = string.match(new RegExp(regexp, "i")); + if (d==null) return Date.parse(string); // at least give javascript a crack at it. + var offset = 0; + var date = new Date(d[1], 0, 1); + if (d[3]) { date.setMonth(d[3] - 1); } + if (d[5]) { date.setDate(d[5]); } + if (d[7]) { + hours = parseInt(d[7]); + offset=0; + is_pm = (d[9].toLowerCase()=="pm") + if (is_pm && hours <= 11) hours+=12; + if (!is_pm && hours == 12) hours=0; + date.setHours(hours); + + } + if (d[8]) { date.setMinutes(d[8]); } + if (d[10]) { date.setSeconds(d[10]); } + if (d[12]) { date.setMilliseconds(Number("0." + d[12]) * 1000); } + if (d[14]) { + offset = (Number(d[16]) * 60) + Number(d[17]); + offset *= ((d[15] == '-') ? 1 : -1); + } + + return date; +} \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/locale/fi.js b/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/locale/fi.js new file mode 100644 index 0000000..443c910 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/javascripts/calendar_date_select/locale/fi.js @@ -0,0 +1,10 @@ +Date.weekdays = $w("Ma Ti Ke To Pe La Su"); +Date.months = $w("Tammikuu Helmikuu Maaliskuu Huhtikuu Toukokuu Kesäkuu Heinäkuu Elokuu Syyskuu Lokakuu Marraskuu Joulukuu" ); + +Date.first_day_of_week = 1 + +_translations = { + "OK": "OK", + "Now": "Nyt", + "Today": "Tänään" +} \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/blue.css b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/blue.css new file mode 100644 index 0000000..f3639d3 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/blue.css @@ -0,0 +1,112 @@ +.calendar_date_select { + color:white; + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; +} + +.calendar_date_select thead th { + font-weight:bold; + background-color: #000; + border-top:1px solid #777; + border-bottom:2px solid #334; + color: white !important; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; + background-color: #000055; +} + +.calendar_date_select .cds_footer { + background-color: black; + padding:3px; + font-size:12px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + background-color: #ccc; + border-bottom: 2px solid #aaa; + text-align:center; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} + +.calendar_date_select .cds_header a.next { + float:right; +} + +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} + +.calendar_date_select .cds_buttons a { + color: white; + font-size: 9px; +} + +.calendar_date_select td { + background-color: #000066; + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; +} +.calendar_date_select td.weekend { + background-color: #00005a; +} + +.calendar_date_select td div.other { + color: #4C5593; +} + +.calendar_date_select tbody td { + border-bottom: 1px solid #000044; +} +.calendar_date_select td.selected { + background-color:white; + color:black; +} + +.calendar_date_select td.hover { + background-color:#ccc; +} + +.calendar_date_select td.today { + border: 1px dashed blue; +} \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/default.css b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/default.css new file mode 100644 index 0000000..00b340d --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/default.css @@ -0,0 +1,116 @@ +.calendar_date_select { + color:white; + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; +} + +.calendar_date_select thead th { + font-weight:bold; + background-color: #aaa; + border-top:1px solid #777; + border-bottom:1px solid #777; + color: white !important; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; + background-color: #555; +} + +.calendar_date_select .cds_footer { + background-color: black; + padding:3px; + font-size:12px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + background-color: #ccc; + border-bottom: 2px solid #aaa; + text-align:center; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} +.calendar_date_select .cds_header a.next { + float:right; +} +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} +.calendar_date_select .cds_buttons a { + color: white; + font-size: 9px; +} + +.calendar_date_select td { + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; + background-color: #fff; +} +.calendar_date_select td.weekend { + background-color: #eee; + border-left:1px solid #ddd; + border-right:1px solid #ddd; +} + +.calendar_date_select td div { + color: #000; +} +.calendar_date_select td div.other { + color: #ccc; +} +.calendar_date_select td.selected div { + color:white; +} + +.calendar_date_select tbody td { + border-bottom: 1px solid #ddd; +} +.calendar_date_select td.selected { + background-color:#777; +} + +.calendar_date_select td.hover { + background-color:#ccc; +} + +.calendar_date_select td.today { + border: 1px dashed #999; +} \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/plain.css b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/plain.css new file mode 100644 index 0000000..0c1b34a --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/plain.css @@ -0,0 +1,110 @@ +.calendar_date_select { + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; + background-color:white; +} + +.calendar_date_select thead th { + color: black !important; + font-weight:bold; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; +} + +.calendar_date_select .cds_footer { + padding:3px; + font-size:10px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + text-align:center; +} + +.calendar_date_select .cds_header * { + border:0px; + background-color:white; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} +.calendar_date_select .cds_header a.next { + float:right; +} +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} + +.calendar_date_select .cds_buttons a { + color: black; + font-size: 9px; +} +.calendar_date_select td { + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; + background-color: #fff; +} +.calendar_date_select td.weekend { +} + +.calendar_date_select td div { + color: #000; +} +.calendar_date_select td div.other { + color: #ccc; +} +.calendar_date_select td.selected div { + color:white; +} + +.calendar_date_select tbody td { +} +.calendar_date_select td.selected { + background-color:#777; +} + +.calendar_date_select td.hover { + background-color:#ccc; +} + +.calendar_date_select td.today { + border: 1px dashed #999; +} \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/red.css b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/red.css new file mode 100644 index 0000000..eb7ee4c --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/red.css @@ -0,0 +1,119 @@ +.calendar_date_select { + color:white; + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; +} + +.calendar_date_select thead th { + font-weight:bold; + background-color: #E7E8E8; + border-bottom:2px solid black; + color: black !important; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; + background-color: #5f0000; + +} + +.calendar_date_select .cds_footer { + background-color: black; + padding:3px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + background-color: #ccc; + border-bottom: 2px solid #aaa; + text-align:center; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} +.calendar_date_select .cds_header a.next { + float:right; +} + +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} + +.calendar_date_select .cds_buttons a { + color: white; + font-size: 9px; +} +.calendar_date_select td { + background-color: #660000; + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; +} +.calendar_date_select td.weekend { + background-color: #5a0000; +} + +.calendar_date_select td div { + color:#fff; +} +.calendar_date_select td div.other { + color: #93554C; +} +.calendar_date_select td.selected div { + color:black; +} + + +.calendar_date_select tbody td { + border-bottom: 1px solid #550000; +} +.calendar_date_select tbody td.selected { + background-color:white; + color:black; +} + +.calendar_date_select tbody td.hover { + background-color:#ccc; +} + +.calendar_date_select tbody td.today { + border: 1px dashed red; +} + +/* adjust bordered cells to have slightly smaller inner areas so they look the same as the rest of the elements */ + \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/silver.css b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/silver.css new file mode 100644 index 0000000..0a44924 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/public/stylesheets/calendar_date_select/silver.css @@ -0,0 +1,114 @@ +.calendar_date_select { + color:white; + border:#777 1px solid; + display:block; + width:195px; + z-index: 1000; +} + +.calendar_date_select thead th { + font-weight:bold; + background-color: #000; + border-top:1px solid #777; + border-bottom:2px solid #333; + color: white !important; +} + +.calendar_date_select .cds_buttons { + text-align:center; + padding:5px 0px; + background-color: #555; +} + +.calendar_date_select .cds_footer { + background-color: black; + padding:3px; + font-size:12px; + text-align:center; +} + +.calendar_date_select table { + margin: 0px; + padding: 0px; +} + + +.calendar_date_select .cds_header { + background-color: #ccc; + border-bottom: 2px solid #aaa; + text-align:center; +} + +.calendar_date_select .cds_header span { + font-size:15px; + color: black; + font-weight: bold; +} + +.calendar_date_select select { font-size:11px;} + +.calendar_date_select .cds_header a:hover { + color: white; +} +.calendar_date_select .cds_header a { + width:22px; + height:20px; + text-decoration: none; + font-size:14px; + color:black !important; +} + +.calendar_date_select .cds_header a.prev { + float:left; +} +.calendar_date_select .cds_header a.next { + float:right; +} +.calendar_date_select .cds_header select.month { + width:90px; +} + +.calendar_date_select .cds_header select.year { + width:61px; +} + +.calendar_date_select .cds_buttons a { + color: white; + font-size: 9px; +} +.calendar_date_select td { + font-size:12px; + width: 24px; + height: 21px; + text-align:center; + vertical-align: middle; + background-color: #666666; +} +.calendar_date_select td.weekend { + background-color: #606060; +} + +.calendar_date_select td div { + color: #fff; +} +.calendar_date_select td div.other { + color: #888; +} +.calendar_date_select td.selected div { + color:black; +} + +.calendar_date_select tbody td { + border-bottom: 1px solid #555; +} +.calendar_date_select td.selected { + background-color:white; +} + +.calendar_date_select td.hover { + background-color:#ccc; +} + +.calendar_date_select td.today { + border: 1px dashed #999; +} \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/test/functional/calendar_date_select_test.rb b/chapter12/training/vendor/plugins/calendar_date_select/test/functional/calendar_date_select_test.rb new file mode 100644 index 0000000..7c23614 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/test/functional/calendar_date_select_test.rb @@ -0,0 +1,49 @@ +require File.join(File.dirname(__FILE__), '../test_helper.rb') + +class HelperMethodsTest < Test::Unit::TestCase + include ActionView::Helpers::FormHelper + include ActionView::Helpers::JavaScriptHelper + include ActionView::Helpers::AssetTagHelper + include ActionView::Helpers::TagHelper + + include CalendarDateSelect::FormHelper + + def setup + @controller = ActionController::Base.new + @request = OpenStruct.new + @controller.request = @request + + @model = OpenStruct.new + end + + def test_mixed_time__model_returns_date__should_render_without_time + @model.start_datetime = Date.parse("January 2, 2007") + output = calendar_date_select(:model, :start_datetime, :time => "mixed") + assert_no_match(/12:00 AM/, output, "Shouldn't have outputted a time") + end + + def test_mixed_time__model_returns_time__should_render_with_time + @model.start_datetime = Time.parse("January 2, 2007 12:00 AM") + output = calendar_date_select(:model, :start_datetime, :time => "mixed") + assert_match(/12:00 AM/, output, "Should have outputted a time") + end + + def test_time_true__model_returns_date__should_render_with_time + @model.start_datetime = Date.parse("January 2, 2007") + output = calendar_date_select(:model, :start_datetime, :time => "true") + assert_match(/12:00 AM/, output, "Should have outputted a time") + end + + def test_time_false__model_returns_time__should_render_without_time + @model.start_datetime = Time.parse("January 2, 2007 12:00 AM") + output = calendar_date_select(:model, :start_datetime, :time => false) + assert_no_match(/12:00 AM/, output, "Shouldn't have outputted a time") + end + + def test__nil_model__shouldnt_populate_value + @model = nil + output = calendar_date_select(:model, :start_datetime) + + assert_no_match(/value/, output) + end +end \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/test/functional/helper_methods_test.rb b/chapter12/training/vendor/plugins/calendar_date_select/test/functional/helper_methods_test.rb new file mode 100644 index 0000000..0a23b57 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/test/functional/helper_methods_test.rb @@ -0,0 +1,15 @@ +require File.join(File.dirname(__FILE__), '../test_helper.rb') + +class HelperMethodsTest < Test::Unit::TestCase + def setup + + end + + def test_has_time + assert( ! CalendarDateSelect.has_time?("January 7, 2007")) + assert(CalendarDateSelect.has_time?("January 7, 2007 5:50pm")) + assert(CalendarDateSelect.has_time?("January 7, 2007 5:50 pm")) + assert(CalendarDateSelect.has_time?("January 7, 2007 16:30 pm")) + end + +end \ No newline at end of file diff --git a/chapter12/training/vendor/plugins/calendar_date_select/test/test_helper.rb b/chapter12/training/vendor/plugins/calendar_date_select/test/test_helper.rb new file mode 100644 index 0000000..0943ef8 --- /dev/null +++ b/chapter12/training/vendor/plugins/calendar_date_select/test/test_helper.rb @@ -0,0 +1,26 @@ +require "rubygems" + +require 'test/unit' + +require 'active_support' +require 'action_pack' +require 'action_controller' +require 'action_view' + +require 'ostruct' + +for file in ["../lib/calendar_date_select.rb", "../lib/includes_helper.rb"] + require File.expand_path(File.join(File.dirname(__FILE__), file)) +end + +def dbg + require 'ruby-debug' + Debugger.start + debugger +end + +class Object + def to_regexp + is_a?(Regexp) ? self : Regexp.new(Regexp.escape(self.to_s)) + end +end \ No newline at end of file diff --git a/chapter12/training_loader.rb b/chapter12/training_loader.rb new file mode 100644 index 0000000..9ff7d25 --- /dev/null +++ b/chapter12/training_loader.rb @@ -0,0 +1,84 @@ +require 'dbi' +require 'xmlsimple' +require 'yaml' +require 'open-uri' +require 'rubyscript2exe' +require 'swin' +database_path = ARGV[0] +unless database_path # If no path was specified on the command line, then ask for one. + + + # You can find out more about Windows common dialogs here: + # http://msdn2.microsoft.com/en-us/library/ms646949.aspx + # You can find the header file with the full list of constants + # here: + # http://doc.ddart.net/msdn/header/include/commdlg.h.html + + OFN_HIDEREADONLY = 0x0004 + OFN_PATHMUSTEXIST = 0x0800 + OFN_FILEMUSTEXIST = 0x1000 + + filetype_filter =[['Access Database (*.mdb)','*.mdb'], + ['All files (*.*)', '*.*']] + + database_path = SWin::CommonDialog::openFilename( + nil, + filetype_filter, + OFN_HIDEREADONLY | + OFN_PATHMUSTEXIST | + OFN_FILEMUSTEXIST, + 'Choose a database') + + + + exit if database_path.nil? +end + +begin + + domain = 'localhost' + port = '3000' + + xml = open("http://#{domain}:#{port}/log/all").read + grades = XmlSimple.xml_in(xml)['grade'] + imported_count = 0 + DBI.connect("DBI:ADO:Provider=Microsoft.Jet.OLEDB.4.0;Data Source=#{database_path}") do |dbh| + + grades.each do |grade_raw| + g ={} + grade_raw.each do |key,value| + if value.length == 1 + g[key] = value.first + else + g[key] = value + end + end + #g.map! { g.length==1 ? g.first : g} + + sql = "SELECT COUNT(*) + FROM grades + WHERE id=?;" + dbh.select_all(sql, g['id'].to_i) do |row| + count = *row + if count == 0 + sql = 'INSERT INTO grades + (id, student, + employer, grade, + class_date, class_name) + VALUES (?,?,?,?,?, ?);' + dbh.do(sql, g['id'], g['student'], + g['employer'], g['grade'], + Date.parse(g['took_class_at']), + g['class'] + ); + dbh.commit + imported_count = imported_count + 1 + end + end + end + end + + SWin::Application.messageBox "Done! #{imported_count} records imported.", "All done!" +#rescue +# SWin::Application.messageBox $!, "Error while importing" +end diff --git a/chapter13/adwords_report.rb b/chapter13/adwords_report.rb new file mode 100644 index 0000000..805d669 --- /dev/null +++ b/chapter13/adwords_report.rb @@ -0,0 +1,17 @@ +require 'active_record' +require 'ruport' + +class AdResult < ActiveRecord::Base +end + +ActiveRecord::Base.establish_connection( + :adapter=>'mysql', + :host=>'127.0.0.1', + :database=>'text_ad_report', + :username=>'root', + :password=>'') + +# This is the default username and password +# for MySQL, but note that if you have a +# different username and password, +# you should change it. diff --git a/chapter13/adwords_reporter/README b/chapter13/adwords_reporter/README new file mode 100644 index 0000000..0d6affd --- /dev/null +++ b/chapter13/adwords_reporter/README @@ -0,0 +1,182 @@ +== Welcome to Rails + +Rails is a web-application and persistence framework that includes everything +needed to create database-backed web-applications according to the +Model-View-Control pattern of separation. This pattern splits the view (also +called the presentation) into "dumb" templates that are primarily responsible +for inserting pre-built data in between HTML tags. The model contains the +"smart" domain objects (such as Account, Product, Person, Post) that holds all +the business logic and knows how to persist themselves to a database. The +controller handles the incoming requests (such as Save New Account, Update +Product, Show Post) by manipulating the model and directing data to the view. + +In Rails, the model is handled by what's called an object-relational mapping +layer entitled Active Record. This layer allows you to present the data from +database rows as objects and embellish these data objects with business logic +methods. You can read more about Active Record in +link:files/vendor/rails/activerecord/README.html. + +The controller and view are handled by the Action Pack, which handles both +layers by its two parts: Action View and Action Controller. These two layers +are bundled in a single package due to their heavy interdependence. This is +unlike the relationship between the Active Record and Action Pack that is much +more separate. Each of these packages can be used independently outside of +Rails. You can read more about Action Pack in +link:files/vendor/rails/actionpack/README.html. + + +== Getting started + +1. At the command prompt, start a new rails application using the rails command + and your application name. Ex: rails myapp + (If you've downloaded rails in a complete tgz or zip, this step is already done) +2. Change directory into myapp and start the web server: script/server (run with --help for options) +3. Go to http://localhost:3000/ and get "Welcome aboard: You’re riding the Rails!" +4. Follow the guidelines to start developing your application + + +== Web Servers + +By default, Rails will try to use Mongrel and lighttpd if they are installed, otherwise +Rails will use the WEBrick, the webserver that ships with Ruby. When you run script/server, +Rails will check if Mongrel exists, then lighttpd and finally fall back to WEBrick. This ensures +that you can always get up and running quickly. + +Mongrel is a Ruby-based webserver with a C-component (which requires compilation) that is +suitable for development and deployment of Rails applications. If you have Ruby Gems installed, +getting up and running with mongrel is as easy as: gem install mongrel. +More info at: http://mongrel.rubyforge.org + +If Mongrel is not installed, Rails will look for lighttpd. It's considerably faster than +Mongrel and WEBrick and also suited for production use, but requires additional +installation and currently only works well on OS X/Unix (Windows users are encouraged +to start with Mongrel). We recommend version 1.4.11 and higher. You can download it from +http://www.lighttpd.net. + +And finally, if neither Mongrel or lighttpd are installed, Rails will use the built-in Ruby +web server, WEBrick. WEBrick is a small Ruby web server suitable for development, but not +for production. + +But of course its also possible to run Rails on any platform that supports FCGI. +Apache, LiteSpeed, IIS are just a few. For more information on FCGI, +please visit: http://wiki.rubyonrails.com/rails/pages/FastCGI + + +== Debugging Rails + +Have "tail -f" commands running on the server.log and development.log. Rails will +automatically display debugging and runtime information to these files. Debugging +info will also be shown in the browser on requests from 127.0.0.1. + + +== Breakpoints + +Breakpoint support is available through the script/breakpointer client. This +means that you can break out of execution at any point in the code, investigate +and change the model, AND then resume execution! Example: + + class WeblogController < ActionController::Base + def index + @posts = Post.find(:all) + breakpoint "Breaking out from the list" + end + end + +So the controller will accept the action, run the first line, then present you +with a IRB prompt in the breakpointer window. Here you can do things like: + +Executing breakpoint "Breaking out from the list" at .../webrick_server.rb:16 in 'breakpoint' + + >> @posts.inspect + => "[#nil, \"body\"=>nil, \"id\"=>\"1\"}>, + #\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]" + >> @posts.first.title = "hello from a breakpoint" + => "hello from a breakpoint" + +...and even better is that you can examine how your runtime objects actually work: + + >> f = @posts.first + => #nil, "body"=>nil, "id"=>"1"}> + >> f. + Display all 152 possibilities? (y or n) + +Finally, when you're ready to resume execution, you press CTRL-D + + +== Console + +You can interact with the domain model by starting the console through script/console. +Here you'll have all parts of the application configured, just like it is when the +application is running. You can inspect domain models, change values, and save to the +database. Starting the script without arguments will launch it in the development environment. +Passing an argument will specify a different environment, like script/console production. + +To reload your controllers and models after launching the console run reload! + +To reload your controllers and models after launching the console run reload! + + + +== Description of contents + +app + Holds all the code that's specific to this particular application. + +app/controllers + Holds controllers that should be named like weblogs_controller.rb for + automated URL mapping. All controllers should descend from ApplicationController + which itself descends from ActionController::Base. + +app/models + Holds models that should be named like post.rb. + Most models will descend from ActiveRecord::Base. + +app/views + Holds the template files for the view that should be named like + weblogs/index.rhtml for the WeblogsController#index action. All views use eRuby + syntax. + +app/views/layouts + Holds the template files for layouts to be used with views. This models the common + header/footer method of wrapping views. In your views, define a layout using the + layout :default and create a file named default.rhtml. Inside default.rhtml, + call <% yield %> to render the view using this layout. + +app/helpers + Holds view helpers that should be named like weblogs_helper.rb. These are generated + for you automatically when using script/generate for controllers. Helpers can be used to + wrap functionality for your views into methods. + +config + Configuration files for the Rails environment, the routing map, the database, and other dependencies. + +components + Self-contained mini-applications that can bundle together controllers, models, and views. + +db + Contains the database schema in schema.rb. db/migrate contains all + the sequence of Migrations for your schema. + +doc + This directory is where your application documentation will be stored when generated + using rake doc:app + +lib + Application specific libraries. Basically, any kind of custom code that doesn't + belong under controllers, models, or helpers. This directory is in the load path. + +public + The directory available for the web server. Contains subdirectories for images, stylesheets, + and javascripts. Also contains the dispatchers and the default HTML files. This should be + set as the DOCUMENT_ROOT of your web server. + +script + Helper scripts for automation and generation. + +test + Unit and functional tests along with fixtures. When using the script/generate scripts, template + test files will be generated for you and placed in this directory. + +vendor + External libraries that the application depends on. Also includes the plugins subdirectory. + This directory is in the load path. diff --git a/chapter13/adwords_reporter/Rakefile b/chapter13/adwords_reporter/Rakefile new file mode 100644 index 0000000..3bb0e85 --- /dev/null +++ b/chapter13/adwords_reporter/Rakefile @@ -0,0 +1,10 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require(File.join(File.dirname(__FILE__), 'config', 'boot')) + +require 'rake' +require 'rake/testtask' +require 'rake/rdoctask' + +require 'tasks/rails' diff --git a/chapter13/adwords_reporter/app/controllers/application.rb b/chapter13/adwords_reporter/app/controllers/application.rb new file mode 100644 index 0000000..6600f7a --- /dev/null +++ b/chapter13/adwords_reporter/app/controllers/application.rb @@ -0,0 +1,7 @@ +# Filters added to this controller apply to all controllers in the application. +# Likewise, all the methods added will be available for all controllers. + +class ApplicationController < ActionController::Base + # Pick a unique cookie name to distinguish our session data from others' + session :session_key => '_adwords_reporter_session_id' +end diff --git a/chapter13/adwords_reporter/app/controllers/budget_optimizer_controller.rb b/chapter13/adwords_reporter/app/controllers/budget_optimizer_controller.rb new file mode 100644 index 0000000..331c025 --- /dev/null +++ b/chapter13/adwords_reporter/app/controllers/budget_optimizer_controller.rb @@ -0,0 +1,33 @@ +class BudgetOptimizerController < ApplicationController + def index + end + + def report + @excel_view = params[:view_as_excel] + @target_clicks=params[:target_clicks].to_f + + results_raw=AdResult.find(:all, + :select=>'headline, + AVG(cost) as cost, + AVG(clicks) as clicks', + :group=>'headline') + + results_raw.sort! { |x,y| (x.cost/x.clicks <=> y.cost/y.clicks) } + @results = [] + click_sum = 0.0 + results_raw.each do |r| + @results << r + click_sum += r.clicks + break if click_sum > @target_clicks + end + @estimated_clicks = click_sum + @avg_cost_per_click = ( + @results.inject(0.0) { |sum,r| sum+=r.cost } ) / ( + @results.inject(0.0){ |sum,r| sum+= r.clicks } ) + if @excel_view + headers['Content-Type'] = "application/vnd.ms-excel" + headers['Content-Disposition'] = 'attachment; filename="adwords_report.xls"' + end + end +end + diff --git a/chapter13/adwords_reporter/app/helpers/application_helper.rb b/chapter13/adwords_reporter/app/helpers/application_helper.rb new file mode 100644 index 0000000..22a7940 --- /dev/null +++ b/chapter13/adwords_reporter/app/helpers/application_helper.rb @@ -0,0 +1,3 @@ +# Methods added to this helper will be available to all templates in the application. +module ApplicationHelper +end diff --git a/chapter13/adwords_reporter/app/helpers/budget_optimizer_helper.rb b/chapter13/adwords_reporter/app/helpers/budget_optimizer_helper.rb new file mode 100644 index 0000000..81f421f --- /dev/null +++ b/chapter13/adwords_reporter/app/helpers/budget_optimizer_helper.rb @@ -0,0 +1,5 @@ +module BudgetOptimizerHelper + def format_google_currency(currency_value) + "#{'%0.2f' % (currency_value/10000.0) } cents" + end +end diff --git a/chapter13/adwords_reporter/app/models/ad_result.rb b/chapter13/adwords_reporter/app/models/ad_result.rb new file mode 100644 index 0000000..a4313ad --- /dev/null +++ b/chapter13/adwords_reporter/app/models/ad_result.rb @@ -0,0 +1,2 @@ +class AdResult < ActiveRecord::Base +end diff --git a/chapter13/adwords_reporter/app/views/budget_optimizer/index.rhtml b/chapter13/adwords_reporter/app/views/budget_optimizer/index.rhtml new file mode 100644 index 0000000..8cecf59 --- /dev/null +++ b/chapter13/adwords_reporter/app/views/budget_optimizer/index.rhtml @@ -0,0 +1,11 @@ +

      Create AdWords Report

      + +
      + <% form_tag(:action => 'report') do %> + Target number of clicks: <%=text_field_tag 'target_clicks', '10', + :size=>4%> + + <%=submit_tag 'Create Report', :id=>'create_report'%> + <% end %> +
      + diff --git a/chapter13/adwords_reporter/app/views/budget_optimizer/report.rhtml b/chapter13/adwords_reporter/app/views/budget_optimizer/report.rhtml new file mode 100644 index 0000000..5bad955 --- /dev/null +++ b/chapter13/adwords_reporter/app/views/budget_optimizer/report.rhtml @@ -0,0 +1,42 @@ +

      Google AdWords Campaign Plan

      +<%unless @excel_view %> +

      <%=link_to '[download as excel]', + :params=>{ + 'view_as_excel' =>true, + 'target_clicks'=>@target_clicks + } + %>

      +<%end%> + + + + + + +<%@results.each do |r| %> + + + + + + +<%end%> +
      Ad HeadlineAvg ClicksCost Per Click
      <%=r.headline%> <%=r.clicks%> clicks <%=format_google_currency(r.cost/r.clicks) %>
      + +

      Summary

      + + + + + + + + + + + + + +
      Goal Clicks<%=@target_clicks%>
      Estimated Available Clicks<%=@estimated_clicks%>
      Estimated Cost Per Click (CPC)<%=format_google_currency(@avg_cost_per_click )%>
      + + diff --git a/chapter13/adwords_reporter/app/views/layouts/application.rhtml b/chapter13/adwords_reporter/app/views/layouts/application.rhtml new file mode 100644 index 0000000..9c3154c --- /dev/null +++ b/chapter13/adwords_reporter/app/views/layouts/application.rhtml @@ -0,0 +1,32 @@ + + + + + + <%=@content_for_layout%> + + diff --git a/chapter13/adwords_reporter/config/boot.rb b/chapter13/adwords_reporter/config/boot.rb new file mode 100644 index 0000000..128fe76 --- /dev/null +++ b/chapter13/adwords_reporter/config/boot.rb @@ -0,0 +1,45 @@ +# Don't change this file. Configuration is done in config/environment.rb and config/environments/*.rb + +unless defined?(RAILS_ROOT) + root_path = File.join(File.dirname(__FILE__), '..') + + unless RUBY_PLATFORM =~ /mswin32/ + require 'pathname' + root_path = Pathname.new(root_path).cleanpath(true).to_s + end + + RAILS_ROOT = root_path +end + +unless defined?(Rails::Initializer) + if File.directory?("#{RAILS_ROOT}/vendor/rails") + require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer" + else + require 'rubygems' + + environment_without_comments = IO.readlines(File.dirname(__FILE__) + '/environment.rb').reject { |l| l =~ /^#/ }.join + environment_without_comments =~ /[^#]RAILS_GEM_VERSION = '([\d.]+)'/ + rails_gem_version = $1 + + if version = defined?(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : rails_gem_version + # Asking for 1.1.6 will give you 1.1.6.5206, if available -- makes it easier to use beta gems + rails_gem = Gem.cache.search('rails', "~>#{version}.0").sort_by { |g| g.version.version }.last + + if rails_gem + require_gem "rails", "=#{rails_gem.version.version}" + require rails_gem.full_gem_path + '/lib/initializer' + else + STDERR.puts %(Cannot find gem for Rails ~>#{version}.0: + Install the missing gem with 'gem install -v=#{version} rails', or + change environment.rb to define RAILS_GEM_VERSION with your desired version. + ) + exit 1 + end + else + require_gem "rails" + require 'initializer' + end + end + + Rails::Initializer.run(:set_load_path) +end \ No newline at end of file diff --git a/chapter13/adwords_reporter/config/database.yml b/chapter13/adwords_reporter/config/database.yml new file mode 100644 index 0000000..a93c553 --- /dev/null +++ b/chapter13/adwords_reporter/config/database.yml @@ -0,0 +1,36 @@ +# MySQL (default setup). Versions 4.1 and 5.0 are recommended. +# +# Install the MySQL driver: +# gem install mysql +# On MacOS X: +# gem install mysql -- --include=/usr/local/lib +# On Windows: +# gem install mysql +# Choose the win32 build. +# Install MySQL and put its /bin directory on your path. +# +# 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: text_ad_report + username: root + password: + host: localhost + +# Warning: The database defined as 'test' will be erased and +# re-generated from your development database when you run 'rake'. +# Do not set this db to the same as development or production. +test: + adapter: mysql + database: adwords_reporter_test + username: root + password: + host: localhost + +production: + adapter: mysql + database: adwords_reporter_production + username: root + password: + host: localhost diff --git a/chapter13/adwords_reporter/config/environment.rb b/chapter13/adwords_reporter/config/environment.rb new file mode 100644 index 0000000..6a93ff4 --- /dev/null +++ b/chapter13/adwords_reporter/config/environment.rb @@ -0,0 +1,60 @@ +# Be sure to restart your web server when you modify this file. + +# Uncomment below to force Rails into production mode when +# you don't control web/app server and can't set it the proper way +# ENV['RAILS_ENV'] ||= 'production' + +# Specifies gem version of Rails to use when vendor/rails is not present +RAILS_GEM_VERSION = '1.2.0' unless defined? RAILS_GEM_VERSION + +# Bootstrap the Rails environment, frameworks, and default configuration +require File.join(File.dirname(__FILE__), 'boot') + +Rails::Initializer.run do |config| + # Settings in config/environments/* take precedence over those specified here + + # Skip frameworks you're not going to use (only works if using vendor/rails) + # config.frameworks -= [ :action_web_service, :action_mailer ] + + # Only load the plugins named here, by default all plugins in vendor/plugins are loaded + # config.plugins = %W( exception_notification ssl_requirement ) + + # Add additional load paths for your own custom dirs + # config.load_paths += %W( #{RAILS_ROOT}/extras ) + + # Force all environments to use the same logger level + # (by default production uses :info, the others :debug) + # config.log_level = :debug + + # Use the database for sessions instead of the file system + # (create the session table with 'rake db:sessions:create') + # config.action_controller.session_store = :active_record_store + + # Use SQL instead of Active Record's schema dumper when creating the test database. + # This is necessary if your schema can't be completely dumped by the schema dumper, + # like if you have constraints or database-specific column types + # config.active_record.schema_format = :sql + + # Activate observers that should always be running + # config.active_record.observers = :cacher, :garbage_collector + + # Make Active Record use UTC-base instead of local time + # config.active_record.default_timezone = :utc + + # See Rails::Configuration for more options +end + +# Add new inflection rules using the following format +# (all these examples are active by default): +# Inflector.inflections do |inflect| +# inflect.plural /^(ox)$/i, '\1en' +# inflect.singular /^(ox)en/i, '\1' +# inflect.irregular 'person', 'people' +# inflect.uncountable %w( fish sheep ) +# end + +# Add new mime types for use in respond_to blocks: +# Mime::Type.register "text/richtext", :rtf +# Mime::Type.register "application/x-mobile", :mobile + +# Include your application configuration below \ No newline at end of file diff --git a/chapter13/adwords_reporter/config/environments/development.rb b/chapter13/adwords_reporter/config/environments/development.rb new file mode 100644 index 0000000..0589aa9 --- /dev/null +++ b/chapter13/adwords_reporter/config/environments/development.rb @@ -0,0 +1,21 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# In the development environment your application's code is reloaded on +# every request. This slows down response time but is perfect for development +# since you don't have to restart the webserver when you make code changes. +config.cache_classes = false + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Enable the breakpoint server that script/breakpointer connects to +config.breakpoint_server = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false +config.action_view.cache_template_extensions = false +config.action_view.debug_rjs = true + +# Don't care if the mailer can't send +config.action_mailer.raise_delivery_errors = false diff --git a/chapter13/adwords_reporter/config/environments/production.rb b/chapter13/adwords_reporter/config/environments/production.rb new file mode 100644 index 0000000..cb295b8 --- /dev/null +++ b/chapter13/adwords_reporter/config/environments/production.rb @@ -0,0 +1,18 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The production environment is meant for finished, "live" apps. +# Code is not reloaded between requests +config.cache_classes = true + +# Use a different logger for distributed setups +# config.logger = SyslogLogger.new + +# Full error reports are disabled and caching is turned on +config.action_controller.consider_all_requests_local = false +config.action_controller.perform_caching = true + +# Enable serving of images, stylesheets, and javascripts from an asset server +# config.action_controller.asset_host = "http://assets.example.com" + +# Disable delivery errors, bad email addresses will be ignored +# config.action_mailer.raise_delivery_errors = false diff --git a/chapter13/adwords_reporter/config/environments/test.rb b/chapter13/adwords_reporter/config/environments/test.rb new file mode 100644 index 0000000..f0689b9 --- /dev/null +++ b/chapter13/adwords_reporter/config/environments/test.rb @@ -0,0 +1,19 @@ +# Settings specified here will take precedence over those in config/environment.rb + +# The test environment is used exclusively to run your application's +# test suite. You never need to work with it otherwise. Remember that +# your test database is "scratch space" for the test suite and is wiped +# and recreated between test runs. Don't rely on the data there! +config.cache_classes = true + +# Log error messages when you accidentally call methods on nil. +config.whiny_nils = true + +# Show full error reports and disable caching +config.action_controller.consider_all_requests_local = true +config.action_controller.perform_caching = false + +# Tell ActionMailer not to deliver emails to the real world. +# The :test delivery method accumulates sent emails in the +# ActionMailer::Base.deliveries array. +config.action_mailer.delivery_method = :test \ No newline at end of file diff --git a/chapter13/adwords_reporter/config/routes.rb b/chapter13/adwords_reporter/config/routes.rb new file mode 100644 index 0000000..33397bb --- /dev/null +++ b/chapter13/adwords_reporter/config/routes.rb @@ -0,0 +1,24 @@ +ActionController::Routing::Routes.draw do |map| + # The priority is based upon order of creation: first created -> highest priority. + + # Sample of regular route: + # map.connect 'products/:id', :controller => 'catalog', :action => 'view' + # Keep in mind you can assign values other than :controller and :action + + # Sample of named route: + # map.purchase 'products/:id/purchase', :controller => 'catalog', :action => 'purchase' + # This route can be invoked with purchase_url(:id => product.id) + + # You can have the root of your site routed by hooking up '' + # -- just remember to delete public/index.html. + # map.connect '', :controller => "welcome" + + # 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.connect '/log', :controller=>'log', :action => 'index', :format=>"xml" + + # Install the default route as the lowest priority. + map.connect ':controller/:action/:id.:format' + map.connect ':controller/:action/:id' +end diff --git a/chapter13/adwords_reporter/db/migrate/001_create_ad_results.rb b/chapter13/adwords_reporter/db/migrate/001_create_ad_results.rb new file mode 100644 index 0000000..7e10fa9 --- /dev/null +++ b/chapter13/adwords_reporter/db/migrate/001_create_ad_results.rb @@ -0,0 +1,10 @@ +class CreateAdResults < ActiveRecord::Migration + def self.up + create_table :ad_results do |t| + end + end + + def self.down + drop_table :ad_results + end +end diff --git a/chapter13/adwords_reporter/doc/README_FOR_APP b/chapter13/adwords_reporter/doc/README_FOR_APP new file mode 100644 index 0000000..ac6c149 --- /dev/null +++ b/chapter13/adwords_reporter/doc/README_FOR_APP @@ -0,0 +1,2 @@ +Use this README file to introduce your application and point to useful places in the API for learning more. +Run "rake appdoc" to generate API documentation for your models and controllers. \ No newline at end of file diff --git a/chapter13/adwords_reporter/public/.htaccess b/chapter13/adwords_reporter/public/.htaccess new file mode 100644 index 0000000..d3c9983 --- /dev/null +++ b/chapter13/adwords_reporter/public/.htaccess @@ -0,0 +1,40 @@ +# General Apache options +AddHandler fastcgi-script .fcgi +AddHandler cgi-script .cgi +Options +FollowSymLinks +ExecCGI + +# If you don't want Rails to look in certain directories, +# use the following rewrite rules so that Apache won't rewrite certain requests +# +# Example: +# RewriteCond %{REQUEST_URI} ^/notrails.* +# RewriteRule .* - [L] + +# Redirect all requests not available on the filesystem to Rails +# By default the cgi dispatcher is used which is very slow +# +# For better performance replace the dispatcher with the fastcgi one +# +# Example: +# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L] +RewriteEngine On + +# If your Rails application is accessed via an Alias directive, +# then you MUST also set the RewriteBase in this htaccess file. +# +# Example: +# Alias /myrailsapp /path/to/myrailsapp/public +# RewriteBase /myrailsapp + +RewriteRule ^$ index.html [QSA] +RewriteRule ^([^.]+)$ $1.html [QSA] +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ dispatch.cgi [QSA,L] + +# In case Rails experiences terminal errors +# Instead of displaying this message you can supply a file here which will be rendered instead +# +# Example: +# ErrorDocument 500 /500.html + +ErrorDocument 500 "

      Application error

      Rails application failed to start properly" \ No newline at end of file diff --git a/chapter13/adwords_reporter/public/404.html b/chapter13/adwords_reporter/public/404.html new file mode 100644 index 0000000..eff660b --- /dev/null +++ b/chapter13/adwords_reporter/public/404.html @@ -0,0 +1,30 @@ + + + + + + + The page you were looking for doesn't exist (404) + + + + + +
      +

      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/chapter13/adwords_reporter/public/500.html b/chapter13/adwords_reporter/public/500.html new file mode 100644 index 0000000..f0aee0e --- /dev/null +++ b/chapter13/adwords_reporter/public/500.html @@ -0,0 +1,30 @@ + + + + + + + We're sorry, but something went wrong + + + + + +
      +

      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/chapter13/adwords_reporter/public/dispatch.cgi b/chapter13/adwords_reporter/public/dispatch.cgi new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter13/adwords_reporter/public/dispatch.cgi @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter13/adwords_reporter/public/dispatch.fcgi b/chapter13/adwords_reporter/public/dispatch.fcgi new file mode 100644 index 0000000..5d9b8ec --- /dev/null +++ b/chapter13/adwords_reporter/public/dispatch.fcgi @@ -0,0 +1,24 @@ +#!c:/ruby/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) +# and the number of requests to process before running garbage collection. +# +# By default, the FastCGI crash log is RAILS_ROOT/log/fastcgi.crash.log +# and the GC period is nil (turned off). A reasonable number of requests +# could range from 10-100 depending on the memory footprint of your app. +# +# Example: +# # Default log path, normal GC behavior. +# RailsFCGIHandler.process! +# +# # Default log path, 50 requests between GC. +# RailsFCGIHandler.process! nil, 50 +# +# # Custom log path, normal GC behavior. +# RailsFCGIHandler.process! '/var/log/myapp_fcgi_crash.log' +# +require File.dirname(__FILE__) + "/../config/environment" +require 'fcgi_handler' + +RailsFCGIHandler.process! diff --git a/chapter13/adwords_reporter/public/dispatch.rb b/chapter13/adwords_reporter/public/dispatch.rb new file mode 100644 index 0000000..c584d66 --- /dev/null +++ b/chapter13/adwords_reporter/public/dispatch.rb @@ -0,0 +1,10 @@ +#!c:/ruby/bin/ruby + +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 +require "dispatcher" + +ADDITIONAL_LOAD_PATHS.reverse.each { |dir| $:.unshift(dir) if File.directory?(dir) } if defined?(Apache::RubyRun) +Dispatcher.dispatch \ No newline at end of file diff --git a/chapter13/adwords_reporter/public/favicon.ico b/chapter13/adwords_reporter/public/favicon.ico new file mode 100644 index 0000000..e69de29 diff --git a/chapter13/adwords_reporter/public/images/rails.png b/chapter13/adwords_reporter/public/images/rails.png new file mode 100644 index 0000000..b8441f1 Binary files /dev/null and b/chapter13/adwords_reporter/public/images/rails.png differ diff --git a/chapter13/adwords_reporter/public/index.html b/chapter13/adwords_reporter/public/index.html new file mode 100644 index 0000000..a2daab7 --- /dev/null +++ b/chapter13/adwords_reporter/public/index.html @@ -0,0 +1,277 @@ + + + + + Ruby on Rails: Welcome aboard + + + + + + +
      + + +
      + + + + +
      +

      Getting started

      +

      Here’s how to get rolling:

      + +
        +
      1. +

        Create your databases and edit config/database.yml

        +

        Rails needs to know your login and password.

        +
      2. + +
      3. +

        Use script/generate to create your models and controllers

        +

        To see all available options, run it without parameters.

        +
      4. + +
      5. +

        Set up a default route and remove or rename this file

        +

        Routes are setup in config/routes.rb.

        +
      6. +
      +
      +
      + + +
      + + \ No newline at end of file diff --git a/chapter13/adwords_reporter/public/javascripts/application.js b/chapter13/adwords_reporter/public/javascripts/application.js new file mode 100644 index 0000000..fe45776 --- /dev/null +++ b/chapter13/adwords_reporter/public/javascripts/application.js @@ -0,0 +1,2 @@ +// Place your application-specific JavaScript functions and classes here +// This file is automatically included by javascript_include_tag :defaults diff --git a/chapter13/adwords_reporter/public/javascripts/controls.js b/chapter13/adwords_reporter/public/javascripts/controls.js new file mode 100644 index 0000000..8c273f8 --- /dev/null +++ b/chapter13/adwords_reporter/public/javascripts/controls.js @@ -0,0 +1,833 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005, 2006 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 +// 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, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = {} +Autocompleter.Base = function() {}; +Autocompleter.Base.prototype = { + baseInitialize: function(element, update, options) { + this.element = $(element); + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || {}; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (navigator.appVersion.indexOf('MSIE')>0) && + (navigator.userAgent.indexOf('Opera')<0) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + 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; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + 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; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + 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++ + 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()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = document.getElementsByClassName(this.options.select, selectedElement) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var lastTokenPos = this.findLastToken(); + if (lastTokenPos != -1) { + var newValue = this.element.value.substr(0, lastTokenPos + 1); + var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value; + } else { + this.element.value = value; + } + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + 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 { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + if(this.getToken().length>=this.options.minChars) { + this.startIndicator(); + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + }, + + getToken: function() { + var tokenPos = this.findLastToken(); + if (tokenPos != -1) + var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,''); + else + var ret = this.element.value; + + return /\n/.test(ret) ? '' : ret; + }, + + findLastToken: function() { + var lastTokenPos = -1; + + for (var i=0; i lastTokenPos) + lastTokenPos = thisTokenPos; + } + return lastTokenPos; + } +} + +Ajax.Autocompleter = Class.create(); +Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } + +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// 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. + +Autocompleter.Local = Class.create(); +Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + 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) + "" + + elem.substr(entry.length) + "
    • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
    • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
    • "); + break; + } + } + + 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)) + return "
        " + ret.join('') + "
      "; + } + }, options || {}); + } +}); + +// AJAX in-place editor +// +// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +} + +Ajax.InPlaceEditor = Class.create(); +Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99"; +Ajax.InPlaceEditor.prototype = { + initialize: function(element, url, options) { + this.url = url; + this.element = $(element); + + this.options = Object.extend({ + paramName: "value", + okButton: true, + okText: "ok", + cancelLink: true, + cancelText: "cancel", + savingText: "Saving...", + clickToEditText: "Click to edit", + okText: "ok", + rows: 1, + onComplete: function(transport, element) { + new Effect.Highlight(element, {startcolor: this.options.highlightcolor}); + }, + onFailure: function(transport) { + alert("Error communicating with the server: " + transport.responseText.stripTags()); + }, + callback: function(form) { + return Form.serialize(form); + }, + handleLineBreaks: true, + loadingText: 'Loading...', + savingClassName: 'inplaceeditor-saving', + loadingClassName: 'inplaceeditor-loading', + formClassName: 'inplaceeditor-form', + highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor, + highlightendcolor: "#FFFFFF", + externalControl: null, + submitOnBlur: false, + ajaxOptions: {}, + evalScripts: false + }, options || {}); + + if(!this.options.formId && this.element.id) { + this.options.formId = this.element.id + "-inplaceeditor"; + if ($(this.options.formId)) { + // there's already a form with that name, don't specify an id + this.options.formId = null; + } + } + + if (this.options.externalControl) { + this.options.externalControl = $(this.options.externalControl); + } + + this.originalBackground = Element.getStyle(this.element, 'background-color'); + if (!this.originalBackground) { + this.originalBackground = "transparent"; + } + + this.element.title = this.options.clickToEditText; + + this.onclickListener = this.enterEditMode.bindAsEventListener(this); + this.mouseoverListener = this.enterHover.bindAsEventListener(this); + this.mouseoutListener = this.leaveHover.bindAsEventListener(this); + Event.observe(this.element, 'click', this.onclickListener); + Event.observe(this.element, 'mouseover', this.mouseoverListener); + Event.observe(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.observe(this.options.externalControl, 'click', this.onclickListener); + Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + }, + enterEditMode: function(evt) { + if (this.saving) return; + if (this.editing) return; + this.editing = true; + this.onEnterEditMode(); + if (this.options.externalControl) { + Element.hide(this.options.externalControl); + } + Element.hide(this.element); + this.createForm(); + this.element.parentNode.insertBefore(this.form, this.element); + if (!this.options.loadTextURL) Field.scrollFreeActivate(this.editField); + // stop the event to avoid a page refresh in Safari + if (evt) { + Event.stop(evt); + } + return false; + }, + createForm: function() { + this.form = document.createElement("form"); + this.form.id = this.options.formId; + Element.addClassName(this.form, this.options.formClassName) + this.form.onsubmit = this.onSubmit.bind(this); + + this.createEditField(); + + if (this.options.textarea) { + var br = document.createElement("br"); + this.form.appendChild(br); + } + + if (this.options.okButton) { + okButton = document.createElement("input"); + okButton.type = "submit"; + okButton.value = this.options.okText; + okButton.className = 'editor_ok_button'; + this.form.appendChild(okButton); + } + + if (this.options.cancelLink) { + cancelLink = document.createElement("a"); + cancelLink.href = "#"; + cancelLink.appendChild(document.createTextNode(this.options.cancelText)); + cancelLink.onclick = this.onclickCancel.bind(this); + cancelLink.className = 'editor_cancel'; + this.form.appendChild(cancelLink); + } + }, + hasHTMLLineBreaks: function(string) { + if (!this.options.handleLineBreaks) return false; + return string.match(/
      /i); + }, + convertHTMLLineBreaks: function(string) { + return string.replace(/
      /gi, "\n").replace(//gi, "\n").replace(/<\/p>/gi, "\n").replace(/

      /gi, ""); + }, + createEditField: function() { + var text; + if(this.options.loadTextURL) { + text = this.options.loadingText; + } else { + text = this.getText(); + } + + var obj = this; + + if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) { + this.options.textarea = false; + var textField = document.createElement("input"); + textField.obj = this; + textField.type = "text"; + textField.name = this.options.paramName; + textField.value = text; + textField.style.backgroundColor = this.options.highlightcolor; + textField.className = 'editor_field'; + var size = this.options.size || this.options.cols || 0; + if (size != 0) textField.size = size; + if (this.options.submitOnBlur) + textField.onblur = this.onSubmit.bind(this); + this.editField = textField; + } else { + this.options.textarea = true; + var textArea = document.createElement("textarea"); + textArea.obj = this; + textArea.name = this.options.paramName; + textArea.value = this.convertHTMLLineBreaks(text); + textArea.rows = this.options.rows; + textArea.cols = this.options.cols || 40; + textArea.className = 'editor_field'; + if (this.options.submitOnBlur) + textArea.onblur = this.onSubmit.bind(this); + this.editField = textArea; + } + + if(this.options.loadTextURL) { + this.loadExternalText(); + } + this.form.appendChild(this.editField); + }, + getText: function() { + return this.element.innerHTML; + }, + loadExternalText: function() { + Element.addClassName(this.form, this.options.loadingClassName); + this.editField.disabled = true; + new Ajax.Request( + this.options.loadTextURL, + Object.extend({ + asynchronous: true, + onComplete: this.onLoadedExternalText.bind(this) + }, this.options.ajaxOptions) + ); + }, + onLoadedExternalText: function(transport) { + Element.removeClassName(this.form, this.options.loadingClassName); + this.editField.disabled = false; + this.editField.value = transport.responseText.stripTags(); + Field.scrollFreeActivate(this.editField); + }, + onclickCancel: function() { + this.onComplete(); + this.leaveEditMode(); + return false; + }, + onFailure: function(transport) { + this.options.onFailure(transport); + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + this.oldInnerHTML = null; + } + return false; + }, + onSubmit: function() { + // onLoading resets these so we need to save them away for the Ajax call + var form = this.form; + var value = this.editField.value; + + // do this first, sometimes the ajax call returns before we get a chance to switch on Saving... + // which means this will actually switch on Saving... *after* we've left edit mode causing Saving... + // to be displayed indefinitely + this.onLoading(); + + if (this.options.evalScripts) { + new Ajax.Request( + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this), + asynchronous:true, + evalScripts:true + }, this.options.ajaxOptions)); + } else { + new Ajax.Updater( + { success: this.element, + // don't update on failure (this could be an option) + failure: null }, + this.url, Object.extend({ + parameters: this.options.callback(form, value), + onComplete: this.onComplete.bind(this), + onFailure: this.onFailure.bind(this) + }, this.options.ajaxOptions)); + } + // stop the event to avoid a page refresh in Safari + if (arguments.length > 1) { + Event.stop(arguments[0]); + } + return false; + }, + onLoading: function() { + this.saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + showSaving: function() { + this.oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + Element.addClassName(this.element, this.options.savingClassName); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + }, + removeForm: function() { + if(this.form) { + if (this.form.parentNode) Element.remove(this.form); + this.form = null; + } + }, + enterHover: function() { + if (this.saving) return; + this.element.style.backgroundColor = this.options.highlightcolor; + if (this.effect) { + this.effect.cancel(); + } + Element.addClassName(this.element, this.options.hoverClassName) + }, + leaveHover: function() { + if (this.options.backgroundColor) { + this.element.style.backgroundColor = this.oldBackground; + } + Element.removeClassName(this.element, this.options.hoverClassName) + if (this.saving) return; + this.effect = new Effect.Highlight(this.element, { + startcolor: this.options.highlightcolor, + endcolor: this.options.highlightendcolor, + restorecolor: this.originalBackground + }); + }, + leaveEditMode: function() { + Element.removeClassName(this.element, this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this.originalBackground; + Element.show(this.element); + if (this.options.externalControl) { + Element.show(this.options.externalControl); + } + this.editing = false; + this.saving = false; + this.oldInnerHTML = null; + this.onLeaveEditMode(); + }, + onComplete: function(transport) { + this.leaveEditMode(); + this.options.onComplete.bind(this)(transport, this.element); + }, + onEnterEditMode: function() {}, + onLeaveEditMode: function() {}, + dispose: function() { + if (this.oldInnerHTML) { + this.element.innerHTML = this.oldInnerHTML; + } + this.leaveEditMode(); + Event.stopObserving(this.element, 'click', this.onclickListener); + Event.stopObserving(this.element, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.element, 'mouseout', this.mouseoutListener); + if (this.options.externalControl) { + Event.stopObserving(this.options.externalControl, 'click', this.onclickListener); + Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener); + Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener); + } + } +}; + +Ajax.InPlaceCollectionEditor = Class.create(); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype); +Object.extend(Ajax.InPlaceCollectionEditor.prototype, { + createEditField: function() { + if (!this.cached_selectTag) { + var selectTag = document.createElement("select"); + var collection = this.options.collection || []; + var optionTag; + collection.each(function(e,i) { + optionTag = document.createElement("option"); + optionTag.value = (e instanceof Array) ? e[0] : e; + if((typeof this.options.value == 'undefined') && + ((e instanceof Array) ? this.element.innerHTML == e[1] : e == optionTag.value)) optionTag.selected = true; + if(this.options.value==optionTag.value) optionTag.selected = true; + optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e)); + selectTag.appendChild(optionTag); + }.bind(this)); + this.cached_selectTag = selectTag; + } + + this.editField = this.cached_selectTag; + if(this.options.loadTextURL) this.loadExternalText(); + this.form.appendChild(this.editField); + this.options.callback = function(form, value) { + return "value=" + encodeURIComponent(value); + } + } +}); + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create(); +Form.Element.DelayedObserver.prototype = { + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}; diff --git a/chapter13/adwords_reporter/public/javascripts/dragdrop.js b/chapter13/adwords_reporter/public/javascripts/dragdrop.js new file mode 100644 index 0000000..c71ddb8 --- /dev/null +++ b/chapter13/adwords_reporter/public/javascripts/dragdrop.js @@ -0,0 +1,942 @@ +// Copyright (c) 2005, 2006 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005, 2006 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// 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/ + +if(typeof Effect == 'undefined') + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || {}); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if((typeof containment == 'object') && + (containment.constructor == Array)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + 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; + } 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( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var affected = []; + + if(this.last_active) this.deactivate(this.last_active); + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) { + drop = Droppables.findDeepestChild(affected); + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + 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); + }, + + reset: function() { + 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) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + 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); + } 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)]; + // Mozilla-based browsers fire successive mousemove events with + // 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.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) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +} + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create(); +Draggable._dragging = {}; + +Draggable.prototype = { + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || typeof arguments[1].endeffect == 'undefined') + 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}); + } + }); + + var options = Object.extend(defaults, arguments[1] || {}); + + this.element = $(element); + + if(options.handle && (typeof options.handle == 'string')) + 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 + + this.delta = this.currentDelta(); + this.options = options; + 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(typeof Draggable._dragging[this.element] != 'undefined' && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if(src.tagName && ( + src.tagName=='INPUT' || + src.tagName=='SELECT' || + src.tagName=='OPTION' || + src.tagName=='BUTTON' || + src.tagName=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + 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.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); + 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); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + 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 ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.ghosting) { + Position.relativize(this.element); + Element.remove(this._clone); + this._clone = null; + } + + if(success) Droppables.fire(event, this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && typeof revert == 'function') revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + 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); + 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]) + }.bind(this)); + + if(this.options.snap) { + if(typeof this.options.snap == 'function') { + p = this.options.snap(p[0],p[1],this); + } else { + if(this.options.snap instanceof Array) { + p = p.map( function(v, i) { + return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this)) + } else { + p = p.map( function(v) { + return Math.round(v/this.options.snap)*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); + this.scrollInterval = null; + 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; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + 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) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight + } + } + return { top: T, left: L, width: W, height: H }; + } +} + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create(); +SortableObserver.prototype = { + initialize: function(element, observer) { + this.element = $(element); + 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)) + this.observer(this.element) + } +} + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: {}, + + _findRootElement: function(element) { + while (element.tagName != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + var s = Sortable.options(element); + + 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({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || {}); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // 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); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (this.findElements(element, options) || []).each( function(e) { + // handles are per-draggable + var handle = options.handle ? + $(e).down('.'+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); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + 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); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, 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); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + 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, + parent: parent, + 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) + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + 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); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || {}); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || {}); + + var nodeMap = {}; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var 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]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).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) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +} + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +} diff --git a/chapter13/adwords_reporter/public/javascripts/effects.js b/chapter13/adwords_reporter/public/javascripts/effects.js new file mode 100644 index 0000000..3b02eda --- /dev/null +++ b/chapter13/adwords_reporter/public/javascripts/effects.js @@ -0,0 +1,1088 @@ +// Copyright (c) 2005, 2006 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/ + +// 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)); +} + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +} + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +} + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0); + return element; +} + +Element.getOpacity = function(element){ + element = $(element); + var opacity; + if (opacity = element.getStyle('opacity')) + return parseFloat(opacity); + if (opacity = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(opacity[1]) return parseFloat(opacity[1]) / 100; + return 1.0; +} + +Element.setOpacity = function(element, value){ + element= $(element); + if (value == 1){ + element.setStyle({ opacity: + (/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? + 0.999999 : 1.0 }); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle({filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')}); + } else { + if(value < 0.00001) value = 0; + element.setStyle({opacity: value}); + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.setStyle( + { filter: element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')' }); + } + return element; +} + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +} + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +Array.prototype.call = function() { + var args = arguments; + this.each(function(f){ f.apply(this, args) }); +} + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + tagifyText: function(element) { + if(typeof Builder == 'undefined') + throw("Effect.tagifyText requires including script.aculo.us' builder.js library"); + + var tagifyStyle = 'position:relative'; + if(/MSIE/.test(navigator.userAgent) && !window.opera) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if(child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + Builder.node('span',{style: tagifyStyle}, + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if(((typeof element == 'object') || + (typeof element == 'function')) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || {}); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || {}); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +var Effect2 = Effect; // deprecated + +/* ------------- transitions ------------- */ + +Effect.Transitions = { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + 0.5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5; + }, + pulse: function(pos, pulses) { + pulses = pulses || 5; + return ( + Math.round((pos % (1/pulses)) * pulses) == 0 ? + ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) : + 1 - ((pos * pulses * 2) - Math.floor(pos * pulses * 2)) + ); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } +}; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(); +Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = (typeof effect.options.queue == 'string') ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + 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), 40); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if(this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + this.effects.invoke('loop', timePos); + } +}); + +Effect.Queues = { + instances: $H(), + get: function(queueName) { + if(typeof queueName != 'string') return queueName; + + if(!this.instances[queueName]) + this.instances[queueName] = new Effect.ScopedQueue(); + + return this.instances[queueName]; + } +} +Effect.Queue = Effect.Queues.get('global'); + +Effect.DefaultOptions = { + transition: Effect.Transitions.sinoidal, + duration: 1.0, // seconds + fps: 25.0, // max. 25fps due to Effect.Queue implementation + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' +} + +Effect.Base = function() {}; +Effect.Base.prototype = { + position: null, + start: function(options) { + this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {}); + this.currentFrame = 0; + this.state = 'idle'; + this.startOn = this.options.delay*1000; + this.finishOn = this.startOn + (this.options.duration*1000); + this.event('beforeStart'); + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).add(this); + }, + loop: function(timePos) { + if(timePos >= this.startOn) { + if(timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if(this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / (this.finishOn - this.startOn); + var frame = Math.round(pos * this.options.fps * this.options.duration); + if(frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + render: function(pos) { + if(this.state == 'idle') { + this.state = 'running'; + this.event('beforeSetup'); + if(this.setup) this.setup(); + this.event('afterSetup'); + } + if(this.state == 'running') { + if(this.options.transition) pos = this.options.transition(pos); + pos *= (this.options.to-this.options.from); + pos += this.options.from; + this.position = pos; + this.event('beforeUpdate'); + if(this.update) this.update(pos); + this.event('afterUpdate'); + } + }, + cancel: function() { + if(!this.options.sync) + Effect.Queues.get(typeof this.options.queue == 'string' ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if(this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + return '#'; + } +} + +Effect.Parallel = Class.create(); +Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if(effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Event = Class.create(); +Object.extend(Object.extend(Effect.Event.prototype, Effect.Base.prototype), { + initialize: function() { + var options = Object.extend({ + duration: 0 + }, arguments[0] || {}); + this.start(options); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(); +Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if(/MSIE/.test(navigator.userAgent) && !window.opera && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || {}); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(); +Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Bug in Opera: Opera returns the "real" position of a static element or + // relative element that does not have top/left explicitly set. + // ==> Always set top and left for position relative elements in your stylesheets + // (to 0 if you do not need them) + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if(this.options.mode == 'absolute') { + // absolute movement, so we need to calc deltaX and deltaY + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: Math.round(this.options.x * position + this.originalLeft) + 'px', + top: Math.round(this.options.y * position + this.originalTop) + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || {})); +}; + +Effect.Scale = Class.create(); +Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), { + initialize: function(element, percent) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or {} with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || {}); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = {}; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if(fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + 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]; + if(/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if(!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if(this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if(this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = {}; + if(this.options.scaleX) d.width = Math.round(width) + 'px'; + if(this.options.scaleY) d.height = Math.round(height) + 'px'; + if(this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if(this.elementPositioning == 'absolute') { + if(this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if(this.options.scaleY) d.top = -topd + 'px'; + if(this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(); +Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {}); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if(this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { + backgroundImage: this.element.getStyle('background-image') }; + this.element.setStyle({backgroundImage: 'none'}); + if(!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if(!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = Class.create(); +Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + this.start(arguments[1] || {}); + }, + setup: function() { + Position.prepare(); + var offsets = Position.cumulativeOffset(this.element); + if(this.options.offset) offsets[1] += this.options.offset; + var max = window.innerHeight ? + window.height - window.innerHeight : + document.body.scrollHeight - + (document.documentElement.clientHeight ? + document.documentElement.clientHeight : document.body.clientHeight); + this.scrollStart = Position.deltaY; + this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart; + }, + update: function(position) { + Position.prepare(); + window.scrollTo(Position.deltaX, + this.scrollStart + (position*this.delta)); + } +}); + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if(effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + }}, arguments[1] || {}); + return new Effect.Opacity(element,options); +} + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + 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(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + 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, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element) + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || {}) + ); +} + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || {}) + ); +} + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || {})); +} + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }) + } + }, arguments[1] || {})); +} + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ 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(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || {})); +} + +Effect.Shake = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}) }}) }}) }}) }}) }}); +} + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // 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, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if(window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || {}) + ); +} + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + restoreAfterFinish: true, + beforeStartInternal: function(effect) { + effect.element.makePositioned(); + 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' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({bottom: oldInnerBottom}); + effect.element.down().undoPositioned(); + } + }, arguments[1] || {}) + ); +} + +// 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, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +} + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ) + } + }); +} + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || {}); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + 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({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +} + +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, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +} + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || {})); +}; + +Effect.Morph = Class.create(); +Object.extend(Object.extend(Effect.Morph.prototype, Effect.Base.prototype), { + initialize: function(element) { + this.element = $(element); + if(!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: '' + }, arguments[1] || {}); + 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 ) + }); + } + this.transforms = this.options.style.parseStyle().map(function(property){ + var originalValue = this.element.getStyle(property[0]); + return $H({ + style: property[0], + originalValue: property[1].unit=='color' ? + parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: property[1].unit=='color' ? + parseColor(property[1].value) : property[1].value, + unit: property[1].unit + }); + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ) + }); + }, + update: function(position) { + var style = $H(), value = null; + this.transforms.each(function(transform){ + value = transform.unit=='color' ? + $R(0,2).inject('#',function(m,v,i){ + return m+(Math.round(transform.originalValue[i]+ + (transform.targetValue[i] - transform.originalValue[i])*position)).toColorPart() }) : + transform.originalValue + Math.round( + ((transform.targetValue - transform.originalValue) * position) * 1000)/1000 + transform.unit; + style[transform.style] = value; + }); + this.element.setStyle(style); + } +}); + +Effect.Transform = Class.create(); +Object.extend(Effect.Transform.prototype, { + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || {}; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + var data = $H(track).values().first(); + this.tracks.push($H({ + ids: $H(track).keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var elements = [$(track.ids) || $$(track.ids)].flatten(); + return elements.map(function(e){ return new track.effect(e, Object.extend({ sync:true }, track.options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = ['azimuth', 'backgroundAttachment', 'backgroundColor', 'backgroundImage', + 'backgroundPosition', 'backgroundRepeat', 'borderBottomColor', 'borderBottomStyle', + 'borderBottomWidth', 'borderCollapse', 'borderLeftColor', 'borderLeftStyle', 'borderLeftWidth', + 'borderRightColor', 'borderRightStyle', 'borderRightWidth', 'borderSpacing', 'borderTopColor', + 'borderTopStyle', 'borderTopWidth', 'bottom', 'captionSide', 'clear', 'clip', 'color', 'content', + 'counterIncrement', 'counterReset', 'cssFloat', 'cueAfter', 'cueBefore', 'cursor', 'direction', + 'display', 'elevation', 'emptyCells', 'fontFamily', 'fontSize', 'fontSizeAdjust', 'fontStretch', + 'fontStyle', 'fontVariant', 'fontWeight', 'height', 'left', 'letterSpacing', 'lineHeight', + 'listStyleImage', 'listStylePosition', 'listStyleType', 'marginBottom', 'marginLeft', 'marginRight', + 'marginTop', 'markerOffset', 'marks', 'maxHeight', 'maxWidth', 'minHeight', 'minWidth', 'opacity', + 'orphans', 'outlineColor', 'outlineOffset', 'outlineStyle', 'outlineWidth', 'overflowX', 'overflowY', + 'paddingBottom', 'paddingLeft', 'paddingRight', 'paddingTop', 'page', 'pageBreakAfter', 'pageBreakBefore', + 'pageBreakInside', 'pauseAfter', 'pauseBefore', 'pitch', 'pitchRange', 'position', 'quotes', + 'richness', 'right', 'size', 'speakHeader', 'speakNumeral', 'speakPunctuation', 'speechRate', 'stress', + 'tableLayout', 'textAlign', 'textDecoration', 'textIndent', 'textShadow', 'textTransform', 'top', + 'unicodeBidi', 'verticalAlign', 'visibility', 'voiceFamily', 'volume', 'whiteSpace', 'widows', + 'width', 'wordSpacing', 'zIndex']; + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.prototype.parseStyle = function(){ + var element = Element.extend(document.createElement('div')); + element.innerHTML = '

      '; + var style = element.down().style, styleRules = $H(); + + Element.CSS_PROPERTIES.each(function(property){ + if(style[property]) styleRules[property] = style[property]; + }); + + var result = $H(); + + styleRules.each(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if(value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if(Element.CSS_LENGTH.test(value)) + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/), + value = parseFloat(components[1]), unit = (components.length == 3) ? components[2] : null; + + result[property.underscore().dasherize()] = $H({ value:value, unit:unit }); + }.bind(this)); + + return result; +}; + +Element.morph = function(element, style) { + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || {})); + return element; +}; + +['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom', + 'collectTextNodes','collectTextNodesIgnoreClass','morph'].each( + function(f) { Element.Methods[f] = Element[f]; } +); + +Element.Methods.visualEffect = function(element, effect, options) { + s = effect.gsub(/_/, '-').camelize(); + effect_class = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[effect_class](element, options); + return $(element); +}; + +Element.addMethods(); \ No newline at end of file diff --git a/chapter13/adwords_reporter/public/javascripts/prototype.js b/chapter13/adwords_reporter/public/javascripts/prototype.js new file mode 100644 index 0000000..cedfeee --- /dev/null +++ b/chapter13/adwords_reporter/public/javascripts/prototype.js @@ -0,0 +1,2514 @@ +/* Prototype JavaScript framework, version 1.5.0 + * (c) 2005-2007 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://prototype.conio.net/ + * +/*--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.5.0', + BrowserFeatures: { + XPath: !!document.evaluate + }, + + ScriptFragment: '(?:)((\n|\r|.)*?)(?:<\/script>)', + emptyFunction: function() {}, + K: function(x) { return x } +} + +var Class = { + create: function() { + return function() { + this.initialize.apply(this, arguments); + } + } +} + +var Abstract = new Object(); + +Object.extend = function(destination, source) { + for (var property in source) { + destination[property] = source[property]; + } + return destination; +} + +Object.extend(Object, { + inspect: function(object) { + try { + if (object === undefined) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : object.toString(); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({}, object); + } +}); + +Function.prototype.bind = function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } +} + +Function.prototype.bindAsEventListener = function(object) { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [( event || window.event)].concat(args).concat($A(arguments))); + } +} + +Object.extend(Number.prototype, { + toColorPart: function() { + var digits = this.toString(16); + if (this < 16) return '0' + digits; + return digits; + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator) { + $R(0, this, true).each(iterator); + return this; + } +}); + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) {} + } + + return returnValue; + } +} + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create(); +PeriodicalExecuter.prototype = { + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.callback(this); + } finally { + this.currentlyExecuting = false; + } + } + } +} +String.interpret = function(value){ + return value == null ? '' : String(value); +} + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = count === undefined ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return this; + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = truncation === undefined ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : this; + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var div = document.createElement('div'); + var text = document.createTextNode(this); + div.appendChild(text); + return div.innerHTML; + }, + + unescapeHTML: function() { + var div = document.createElement('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) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return {}; + + return match[1].split(separator || '&').inject({}, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var name = decodeURIComponent(pair[0]); + var value = pair[1] ? decodeURIComponent(pair[1]) : undefined; + + if (hash[name] !== undefined) { + if (hash[name].constructor != Array) + hash[name] = [hash[name]]; + if (value) hash[name].push(value); + } + else hash[name] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + 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; + }, + + capitalize: function(){ + 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(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.replace(/\\/g, '\\\\'); + if (useDoubleQuotes) + return '"' + escapedString.replace(/"/g, '\\"') + '"'; + else + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (typeof replacement == 'function') return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +} + +String.prototype.parseQuery = String.prototype.toQueryParams; + +var Template = Class.create(); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; +Template.prototype = { + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + return this.template.gsub(this.pattern, function(match) { + var before = match[1]; + if (before == '\\') return match[2]; + return before + String.interpret(object[match[3]]); + }); + } +} + +var $break = new Object(); +var $continue = new Object(); + +var Enumerable = { + each: function(iterator) { + var index = 0; + try { + this._each(function(value) { + try { + iterator(value, index++); + } catch (e) { + if (e != $continue) throw e; + } + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator) { + var index = -number, slices = [], array = this.toArray(); + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.map(iterator); + }, + + all: function(iterator) { + var result = true; + this.each(function(value, index) { + result = result && !!(iterator || Prototype.K)(value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator) { + var result = false; + this.each(function(value, index) { + if (result = !!(iterator || Prototype.K)(value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator) { + var results = []; + this.each(function(value, index) { + results.push((iterator || Prototype.K)(value, index)); + }); + return results; + }, + + detect: function(iterator) { + var result; + this.each(function(value, index) { + if (iterator(value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator) { + var results = []; + this.each(function(value, index) { + if (iterator(value, index)) + results.push(value); + }); + return results; + }, + + grep: function(pattern, iterator) { + var results = []; + this.each(function(value, index) { + var stringValue = value.toString(); + if (stringValue.match(pattern)) + results.push((iterator || Prototype.K)(value, index)); + }) + return results; + }, + + include: function(object) { + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = fillWith === undefined ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator) { + this.each(function(value, index) { + memo = iterator(memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator) { + var result; + this.each(function(value, index) { + value = (iterator || Prototype.K)(value, index); + if (result == undefined || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator) { + var trues = [], falses = []; + this.each(function(value, index) { + ((iterator || Prototype.K)(value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value, index) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator) { + var results = []; + this.each(function(value, index) { + if (!iterator(value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator) { + return this.map(function(value, index) { + return {value: value, criteria: iterator(value, index)}; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (typeof args.last() == 'function') + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +} + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray +}); +var $A = Array.from = function(iterable) { + if (!iterable) return []; + if (iterable.toArray) { + return iterable.toArray(); + } else { + var results = []; + for (var i = 0, length = iterable.length; i < length; i++) + results.push(iterable[i]); + return results; + } +} + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) + Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(value && value.constructor == Array ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + indexOf: function(object) { + for (var i = 0, length = this.length; i < length; i++) + if (this[i] == object) return i; + return -1; + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function() { + return this.inject([], function(array, value) { + return array.include(value) ? array : array.concat([value]); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + } +}); + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string){ + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if(window.opera){ + Array.prototype.concat = function(){ + var array = []; + for(var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for(var i = 0, length = arguments.length; i < length; i++) { + if(arguments[i].constructor == Array) { + for(var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + } +} +var Hash = function(obj) { + Object.extend(this, obj || {}); +}; + +Object.extend(Hash, { + toQueryString: function(obj) { + var parts = []; + + this.prototype._each.call(obj, function(pair) { + if (!pair.key) return; + + if (pair.value && pair.value.constructor == Array) { + var values = pair.value.compact(); + if (values.length < 2) pair.value = values.reduce(); + else { + key = encodeURIComponent(pair.key); + values.each(function(value) { + value = value != undefined ? encodeURIComponent(value) : ''; + parts.push(key + '=' + encodeURIComponent(value)); + }); + return; + } + } + if (pair.value == undefined) pair[1] = ''; + parts.push(pair.map(encodeURIComponent).join('=')); + }); + + return parts.join('&'); + } +}); + +Object.extend(Hash.prototype, Enumerable); +Object.extend(Hash.prototype, { + _each: function(iterator) { + for (var key in this) { + var value = this[key]; + if (value && value == Hash.prototype[key]) continue; + + var pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + merge: function(hash) { + return $H(hash).inject(this, function(mergedHash, pair) { + mergedHash[pair.key] = pair.value; + return mergedHash; + }); + }, + + remove: function() { + var result; + for(var i = 0, length = arguments.length; i < length; i++) { + var value = this[arguments[i]]; + if (value !== undefined){ + if (result === undefined) result = value; + else { + if (result.constructor != Array) result = [result]; + result.push(value) + } + } + delete this[arguments[i]]; + } + return result; + }, + + toQueryString: function() { + return Hash.toQueryString(this); + }, + + inspect: function() { + return '#'; + } +}); + +function $H(object) { + if (object && object.constructor == Hash) return object; + return new Hash(object); +}; +ObjectRange = Class.create(); +Object.extend(ObjectRange.prototype, Enumerable); +Object.extend(ObjectRange.prototype, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +} + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +} + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (typeof responder[callback] == 'function') { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) {} + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { + Ajax.activeRequestCount++; + }, + onComplete: function() { + Ajax.activeRequestCount--; + } +}); + +Ajax.Base = function() {}; +Ajax.Base.prototype = { + setOptions: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '' + } + Object.extend(this.options, options || {}); + + this.options.method = this.options.method.toLowerCase(); + if (typeof this.options.parameters == 'string') + this.options.parameters = this.options.parameters.toQueryParams(); + } +} + +Ajax.Request = Class.create(); +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Request.prototype = Object.extend(new Ajax.Base(), { + _complete: false, + + initialize: function(url, options) { + this.transport = Ajax.getTransport(); + this.setOptions(options); + this.request(url); + }, + + request: function(url) { + this.url = url; + var params = this.options.parameters, method = this.options.method; + + if (!['get', 'post'].include(method)) { + // simulate other verbs over post + params['_method'] = method; + method = 'post'; + } + + params = Hash.toQueryString(params); + if (params && /Konqueror|Safari|KHTML/.test(navigator.userAgent)) params += '&_=' + + // when GET, append parameters to URL + if (method == 'get' && params) + this.url += (this.url.indexOf('?') > -1 ? '&' : '?') + params; + + try { + Ajax.Responders.dispatch('onCreate', this, this.transport); + + this.transport.open(method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) + setTimeout(function() { this.respondToReadyState(1) }.bind(this), 10); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + var body = method == 'post' ? (this.options.postBody || params) : null; + + this.transport.send(body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.options.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (typeof extras.push == 'function') + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + return !this.transport.status + || (this.transport.status >= 200 && this.transport.status < 300); + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState]; + var transport = this.transport, json = this.evalJSON(); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + this.transport.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(transport, json); + } catch (e) { + this.dispatchException(e); + } + + if ((this.getHeader('Content-type') || 'text/javascript').strip(). + match(/^(text|application)\/(x-)?(java|ecma)script(;.*)?$/i)) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(transport, json); + Ajax.Responders.dispatch('on' + state, this, transport, json); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name); + } catch (e) { return null } + }, + + evalJSON: function() { + try { + var json = this.getHeader('X-JSON'); + return json ? eval('(' + json + ')') : null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval(this.transport.responseText); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Updater = Class.create(); + +Object.extend(Object.extend(Ajax.Updater.prototype, Ajax.Request.prototype), { + initialize: function(container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + } + + this.transport = Ajax.getTransport(); + this.setOptions(options); + + var onComplete = this.options.onComplete || Prototype.emptyFunction; + this.options.onComplete = (function(transport, param) { + this.updateContent(); + onComplete(transport, param); + }).bind(this); + + this.request(url); + }, + + updateContent: function() { + var receiver = this.container[this.success() ? 'success' : 'failure']; + var response = this.transport.responseText; + + if (!this.options.evalScripts) response = response.stripScripts(); + + if (receiver = $(receiver)) { + if (this.options.insertion) + new this.options.insertion(receiver, response); + else + receiver.update(response); + } + + if (this.success()) { + if (this.onComplete) + setTimeout(this.onComplete.bind(this), 10); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(); +Ajax.PeriodicalUpdater.prototype = Object.extend(new Ajax.Base(), { + initialize: function(container, url, options) { + this.setOptions(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = {}; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(request) { + if (this.options.decay) { + this.decay = (request.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = request.responseText; + } + this.timer = setTimeout(this.onTimerEvent.bind(this), + this.decay * this.frequency * 1000); + }, + + onTimerEvent: function() { + 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++) + elements.push($(arguments[i])); + return elements; + } + if (typeof element == 'string') + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(query.snapshotItem(i)); + return results; + }; +} + +document.getElementsByClassName = function(className, parentElement) { + if (Prototype.BrowserFeatures.XPath) { + var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; + return document._getElementsByXPath(q, parentElement); + } else { + var children = ($(parentElement) || document.body).getElementsByTagName('*'); + var elements = [], child; + for (var i = 0, length = children.length; i < length; i++) { + child = children[i]; + if (Element.hasClassName(child, className)) + elements.push(Element.extend(child)); + } + return elements; + } +}; + +/*--------------------------------------------------------------------------*/ + +if (!window.Element) + var Element = new Object(); + +Element.extend = function(element) { + if (!element || _nativeExtensions || element.nodeType == 3) return element; + + if (!element._extended && element.tagName && element != window) { + var methods = Object.clone(Element.Methods), cache = Element.extend.cache; + + if (element.tagName == 'FORM') + Object.extend(methods, Form.Methods); + if (['INPUT', 'TEXTAREA', 'SELECT'].include(element.tagName)) + Object.extend(methods, Form.Element.Methods); + + Object.extend(methods, Element.Methods.Simulated); + + for (var property in methods) { + var value = methods[property]; + if (typeof value == 'function' && !(property in element)) + element[property] = cache.findOrStore(value); + } + } + + element._extended = true; + return element; +}; + +Element.extend.cache = { + findOrStore: function(value) { + return this[value] = this[value] || function() { + return value.apply(null, [this].concat($A(arguments))); + } + } +}; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + $(element).style.display = 'none'; + return element; + }, + + show: function(element) { + $(element).style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, html) { + html = typeof html == 'undefined' ? '' : html.toString(); + $(element).innerHTML = html.stripScripts(); + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + replace: function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + if (element.outerHTML) { + element.outerHTML = html.stripScripts(); + } else { + var range = element.ownerDocument.createRange(); + range.selectNodeContents(element); + element.parentNode.replaceChild( + range.createContextualFragment(html.stripScripts()), element); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + }, + + inspect: function(element) { + 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(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $A($(element).getElementsByTagName('*')); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (typeof selector == 'string') + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + return Selector.findElement($(element).ancestors(), expression, index); + }, + + down: function(element, expression, index) { + return Selector.findElement($(element).descendants(), expression, index); + }, + + previous: function(element, expression, index) { + return Selector.findElement($(element).previousSiblings(), expression, index); + }, + + next: function(element, expression, index) { + return Selector.findElement($(element).nextSiblings(), expression, index); + }, + + getElementsBySelector: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + getElementsByClassName: function(element, className) { + return document.getElementsByClassName(className, element); + }, + + readAttribute: function(element, name) { + element = $(element); + if (document.all && !window.opera) { + var t = Element._attributeTranslations; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + var attribute = element.attributes[name]; + if(attribute) return attribute.nodeValue; + } + return element.getAttribute(name); + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + if (elementClassName.length == 0) return false; + if (elementClassName == className || + elementClassName.match(new RegExp("(^|\\s)" + className + "(\\s|$)"))) + return true; + return false; + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).add(className); + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element).remove(className); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + Element.classNames(element)[element.hasClassName(className) ? 'remove' : 'add'](className); + return element; + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.match(/^\s*$/); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + while (element = element.parentNode) + if (element == ancestor) return true; + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = Position.cumulativeOffset(element); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + if (['float','cssFloat'].include(style)) + style = (typeof element.style.styleFloat != 'undefined' ? 'styleFloat' : 'cssFloat'); + style = style.camelize(); + var value = element.style[style]; + if (!value) { + if (document.defaultView && document.defaultView.getComputedStyle) { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } else if (element.currentStyle) { + value = element.currentStyle[style]; + } + } + + if((value == 'auto') && ['width','height'].include(style) && (element.getStyle('display') != 'none')) + value = element['offset'+style.capitalize()] + 'px'; + + if (window.opera && ['left', 'top', 'right', 'bottom'].include(style)) + if (Element.getStyle(element, 'position') == 'static') value = 'auto'; + if(style == 'opacity') { + if(value) return parseFloat(value); + if(value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if(value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + return value == 'auto' ? null : value; + }, + + setStyle: function(element, style) { + element = $(element); + for (var name in style) { + var value = style[name]; + if(name == 'opacity') { + if (value == 1) { + value = (/Gecko/.test(navigator.userAgent) && + !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ? 0.999999 : 1.0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else if(value == '') { + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,''); + } else { + if(value < 0.00001) value = 0; + if(/MSIE/.test(navigator.userAgent) && !window.opera) + element.style.filter = element.getStyle('filter').replace(/alpha\([^\)]*\)/gi,'') + + 'alpha(opacity='+value*100+')'; + } + } else if(['float','cssFloat'].include(name)) name = (typeof element.style.styleFloat != 'undefined') ? 'styleFloat' : 'cssFloat'; + element.style[name.camelize()] = value; + } + 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) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = element.style.overflow || 'auto'; + if ((Element.getStyle(element, 'overflow') || 'visible') != 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + } +}; + +Object.extend(Element.Methods, {childOf: Element.Methods.descendantOf}); + +Element._attributeTranslations = {}; + +Element._attributeTranslations.names = { + colspan: "colSpan", + rowspan: "rowSpan", + valign: "vAlign", + datetime: "dateTime", + accesskey: "accessKey", + tabindex: "tabIndex", + enctype: "encType", + maxlength: "maxLength", + readonly: "readOnly", + longdesc: "longDesc" +}; + +Element._attributeTranslations.values = { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + + title: function(element) { + var node = element.getAttributeNode('title'); + return node.specified ? node.nodeValue : null; + } +}; + +Object.extend(Element._attributeTranslations.values, { + href: Element._attributeTranslations.values._getAttr, + src: Element._attributeTranslations.values._getAttr, + disabled: Element._attributeTranslations.values._flag, + checked: Element._attributeTranslations.values._flag, + readonly: Element._attributeTranslations.values._flag, + multiple: Element._attributeTranslations.values._flag +}); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + var t = Element._attributeTranslations; + attribute = t.names[attribute] || attribute; + return $(element).getAttributeNode(attribute).specified; + } +}; + +// IE is missing .innerHTML support for TABLE-related elements +if (document.all && !window.opera){ + Element.Methods.update = function(element, html) { + element = $(element); + html = typeof html == 'undefined' ? '' : html.toString(); + var tagName = element.tagName.toUpperCase(); + if (['THEAD','TBODY','TR','TD'].include(tagName)) { + var div = document.createElement('div'); + switch (tagName) { + case 'THEAD': + case 'TBODY': + div.innerHTML = '' + html.stripScripts() + '
      '; + depth = 2; + break; + case 'TR': + div.innerHTML = '' + html.stripScripts() + '
      '; + depth = 3; + break; + case 'TD': + div.innerHTML = '
      ' + html.stripScripts() + '
      '; + depth = 4; + } + $A(element.childNodes).each(function(node){ + element.removeChild(node) + }); + depth.times(function(){ div = div.firstChild }); + + $A(div.childNodes).each( + function(node){ element.appendChild(node) }); + } else { + element.innerHTML = html.stripScripts(); + } + setTimeout(function() {html.evalScripts()}, 10); + return element; + } +}; + +Object.extend(Element, Element.Methods); + +var _nativeExtensions = false; + +if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + ['', 'Form', 'Input', 'TextArea', 'Select'].each(function(tag) { + var className = 'HTML' + tag + 'Element'; + if(window[className]) return; + var klass = window[className] = {}; + klass.prototype = document.createElement(tag ? tag.toLowerCase() : 'div').__proto__; + }); + +Element.addMethods = function(methods) { + Object.extend(Element.Methods, methods || {}); + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + var cache = Element.extend.cache; + for (var property in methods) { + var value = methods[property]; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = cache.findOrStore(value); + } + } + + if (typeof HTMLElement != 'undefined') { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + copy(Form.Methods, HTMLFormElement.prototype); + [HTMLInputElement, HTMLTextAreaElement, HTMLSelectElement].each(function(klass) { + copy(Form.Element.Methods, klass.prototype); + }); + _nativeExtensions = true; + } +} + +var Toggle = new Object(); +Toggle.display = Element.toggle; + +/*--------------------------------------------------------------------------*/ + +Abstract.Insertion = function(adjacency) { + this.adjacency = adjacency; +} + +Abstract.Insertion.prototype = { + initialize: function(element, content) { + this.element = $(element); + this.content = content.stripScripts(); + + if (this.adjacency && this.element.insertAdjacentHTML) { + try { + this.element.insertAdjacentHTML(this.adjacency, this.content); + } catch (e) { + var tagName = this.element.tagName.toUpperCase(); + if (['TBODY', 'TR'].include(tagName)) { + this.insertContent(this.contentFromAnonymousTable()); + } else { + throw e; + } + } + } else { + this.range = this.element.ownerDocument.createRange(); + if (this.initializeRange) this.initializeRange(); + this.insertContent([this.range.createContextualFragment(this.content)]); + } + + setTimeout(function() {content.evalScripts()}, 10); + }, + + contentFromAnonymousTable: function() { + var div = document.createElement('div'); + div.innerHTML = '' + this.content + '
      '; + return $A(div.childNodes[0].childNodes[0].childNodes); + } +} + +var Insertion = new Object(); + +Insertion.Before = Class.create(); +Insertion.Before.prototype = Object.extend(new Abstract.Insertion('beforeBegin'), { + initializeRange: function() { + this.range.setStartBefore(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, this.element); + }).bind(this)); + } +}); + +Insertion.Top = Class.create(); +Insertion.Top.prototype = Object.extend(new Abstract.Insertion('afterBegin'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(true); + }, + + insertContent: function(fragments) { + fragments.reverse(false).each((function(fragment) { + this.element.insertBefore(fragment, this.element.firstChild); + }).bind(this)); + } +}); + +Insertion.Bottom = Class.create(); +Insertion.Bottom.prototype = Object.extend(new Abstract.Insertion('beforeEnd'), { + initializeRange: function() { + this.range.selectNodeContents(this.element); + this.range.collapse(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.appendChild(fragment); + }).bind(this)); + } +}); + +Insertion.After = Class.create(); +Insertion.After.prototype = Object.extend(new Abstract.Insertion('afterEnd'), { + initializeRange: function() { + this.range.setStartAfter(this.element); + }, + + insertContent: function(fragments) { + fragments.each((function(fragment) { + this.element.parentNode.insertBefore(fragment, + this.element.nextSibling); + }).bind(this)); + } +}); + +/*--------------------------------------------------------------------------*/ + +Element.ClassNames = Class.create(); +Element.ClassNames.prototype = { + initialize: function(element) { + this.element = $(element); + }, + + _each: function(iterator) { + this.element.className.split(/\s+/).select(function(name) { + return name.length > 0; + })._each(iterator); + }, + + set: function(className) { + this.element.className = className; + }, + + add: function(classNameToAdd) { + if (this.include(classNameToAdd)) return; + this.set($A(this).concat(classNameToAdd).join(' ')); + }, + + remove: function(classNameToRemove) { + if (!this.include(classNameToRemove)) return; + this.set($A(this).without(classNameToRemove).join(' ')); + }, + + toString: function() { + return $A(this).join(' '); + } +}; + +Object.extend(Element.ClassNames.prototype, Enumerable); +var Selector = Class.create(); +Selector.prototype = { + initialize: function(expression) { + this.params = {classNames: []}; + this.expression = expression.toString().strip(); + this.parseExpression(); + this.compileMatcher(); + }, + + parseExpression: function() { + function abort(message) { throw 'Parse error in selector: ' + message; } + + if (this.expression == '') abort('empty expression'); + + var params = this.params, expr = this.expression, match, modifier, clause, rest; + while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { + params.attributes = params.attributes || []; + params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); + expr = match[1]; + } + + if (expr == '*') return this.params.wildcard = true; + + while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { + modifier = match[1], clause = match[2], rest = match[3]; + switch (modifier) { + case '#': params.id = clause; break; + case '.': params.classNames.push(clause); break; + case '': + case undefined: params.tagName = clause.toUpperCase(); break; + default: abort(expr.inspect()); + } + expr = rest; + } + + if (expr.length > 0) abort(expr.inspect()); + }, + + buildMatchExpression: function() { + var params = this.params, conditions = [], clause; + + if (params.wildcard) + conditions.push('true'); + if (clause = params.id) + conditions.push('element.readAttribute("id") == ' + clause.inspect()); + if (clause = params.tagName) + conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); + if ((clause = params.classNames).length > 0) + for (var i = 0, length = clause.length; i < length; i++) + conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); + if (clause = params.attributes) { + clause.each(function(attribute) { + var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; + var splitValueBy = function(delimiter) { + return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; + } + + switch (attribute.operator) { + case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; + case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; + case '|=': conditions.push( + splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() + ); break; + case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; + case '': + case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; + default: throw 'Unknown operator ' + attribute.operator + ' in selector'; + } + }); + } + + return conditions.join(' && '); + }, + + compileMatcher: function() { + this.match = new Function('element', 'if (!element.tagName) return false; \ + element = $(element); \ + return ' + this.buildMatchExpression()); + }, + + findElements: function(scope) { + var element; + + if (element = $(this.params.id)) + if (this.match(element)) + if (!scope || Element.childOf(element, scope)) + return [element]; + + scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); + + var results = []; + for (var i = 0, length = scope.length; i < length; i++) + if (this.match(element = scope[i])) + results.push(Element.extend(element)); + + return results; + }, + + toString: function() { + return this.expression; + } +} + +Object.extend(Selector, { + matchElements: function(elements, expression) { + var selector = new Selector(expression); + return elements.select(selector.match.bind(selector)).map(Element.extend); + }, + + findElement: function(elements, expression, index) { + if (typeof expression == 'number') index = expression, expression = false; + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + return expressions.map(function(expression) { + return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { + var selector = new Selector(expr); + return results.inject([], function(elements, result) { + return elements.concat(selector.findElements(result || element)); + }); + }); + }).flatten(); + } +}); + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, getHash) { + var data = elements.inject({}, function(result, element) { + if (!element.disabled && element.name) { + var key = element.name, value = $(element).getValue(); + if (value != undefined) { + if (result[key]) { + if (result[key].constructor != Array) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return getHash ? data : Hash.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, getHash) { + return Form.serializeElements(Form.getElements(form), getHash); + }, + + 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; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.blur(); + element.disabled = 'true'; + }); + return form; + }, + + enable: function(form) { + form = $(form); + form.getElements().each(function(element) { + element.disabled = ''; + }); + return form; + }, + + findFirstElement: function(form) { + return $(form).getElements().find(function(element) { + return element.type != 'hidden' && !element.disabled && + ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + } +} + +Object.extend(Form, Form.Methods); + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +} + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = {}; + pair[element.name] = value; + return Hash.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + element.focus(); + if (element.select && ( element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type) ) ) + element.select(); + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.blur(); + element.disabled = false; + return element; + } +} + +Object.extend(Form.Element, Form.Element.Methods); +var Field = Form.Element; +var $F = Form.Element.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element); + default: + return Form.Element.Serializers.textarea(element); + } + }, + + inputSelector: function(element) { + return element.checked ? element.value : null; + }, + + textarea: function(element) { + return element.value; + }, + + select: function(element) { + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +} + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = function() {} +Abstract.TimedObserver.prototype = { + initialize: function(element, frequency, callback) { + this.frequency = frequency; + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + this.registerCallback(); + }, + + registerCallback: function() { + setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + onTimerEvent: function() { + var value = this.getValue(); + var changed = ('string' == typeof this.lastValue && 'string' == typeof value + ? this.lastValue != value : String(this.lastValue) != String(value)); + if (changed) { + this.callback(this.element, value); + this.lastValue = value; + } + } +} + +Form.Element.Observer = Class.create(); +Form.Element.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(); +Form.Observer.prototype = Object.extend(new Abstract.TimedObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = function() {} +Abstract.EventObserver.prototype = { + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback.bind(this)); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +} + +Form.Element.EventObserver = Class.create(); +Form.Element.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(); +Form.EventObserver.prototype = Object.extend(new Abstract.EventObserver(), { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) { + var Event = new Object(); +} + +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, + + element: function(event) { + return event.target || event.srcElement; + }, + + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); + }, + + pointerX: function(event) { + return event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)); + }, + + pointerY: function(event) { + return event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)); + }, + + stop: function(event) { + if (event.preventDefault) { + event.preventDefault(); + event.stopPropagation(); + } else { + event.returnValue = false; + event.cancelBubble = true; + } + }, + + // find the first node with the given tagName, starting from the + // node the event was triggered on; traverses the DOM upwards + findElement: function(event, tagName) { + var element = Event.element(event); + while (element.parentNode && (!element.tagName || + (element.tagName.toUpperCase() != tagName.toUpperCase()))) + element = element.parentNode; + return element; + }, + + observers: false, + + _observeAndCache: function(element, name, observer, useCapture) { + if (!this.observers) this.observers = []; + if (element.addEventListener) { + this.observers.push([element, name, observer, useCapture]); + element.addEventListener(name, observer, useCapture); + } else if (element.attachEvent) { + this.observers.push([element, name, observer, useCapture]); + element.attachEvent('on' + name, observer); + } + }, + + unloadCache: function() { + if (!Event.observers) return; + for (var i = 0, length = Event.observers.length; i < length; i++) { + Event.stopObserving.apply(this, Event.observers[i]); + Event.observers[i][0] = null; + } + Event.observers = false; + }, + + observe: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.attachEvent)) + name = 'keydown'; + + Event._observeAndCache(element, name, observer, useCapture); + }, + + stopObserving: function(element, name, observer, useCapture) { + element = $(element); + useCapture = useCapture || false; + + if (name == 'keypress' && + (navigator.appVersion.match(/Konqueror|Safari|KHTML/) + || element.detachEvent)) + name = 'keydown'; + + if (element.removeEventListener) { + element.removeEventListener(name, observer, useCapture); + } else if (element.detachEvent) { + try { + element.detachEvent('on' + name, observer); + } catch (e) {} + } + } +}); + +/* prevent memory leaks in IE */ +if (navigator.appVersion.match(/\bMSIE\b/)) + Event.observe(window, 'unload', Event.unloadCache, false); +var Position = { + // set to true if needed, warning: firefox performance problems + // NOT neeeded for page scrolling, only if draggable contained in + // scrollable elements + includeScrollOffsets: false, + + // must be called before calling withinIncludingScrolloffset, every time the + // page is scrolled + prepare: function() { + this.deltaX = window.pageXOffset + || document.documentElement.scrollLeft + || document.body.scrollLeft + || 0; + this.deltaY = window.pageYOffset + || document.documentElement.scrollTop + || document.body.scrollTop + || 0; + }, + + realOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return [valueL, valueT]; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return [valueL, valueT]; + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if(element.tagName=='BODY') break; + var p = Element.getStyle(element, 'position'); + if (p == 'relative' || p == 'absolute') break; + } + } while (element); + return [valueL, valueT]; + }, + + offsetParent: function(element) { + if (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; + + return document.body; + }, + + // caches x/y coordinate pair to use with overlap + within: function(element, x, y) { + if (this.includeScrollOffsets) + return this.withinIncludingScrolloffsets(element, x, y); + this.xcomp = x; + this.ycomp = y; + this.offset = this.cumulativeOffset(element); + + return (y >= this.offset[1] && + y < this.offset[1] + element.offsetHeight && + x >= this.offset[0] && + x < this.offset[0] + element.offsetWidth); + }, + + withinIncludingScrolloffsets: function(element, x, y) { + var offsetcache = this.realOffset(element); + + this.xcomp = x + offsetcache[0] - this.deltaX; + this.ycomp = y + offsetcache[1] - this.deltaY; + this.offset = this.cumulativeOffset(element); + + return (this.ycomp >= this.offset[1] && + this.ycomp < this.offset[1] + element.offsetHeight && + this.xcomp >= this.offset[0] && + this.xcomp < this.offset[0] + element.offsetWidth); + }, + + // within must be called directly before + overlap: function(mode, element) { + if (!mode) return 0; + if (mode == 'vertical') + return ((this.offset[1] + element.offsetHeight) - this.ycomp) / + element.offsetHeight; + if (mode == 'horizontal') + return ((this.offset[0] + element.offsetWidth) - this.xcomp) / + element.offsetWidth; + }, + + page: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent==document.body) + if (Element.getStyle(element,'position')=='absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!window.opera || element.tagName=='BODY') { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return [valueL, valueT]; + }, + + clone: function(source, target) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || {}) + + // find page position of source + source = $(source); + var p = Position.page(source); + + // find coordinate system to use + target = $(target); + 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(target,'position') == 'absolute') { + parent = Position.offsetParent(target); + delta = Position.page(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) target.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if(options.setTop) target.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if(options.setWidth) target.style.width = source.offsetWidth + 'px'; + if(options.setHeight) target.style.height = source.offsetHeight + 'px'; + }, + + absolutize: function(element) { + element = $(element); + if (element.style.position == 'absolute') return; + Position.prepare(); + + var offsets = Position.positionedOffset(element); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + }, + + relativize: function(element) { + element = $(element); + if (element.style.position == 'relative') return; + Position.prepare(); + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + } +} + +// Safari returns margins on body which is incorrect if the child is absolutely +// positioned. For performance reasons, redefine Position.cumulativeOffset for +// KHTML/WebKit only. +if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) { + Position.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return [valueL, valueT]; + } +} + +Element.addMethods(); \ No newline at end of file diff --git a/chapter13/adwords_reporter/public/robots.txt b/chapter13/adwords_reporter/public/robots.txt new file mode 100644 index 0000000..4ab9e89 --- /dev/null +++ b/chapter13/adwords_reporter/public/robots.txt @@ -0,0 +1 @@ +# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file \ No newline at end of file diff --git a/chapter13/adwords_reporter/script/about b/chapter13/adwords_reporter/script/about new file mode 100644 index 0000000..7b07d46 --- /dev/null +++ b/chapter13/adwords_reporter/script/about @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/about' \ No newline at end of file diff --git a/chapter13/adwords_reporter/script/breakpointer b/chapter13/adwords_reporter/script/breakpointer new file mode 100644 index 0000000..64af76e --- /dev/null +++ b/chapter13/adwords_reporter/script/breakpointer @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/breakpointer' \ No newline at end of file diff --git a/chapter13/adwords_reporter/script/console b/chapter13/adwords_reporter/script/console new file mode 100644 index 0000000..42f28f7 --- /dev/null +++ b/chapter13/adwords_reporter/script/console @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/console' \ No newline at end of file diff --git a/chapter13/adwords_reporter/script/destroy b/chapter13/adwords_reporter/script/destroy new file mode 100644 index 0000000..fa0e6fc --- /dev/null +++ b/chapter13/adwords_reporter/script/destroy @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/destroy' \ No newline at end of file diff --git a/chapter13/adwords_reporter/script/generate b/chapter13/adwords_reporter/script/generate new file mode 100644 index 0000000..ef976e0 --- /dev/null +++ b/chapter13/adwords_reporter/script/generate @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/generate' \ No newline at end of file diff --git a/chapter13/adwords_reporter/script/performance/benchmarker b/chapter13/adwords_reporter/script/performance/benchmarker new file mode 100644 index 0000000..c842d35 --- /dev/null +++ b/chapter13/adwords_reporter/script/performance/benchmarker @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/benchmarker' diff --git a/chapter13/adwords_reporter/script/performance/profiler b/chapter13/adwords_reporter/script/performance/profiler new file mode 100644 index 0000000..d855ac8 --- /dev/null +++ b/chapter13/adwords_reporter/script/performance/profiler @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/performance/profiler' diff --git a/chapter13/adwords_reporter/script/plugin b/chapter13/adwords_reporter/script/plugin new file mode 100644 index 0000000..26ca64c --- /dev/null +++ b/chapter13/adwords_reporter/script/plugin @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/plugin' \ No newline at end of file diff --git a/chapter13/adwords_reporter/script/process/inspector b/chapter13/adwords_reporter/script/process/inspector new file mode 100644 index 0000000..bf25ad8 --- /dev/null +++ b/chapter13/adwords_reporter/script/process/inspector @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/inspector' diff --git a/chapter13/adwords_reporter/script/process/reaper b/chapter13/adwords_reporter/script/process/reaper new file mode 100644 index 0000000..c77f045 --- /dev/null +++ b/chapter13/adwords_reporter/script/process/reaper @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/reaper' diff --git a/chapter13/adwords_reporter/script/process/spawner b/chapter13/adwords_reporter/script/process/spawner new file mode 100644 index 0000000..7118f39 --- /dev/null +++ b/chapter13/adwords_reporter/script/process/spawner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../config/boot' +require 'commands/process/spawner' diff --git a/chapter13/adwords_reporter/script/runner b/chapter13/adwords_reporter/script/runner new file mode 100644 index 0000000..ccc30f9 --- /dev/null +++ b/chapter13/adwords_reporter/script/runner @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/runner' \ No newline at end of file diff --git a/chapter13/adwords_reporter/script/server b/chapter13/adwords_reporter/script/server new file mode 100644 index 0000000..dfabcb8 --- /dev/null +++ b/chapter13/adwords_reporter/script/server @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../config/boot' +require 'commands/server' \ No newline at end of file diff --git a/chapter13/adwords_reporter/test/fixtures/ad_results.yml b/chapter13/adwords_reporter/test/fixtures/ad_results.yml new file mode 100644 index 0000000..b49c4eb --- /dev/null +++ b/chapter13/adwords_reporter/test/fixtures/ad_results.yml @@ -0,0 +1,5 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html +one: + id: 1 +two: + id: 2 diff --git a/chapter13/adwords_reporter/test/functional/budget_optimizer_controller_test.rb b/chapter13/adwords_reporter/test/functional/budget_optimizer_controller_test.rb new file mode 100644 index 0000000..795487b --- /dev/null +++ b/chapter13/adwords_reporter/test/functional/budget_optimizer_controller_test.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../test_helper' +require 'budget_optimizer_controller' + +# Re-raise errors caught by the controller. +class BudgetOptimizerController; def rescue_action(e) raise e end; end + +class BudgetOptimizerControllerTest < Test::Unit::TestCase + def setup + @controller = BudgetOptimizerController.new + @request = ActionController::TestRequest.new + @response = ActionController::TestResponse.new + end + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter13/adwords_reporter/test/test_helper.rb b/chapter13/adwords_reporter/test/test_helper.rb new file mode 100644 index 0000000..a299c7f --- /dev/null +++ b/chapter13/adwords_reporter/test/test_helper.rb @@ -0,0 +1,28 @@ +ENV["RAILS_ENV"] = "test" +require File.expand_path(File.dirname(__FILE__) + "/../config/environment") +require 'test_help' + +class Test::Unit::TestCase + # Transactional fixtures accelerate your tests by wrapping each test method + # in a transaction that's rolled back on completion. This ensures that the + # test database remains unchanged so your fixtures don't have to be reloaded + # between every test method. Fewer database queries means faster tests. + # + # Read Mike Clark's excellent walkthrough at + # http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting + # + # Every Active Record database supports transactions except MyISAM tables + # in MySQL. Turn off transactional fixtures in this case; however, if you + # don't care one way or the other, switching from MyISAM to InnoDB tables + # is recommended. + self.use_transactional_fixtures = true + + # Instantiated fixtures are slow, but give you @david where otherwise you + # would need people(:david). If you don't want to migrate your existing + # test cases which use the @david style and don't mind the speed hit (each + # instantiated fixtures translates to a database query per test method), + # then set this back to true. + self.use_instantiated_fixtures = false + + # Add more helper methods to be used by all tests here... +end diff --git a/chapter13/adwords_reporter/test/unit/ad_results_test.rb b/chapter13/adwords_reporter/test/unit/ad_results_test.rb new file mode 100644 index 0000000..0377214 --- /dev/null +++ b/chapter13/adwords_reporter/test/unit/ad_results_test.rb @@ -0,0 +1,10 @@ +require File.dirname(__FILE__) + '/../test_helper' + +class AdResultsTest < Test::Unit::TestCase + fixtures :ad_results + + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/chapter13/google_adwords_sample.xml b/chapter13/google_adwords_sample.xml new file mode 100644 index 0000000..8896c19 --- /dev/null +++ b/chapter13/google_adwords_sample.xml @@ -0,0 +1,325 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      + + + + + + + +
      diff --git a/chapter13/loader.rb b/chapter13/loader.rb new file mode 100644 index 0000000..323c346 --- /dev/null +++ b/chapter13/loader.rb @@ -0,0 +1,57 @@ +require 'hpricot' +require 'active_record' + +hpricot_doc = Hpricot.XML(ARGF) + +ActiveRecord::Base.establish_connection( + :adapter=>'mysql', + :host=>'127.0.0.1', + :database=>'text_ad_report', + :username=>'root', + :password=>'') + +# This is the default username and password +# for MySQL, but note that if you have a +# different username and password, +# you should change it. + +class AdResult < ActiveRecord::Base +end + +rows=(hpricot_doc/"rows/row") + +unless AdResult.table_exists? + first_row = rows.first # We'll use this row as a model + # to create the database schema + + field_override_types = { + 'imps'=>:integer, + 'clicks'=>:integer, + 'ctr'=>:float, + 'cpc'=>:integer, + 'cost'=>:integer + } + + ActiveRecord::Schema.define do + create_table AdResult.table_name do |t| + first_row.attributes.each do |attribute_name, value| + if field_override_types.include?(attribute_name) + t.column attribute_name, field_override_types[attribute_name] + else + t.column attribute_name, :text, :length=>25 + end + end + end + end + +end + + +rows.each do |row| + AdResult.new do |n| + row.attributes.each do |attribute_name, attribute_value| + n.send("#{attribute_name}=", attribute_value) + end + n.save + end +end diff --git a/contributing.md b/contributing.md new file mode 100644 index 0000000..f6005ad --- /dev/null +++ b/contributing.md @@ -0,0 +1,14 @@ +# Contributing to Apress Source Code + +Copyright for Apress source code belongs to the author(s). However, under fair use you are encouraged to fork and contribute minor corrections and updates for the benefit of the author(s) and other readers. + +## How to Contribute + +1. Make sure you have a GitHub account. +2. Fork the repository for the relevant book. +3. Create a new branch on which to make your change, e.g. +`git checkout -b my_code_contribution` +4. Commit your change. Include a commit message describing the correction. Please note that if your commit message is not clear, the correction will not be accepted. +5. Submit a pull request. + +Thank you for your contribution! \ No newline at end of file