mirror of
https://github.com/TracksApp/tracks.git
synced 2026-02-04 15:01:48 +01:00
Removed superfluous 'tracks' directory at the root of the repository.
Testing commits to github.
This commit is contained in:
parent
6a42901514
commit
4cbf5a34d3
2269 changed files with 0 additions and 0 deletions
23
vendor/plugins/acts_as_list/README
vendored
Normal file
23
vendor/plugins/acts_as_list/README
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
ActsAsList
|
||||
==========
|
||||
|
||||
This acts_as extension provides the capabilities for sorting and reordering a number of objects in a list. The class that has this specified needs to have a +position+ column defined as an integer on the mapped database table.
|
||||
|
||||
|
||||
Example
|
||||
=======
|
||||
|
||||
class TodoList < ActiveRecord::Base
|
||||
has_many :todo_items, :order => "position"
|
||||
end
|
||||
|
||||
class TodoItem < ActiveRecord::Base
|
||||
belongs_to :todo_list
|
||||
acts_as_list :scope => :todo_list
|
||||
end
|
||||
|
||||
todo_list.first.move_to_bottom
|
||||
todo_list.last.move_higher
|
||||
|
||||
|
||||
Copyright (c) 2007 David Heinemeier Hansson, released under the MIT license
|
||||
3
vendor/plugins/acts_as_list/init.rb
vendored
Normal file
3
vendor/plugins/acts_as_list/init.rb
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
$:.unshift "#{File.dirname(__FILE__)}/lib"
|
||||
require 'active_record/acts/list'
|
||||
ActiveRecord::Base.class_eval { include ActiveRecord::Acts::List }
|
||||
256
vendor/plugins/acts_as_list/lib/active_record/acts/list.rb
vendored
Normal file
256
vendor/plugins/acts_as_list/lib/active_record/acts/list.rb
vendored
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
module ActiveRecord
|
||||
module Acts #:nodoc:
|
||||
module List #:nodoc:
|
||||
def self.included(base)
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
# This +acts_as+ extension provides the capabilities for sorting and reordering a number of objects in a list.
|
||||
# The class that has this specified needs to have a +position+ column defined as an integer on
|
||||
# the mapped database table.
|
||||
#
|
||||
# Todo list example:
|
||||
#
|
||||
# class TodoList < ActiveRecord::Base
|
||||
# has_many :todo_items, :order => "position"
|
||||
# end
|
||||
#
|
||||
# class TodoItem < ActiveRecord::Base
|
||||
# belongs_to :todo_list
|
||||
# acts_as_list :scope => :todo_list
|
||||
# end
|
||||
#
|
||||
# todo_list.first.move_to_bottom
|
||||
# todo_list.last.move_higher
|
||||
module ClassMethods
|
||||
# Configuration options are:
|
||||
#
|
||||
# * +column+ - specifies the column name to use for keeping the position integer (default: +position+)
|
||||
# * +scope+ - restricts what is to be considered a list. Given a symbol, it'll attach <tt>_id</tt>
|
||||
# (if it hasn't already been added) and use that as the foreign key restriction. It's also possible
|
||||
# to give it an entire string that is interpolated if you need a tighter scope than just a foreign key.
|
||||
# Example: <tt>acts_as_list :scope => 'todo_list_id = #{todo_list_id} AND completed = 0'</tt>
|
||||
def acts_as_list(options = {})
|
||||
configuration = { :column => "position", :scope => "1 = 1" }
|
||||
configuration.update(options) if options.is_a?(Hash)
|
||||
|
||||
configuration[:scope] = "#{configuration[:scope]}_id".intern if configuration[:scope].is_a?(Symbol) && configuration[:scope].to_s !~ /_id$/
|
||||
|
||||
if configuration[:scope].is_a?(Symbol)
|
||||
scope_condition_method = %(
|
||||
def scope_condition
|
||||
if #{configuration[:scope].to_s}.nil?
|
||||
"#{configuration[:scope].to_s} IS NULL"
|
||||
else
|
||||
"#{configuration[:scope].to_s} = \#{#{configuration[:scope].to_s}}"
|
||||
end
|
||||
end
|
||||
)
|
||||
else
|
||||
scope_condition_method = "def scope_condition() \"#{configuration[:scope]}\" end"
|
||||
end
|
||||
|
||||
class_eval <<-EOV
|
||||
include ActiveRecord::Acts::List::InstanceMethods
|
||||
|
||||
def acts_as_list_class
|
||||
::#{self.name}
|
||||
end
|
||||
|
||||
def position_column
|
||||
'#{configuration[:column]}'
|
||||
end
|
||||
|
||||
#{scope_condition_method}
|
||||
|
||||
before_destroy :remove_from_list
|
||||
before_create :add_to_list_bottom
|
||||
EOV
|
||||
end
|
||||
end
|
||||
|
||||
# All the methods available to a record that has had <tt>acts_as_list</tt> specified. Each method works
|
||||
# by assuming the object to be the item in the list, so <tt>chapter.move_lower</tt> would move that chapter
|
||||
# lower in the list of all chapters. Likewise, <tt>chapter.first?</tt> would return +true+ if that chapter is
|
||||
# the first in the list of all chapters.
|
||||
module InstanceMethods
|
||||
# Insert the item at the given position (defaults to the top position of 1).
|
||||
def insert_at(position = 1)
|
||||
insert_at_position(position)
|
||||
end
|
||||
|
||||
# Swap positions with the next lower item, if one exists.
|
||||
def move_lower
|
||||
return unless lower_item
|
||||
|
||||
acts_as_list_class.transaction do
|
||||
lower_item.decrement_position
|
||||
increment_position
|
||||
end
|
||||
end
|
||||
|
||||
# Swap positions with the next higher item, if one exists.
|
||||
def move_higher
|
||||
return unless higher_item
|
||||
|
||||
acts_as_list_class.transaction do
|
||||
higher_item.increment_position
|
||||
decrement_position
|
||||
end
|
||||
end
|
||||
|
||||
# Move to the bottom of the list. If the item is already in the list, the items below it have their
|
||||
# position adjusted accordingly.
|
||||
def move_to_bottom
|
||||
return unless in_list?
|
||||
acts_as_list_class.transaction do
|
||||
decrement_positions_on_lower_items
|
||||
assume_bottom_position
|
||||
end
|
||||
end
|
||||
|
||||
# Move to the top of the list. If the item is already in the list, the items above it have their
|
||||
# position adjusted accordingly.
|
||||
def move_to_top
|
||||
return unless in_list?
|
||||
acts_as_list_class.transaction do
|
||||
increment_positions_on_higher_items
|
||||
assume_top_position
|
||||
end
|
||||
end
|
||||
|
||||
# Removes the item from the list.
|
||||
def remove_from_list
|
||||
if in_list?
|
||||
decrement_positions_on_lower_items
|
||||
update_attribute position_column, nil
|
||||
end
|
||||
end
|
||||
|
||||
# Increase the position of this item without adjusting the rest of the list.
|
||||
def increment_position
|
||||
return unless in_list?
|
||||
update_attribute position_column, self.send(position_column).to_i + 1
|
||||
end
|
||||
|
||||
# Decrease the position of this item without adjusting the rest of the list.
|
||||
def decrement_position
|
||||
return unless in_list?
|
||||
update_attribute position_column, self.send(position_column).to_i - 1
|
||||
end
|
||||
|
||||
# Return +true+ if this object is the first in the list.
|
||||
def first?
|
||||
return false unless in_list?
|
||||
self.send(position_column) == 1
|
||||
end
|
||||
|
||||
# Return +true+ if this object is the last in the list.
|
||||
def last?
|
||||
return false unless in_list?
|
||||
self.send(position_column) == bottom_position_in_list
|
||||
end
|
||||
|
||||
# Return the next higher item in the list.
|
||||
def higher_item
|
||||
return nil unless in_list?
|
||||
acts_as_list_class.find(:first, :conditions =>
|
||||
"#{scope_condition} AND #{position_column} = #{(send(position_column).to_i - 1).to_s}"
|
||||
)
|
||||
end
|
||||
|
||||
# Return the next lower item in the list.
|
||||
def lower_item
|
||||
return nil unless in_list?
|
||||
acts_as_list_class.find(:first, :conditions =>
|
||||
"#{scope_condition} AND #{position_column} = #{(send(position_column).to_i + 1).to_s}"
|
||||
)
|
||||
end
|
||||
|
||||
# Test if this record is in a list
|
||||
def in_list?
|
||||
!send(position_column).nil?
|
||||
end
|
||||
|
||||
private
|
||||
def add_to_list_top
|
||||
increment_positions_on_all_items
|
||||
end
|
||||
|
||||
def add_to_list_bottom
|
||||
self[position_column] = bottom_position_in_list.to_i + 1
|
||||
end
|
||||
|
||||
# Overwrite this method to define the scope of the list changes
|
||||
def scope_condition() "1" end
|
||||
|
||||
# Returns the bottom position number in the list.
|
||||
# bottom_position_in_list # => 2
|
||||
def bottom_position_in_list(except = nil)
|
||||
item = bottom_item(except)
|
||||
item ? item.send(position_column) : 0
|
||||
end
|
||||
|
||||
# Returns the bottom item
|
||||
def bottom_item(except = nil)
|
||||
conditions = scope_condition
|
||||
conditions = "#{conditions} AND #{self.class.primary_key} != #{except.id}" if except
|
||||
acts_as_list_class.find(:first, :conditions => conditions, :order => "#{position_column} DESC")
|
||||
end
|
||||
|
||||
# Forces item to assume the bottom position in the list.
|
||||
def assume_bottom_position
|
||||
update_attribute(position_column, bottom_position_in_list(self).to_i + 1)
|
||||
end
|
||||
|
||||
# Forces item to assume the top position in the list.
|
||||
def assume_top_position
|
||||
update_attribute(position_column, 1)
|
||||
end
|
||||
|
||||
# This has the effect of moving all the higher items up one.
|
||||
def decrement_positions_on_higher_items(position)
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} <= #{position}"
|
||||
)
|
||||
end
|
||||
|
||||
# This has the effect of moving all the lower items up one.
|
||||
def decrement_positions_on_lower_items
|
||||
return unless in_list?
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} - 1)", "#{scope_condition} AND #{position_column} > #{send(position_column).to_i}"
|
||||
)
|
||||
end
|
||||
|
||||
# This has the effect of moving all the higher items down one.
|
||||
def increment_positions_on_higher_items
|
||||
return unless in_list?
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} < #{send(position_column).to_i}"
|
||||
)
|
||||
end
|
||||
|
||||
# This has the effect of moving all the lower items down one.
|
||||
def increment_positions_on_lower_items(position)
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} + 1)", "#{scope_condition} AND #{position_column} >= #{position}"
|
||||
)
|
||||
end
|
||||
|
||||
# Increments position (<tt>position_column</tt>) of all items in the list.
|
||||
def increment_positions_on_all_items
|
||||
acts_as_list_class.update_all(
|
||||
"#{position_column} = (#{position_column} + 1)", "#{scope_condition}"
|
||||
)
|
||||
end
|
||||
|
||||
def insert_at_position(position)
|
||||
remove_from_list
|
||||
increment_positions_on_lower_items(position)
|
||||
self.update_attribute(position_column, position)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
332
vendor/plugins/acts_as_list/test/list_test.rb
vendored
Normal file
332
vendor/plugins/acts_as_list/test/list_test.rb
vendored
Normal file
|
|
@ -0,0 +1,332 @@
|
|||
require 'test/unit'
|
||||
|
||||
require 'rubygems'
|
||||
gem 'activerecord', '>= 1.15.4.7794'
|
||||
require 'active_record'
|
||||
|
||||
require "#{File.dirname(__FILE__)}/../init"
|
||||
|
||||
ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :dbfile => ":memory:")
|
||||
|
||||
def setup_db
|
||||
ActiveRecord::Schema.define(:version => 1) do
|
||||
create_table :mixins do |t|
|
||||
t.column :pos, :integer
|
||||
t.column :parent_id, :integer
|
||||
t.column :created_at, :datetime
|
||||
t.column :updated_at, :datetime
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def teardown_db
|
||||
ActiveRecord::Base.connection.tables.each do |table|
|
||||
ActiveRecord::Base.connection.drop_table(table)
|
||||
end
|
||||
end
|
||||
|
||||
class Mixin < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class ListMixin < Mixin
|
||||
acts_as_list :column => "pos", :scope => :parent
|
||||
|
||||
def self.table_name() "mixins" end
|
||||
end
|
||||
|
||||
class ListMixinSub1 < ListMixin
|
||||
end
|
||||
|
||||
class ListMixinSub2 < ListMixin
|
||||
end
|
||||
|
||||
class ListWithStringScopeMixin < ActiveRecord::Base
|
||||
acts_as_list :column => "pos", :scope => 'parent_id = #{parent_id}'
|
||||
|
||||
def self.table_name() "mixins" end
|
||||
end
|
||||
|
||||
|
||||
class ListTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
setup_db
|
||||
(1..4).each { |counter| ListMixin.create! :pos => counter, :parent_id => 5 }
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_db
|
||||
end
|
||||
|
||||
def test_reordering
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_lower
|
||||
assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_higher
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(1).move_to_bottom
|
||||
assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(1).move_to_top
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_to_bottom
|
||||
assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(4).move_to_top
|
||||
assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
end
|
||||
|
||||
def test_move_to_bottom_with_next_to_last_item
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
ListMixin.find(3).move_to_bottom
|
||||
assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
end
|
||||
|
||||
def test_next_prev
|
||||
assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
|
||||
assert_nil ListMixin.find(1).higher_item
|
||||
assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
|
||||
assert_nil ListMixin.find(4).lower_item
|
||||
end
|
||||
|
||||
def test_injection
|
||||
item = ListMixin.new(:parent_id => 1)
|
||||
assert_equal "parent_id = 1", item.scope_condition
|
||||
assert_equal "pos", item.position_column
|
||||
end
|
||||
|
||||
def test_insert
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 1, new.pos
|
||||
assert new.first?
|
||||
assert new.last?
|
||||
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 2, new.pos
|
||||
assert !new.first?
|
||||
assert new.last?
|
||||
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 3, new.pos
|
||||
assert !new.first?
|
||||
assert new.last?
|
||||
|
||||
new = ListMixin.create(:parent_id => 0)
|
||||
assert_equal 1, new.pos
|
||||
assert new.first?
|
||||
assert new.last?
|
||||
end
|
||||
|
||||
def test_insert_at
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 1, new.pos
|
||||
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 2, new.pos
|
||||
|
||||
new = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 3, new.pos
|
||||
|
||||
new4 = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 4, new4.pos
|
||||
|
||||
new4.insert_at(3)
|
||||
assert_equal 3, new4.pos
|
||||
|
||||
new.reload
|
||||
assert_equal 4, new.pos
|
||||
|
||||
new.insert_at(2)
|
||||
assert_equal 2, new.pos
|
||||
|
||||
new4.reload
|
||||
assert_equal 4, new4.pos
|
||||
|
||||
new5 = ListMixin.create(:parent_id => 20)
|
||||
assert_equal 5, new5.pos
|
||||
|
||||
new5.insert_at(1)
|
||||
assert_equal 1, new5.pos
|
||||
|
||||
new4.reload
|
||||
assert_equal 5, new4.pos
|
||||
end
|
||||
|
||||
def test_delete_middle
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).destroy
|
||||
|
||||
assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(1).pos
|
||||
assert_equal 2, ListMixin.find(3).pos
|
||||
assert_equal 3, ListMixin.find(4).pos
|
||||
|
||||
ListMixin.find(1).destroy
|
||||
|
||||
assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(3).pos
|
||||
assert_equal 2, ListMixin.find(4).pos
|
||||
end
|
||||
|
||||
def test_with_string_based_scope
|
||||
new = ListWithStringScopeMixin.create(:parent_id => 500)
|
||||
assert_equal 1, new.pos
|
||||
assert new.first?
|
||||
assert new.last?
|
||||
end
|
||||
|
||||
def test_nil_scope
|
||||
new1, new2, new3 = ListMixin.create, ListMixin.create, ListMixin.create
|
||||
new2.move_higher
|
||||
assert_equal [new2, new1, new3], ListMixin.find(:all, :conditions => 'parent_id IS NULL', :order => 'pos')
|
||||
end
|
||||
|
||||
|
||||
def test_remove_from_list_should_then_fail_in_list?
|
||||
assert_equal true, ListMixin.find(1).in_list?
|
||||
ListMixin.find(1).remove_from_list
|
||||
assert_equal false, ListMixin.find(1).in_list?
|
||||
end
|
||||
|
||||
def test_remove_from_list_should_set_position_to_nil
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).remove_from_list
|
||||
|
||||
assert_equal [2, 1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(1).pos
|
||||
assert_equal nil, ListMixin.find(2).pos
|
||||
assert_equal 2, ListMixin.find(3).pos
|
||||
assert_equal 3, ListMixin.find(4).pos
|
||||
end
|
||||
|
||||
def test_remove_before_destroy_does_not_shift_lower_items_twice
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).remove_from_list
|
||||
ListMixin.find(2).destroy
|
||||
|
||||
assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(1).pos
|
||||
assert_equal 2, ListMixin.find(3).pos
|
||||
assert_equal 3, ListMixin.find(4).pos
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
class ListSubTest < Test::Unit::TestCase
|
||||
|
||||
def setup
|
||||
setup_db
|
||||
(1..4).each { |i| ((i % 2 == 1) ? ListMixinSub1 : ListMixinSub2).create! :pos => i, :parent_id => 5000 }
|
||||
end
|
||||
|
||||
def teardown
|
||||
teardown_db
|
||||
end
|
||||
|
||||
def test_reordering
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_lower
|
||||
assert_equal [1, 3, 2, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_higher
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(1).move_to_bottom
|
||||
assert_equal [2, 3, 4, 1], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(1).move_to_top
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).move_to_bottom
|
||||
assert_equal [1, 3, 4, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(4).move_to_top
|
||||
assert_equal [4, 1, 3, 2], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
end
|
||||
|
||||
def test_move_to_bottom_with_next_to_last_item
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
ListMixin.find(3).move_to_bottom
|
||||
assert_equal [1, 2, 4, 3], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
end
|
||||
|
||||
def test_next_prev
|
||||
assert_equal ListMixin.find(2), ListMixin.find(1).lower_item
|
||||
assert_nil ListMixin.find(1).higher_item
|
||||
assert_equal ListMixin.find(3), ListMixin.find(4).higher_item
|
||||
assert_nil ListMixin.find(4).lower_item
|
||||
end
|
||||
|
||||
def test_injection
|
||||
item = ListMixin.new("parent_id"=>1)
|
||||
assert_equal "parent_id = 1", item.scope_condition
|
||||
assert_equal "pos", item.position_column
|
||||
end
|
||||
|
||||
def test_insert_at
|
||||
new = ListMixin.create("parent_id" => 20)
|
||||
assert_equal 1, new.pos
|
||||
|
||||
new = ListMixinSub1.create("parent_id" => 20)
|
||||
assert_equal 2, new.pos
|
||||
|
||||
new = ListMixinSub2.create("parent_id" => 20)
|
||||
assert_equal 3, new.pos
|
||||
|
||||
new4 = ListMixin.create("parent_id" => 20)
|
||||
assert_equal 4, new4.pos
|
||||
|
||||
new4.insert_at(3)
|
||||
assert_equal 3, new4.pos
|
||||
|
||||
new.reload
|
||||
assert_equal 4, new.pos
|
||||
|
||||
new.insert_at(2)
|
||||
assert_equal 2, new.pos
|
||||
|
||||
new4.reload
|
||||
assert_equal 4, new4.pos
|
||||
|
||||
new5 = ListMixinSub1.create("parent_id" => 20)
|
||||
assert_equal 5, new5.pos
|
||||
|
||||
new5.insert_at(1)
|
||||
assert_equal 1, new5.pos
|
||||
|
||||
new4.reload
|
||||
assert_equal 5, new4.pos
|
||||
end
|
||||
|
||||
def test_delete_middle
|
||||
assert_equal [1, 2, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
ListMixin.find(2).destroy
|
||||
|
||||
assert_equal [1, 3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(1).pos
|
||||
assert_equal 2, ListMixin.find(3).pos
|
||||
assert_equal 3, ListMixin.find(4).pos
|
||||
|
||||
ListMixin.find(1).destroy
|
||||
|
||||
assert_equal [3, 4], ListMixin.find(:all, :conditions => 'parent_id = 5000', :order => 'pos').map(&:id)
|
||||
|
||||
assert_equal 1, ListMixin.find(3).pos
|
||||
assert_equal 2, ListMixin.find(4).pos
|
||||
end
|
||||
|
||||
end
|
||||
13
vendor/plugins/acts_as_state_machine/CHANGELOG
vendored
Normal file
13
vendor/plugins/acts_as_state_machine/CHANGELOG
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
* trunk *
|
||||
break with true value [Kaspar Schiess]
|
||||
|
||||
* 2.1 *
|
||||
After actions [Saimon Moore]
|
||||
|
||||
* 2.0 * (2006-01-20 15:26:28 -0500)
|
||||
Enter / Exit actions
|
||||
Transition guards
|
||||
Guards and actions can be a symbol pointing to a method or a Proc
|
||||
|
||||
* 1.0 * (2006-01-15 12:16:55 -0500)
|
||||
Initial Release
|
||||
20
vendor/plugins/acts_as_state_machine/MIT-LICENSE
vendored
Normal file
20
vendor/plugins/acts_as_state_machine/MIT-LICENSE
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
Copyright (c) 2006 Scott Barron
|
||||
|
||||
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.
|
||||
33
vendor/plugins/acts_as_state_machine/README
vendored
Normal file
33
vendor/plugins/acts_as_state_machine/README
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
= Acts As State Machine
|
||||
|
||||
This act gives an Active Record model the ability to act as a finite state
|
||||
machine (FSM).
|
||||
|
||||
Acquire via subversion at:
|
||||
|
||||
http://elitists.textdriven.com/svn/plugins/acts_as_state_machine/trunk
|
||||
|
||||
If prompted, use the user/pass anonymous/anonymous.
|
||||
|
||||
== Example
|
||||
|
||||
class Order < ActiveRecord::Base
|
||||
acts_as_state_machine :initial => :opened
|
||||
|
||||
state :opened
|
||||
state :closed, :enter => Proc.new {|o| Mailer.send_notice(o)}
|
||||
state :returned
|
||||
|
||||
event :close do
|
||||
transitions :to => :closed, :from => :opened
|
||||
end
|
||||
|
||||
event :return do
|
||||
transitions :to => :returned, :from => :closed
|
||||
end
|
||||
end
|
||||
|
||||
o = Order.create
|
||||
o.close! # notice is sent by mailer
|
||||
o.return!
|
||||
|
||||
28
vendor/plugins/acts_as_state_machine/Rakefile
vendored
Normal file
28
vendor/plugins/acts_as_state_machine/Rakefile
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => [:clean_db, :test]
|
||||
|
||||
desc 'Remove the stale db file'
|
||||
task :clean_db do
|
||||
`rm -f #{File.dirname(__FILE__)}/test/state_machine.sqlite.db`
|
||||
end
|
||||
|
||||
desc 'Test the acts as state machine plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the acts as state machine plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'Acts As State Machine'
|
||||
rdoc.options << '--line-numbers --inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('TODO')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
11
vendor/plugins/acts_as_state_machine/TODO
vendored
Normal file
11
vendor/plugins/acts_as_state_machine/TODO
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
* Currently invalid events are ignored, create an option so that they can be
|
||||
ignored or raise an exception.
|
||||
|
||||
* Query for a list of possible next states.
|
||||
|
||||
* Make listing states optional since they can be inferred from the events.
|
||||
Only required to list a state if you want to define a transition block for it.
|
||||
|
||||
* Real transition actions
|
||||
|
||||
* Default states
|
||||
5
vendor/plugins/acts_as_state_machine/init.rb
vendored
Normal file
5
vendor/plugins/acts_as_state_machine/init.rb
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'acts_as_state_machine'
|
||||
|
||||
ActiveRecord::Base.class_eval do
|
||||
include ScottBarron::Acts::StateMachine
|
||||
end
|
||||
268
vendor/plugins/acts_as_state_machine/lib/acts_as_state_machine.rb
vendored
Normal file
268
vendor/plugins/acts_as_state_machine/lib/acts_as_state_machine.rb
vendored
Normal file
|
|
@ -0,0 +1,268 @@
|
|||
module ScottBarron #:nodoc:
|
||||
module Acts #:nodoc:
|
||||
module StateMachine #:nodoc:
|
||||
class InvalidState < Exception #:nodoc:
|
||||
end
|
||||
class NoInitialState < Exception #:nodoc:
|
||||
end
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
base.extend ActMacro
|
||||
end
|
||||
|
||||
module SupportingClasses
|
||||
class State
|
||||
attr_reader :name
|
||||
|
||||
def initialize(name, opts)
|
||||
@name, @opts = name, opts
|
||||
end
|
||||
|
||||
def entering(record)
|
||||
enteract = @opts[:enter]
|
||||
record.send(:run_transition_action, enteract) if enteract
|
||||
end
|
||||
|
||||
def entered(record)
|
||||
afteractions = @opts[:after]
|
||||
return unless afteractions
|
||||
Array(afteractions).each do |afteract|
|
||||
record.send(:run_transition_action, afteract)
|
||||
end
|
||||
end
|
||||
|
||||
def exited(record)
|
||||
exitact = @opts[:exit]
|
||||
record.send(:run_transition_action, exitact) if exitact
|
||||
end
|
||||
end
|
||||
|
||||
class StateTransition
|
||||
attr_reader :from, :to, :opts
|
||||
|
||||
def initialize(opts)
|
||||
@from, @to, @guard = opts[:from], opts[:to], opts[:guard]
|
||||
@opts = opts
|
||||
end
|
||||
|
||||
def guard(obj)
|
||||
@guard ? obj.send(:run_transition_action, @guard) : true
|
||||
end
|
||||
|
||||
def perform(record)
|
||||
return false unless guard(record)
|
||||
loopback = record.current_state == to
|
||||
states = record.class.read_inheritable_attribute(:states)
|
||||
next_state = states[to]
|
||||
old_state = states[record.current_state]
|
||||
|
||||
next_state.entering(record) unless loopback
|
||||
|
||||
record.update_attribute(record.class.state_column, to.to_s)
|
||||
|
||||
next_state.entered(record) unless loopback
|
||||
old_state.exited(record) unless loopback
|
||||
true
|
||||
end
|
||||
|
||||
def ==(obj)
|
||||
@from == obj.from && @to == obj.to
|
||||
end
|
||||
end
|
||||
|
||||
class Event
|
||||
attr_reader :name
|
||||
attr_reader :transitions
|
||||
attr_reader :opts
|
||||
|
||||
def initialize(name, opts, transition_table, &block)
|
||||
@name = name.to_sym
|
||||
@transitions = transition_table[@name] = []
|
||||
instance_eval(&block) if block
|
||||
@opts = opts
|
||||
@opts.freeze
|
||||
@transitions.freeze
|
||||
freeze
|
||||
end
|
||||
|
||||
def next_states(record)
|
||||
@transitions.select { |t| t.from == record.current_state }
|
||||
end
|
||||
|
||||
def fire(record)
|
||||
next_states(record).each do |transition|
|
||||
break true if transition.perform(record)
|
||||
end
|
||||
end
|
||||
|
||||
def transitions(trans_opts)
|
||||
Array(trans_opts[:from]).each do |s|
|
||||
@transitions << SupportingClasses::StateTransition.new(trans_opts.merge({:from => s.to_sym}))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module ActMacro
|
||||
# Configuration options are
|
||||
#
|
||||
# * +column+ - specifies the column name to use for keeping the state (default: state)
|
||||
# * +initial+ - specifies an initial state for newly created objects (required)
|
||||
def acts_as_state_machine(opts)
|
||||
self.extend(ClassMethods)
|
||||
raise NoInitialState unless opts[:initial]
|
||||
|
||||
write_inheritable_attribute :states, {}
|
||||
write_inheritable_attribute :initial_state, opts[:initial]
|
||||
write_inheritable_attribute :transition_table, {}
|
||||
write_inheritable_attribute :event_table, {}
|
||||
write_inheritable_attribute :state_column, opts[:column] || 'state'
|
||||
|
||||
class_inheritable_reader :initial_state
|
||||
class_inheritable_reader :state_column
|
||||
class_inheritable_reader :transition_table
|
||||
class_inheritable_reader :event_table
|
||||
|
||||
self.send(:include, ScottBarron::Acts::StateMachine::InstanceMethods)
|
||||
|
||||
before_create :set_initial_state
|
||||
after_create :run_initial_state_actions
|
||||
end
|
||||
end
|
||||
|
||||
module InstanceMethods
|
||||
def set_initial_state #:nodoc:
|
||||
write_attribute self.class.state_column, self.class.initial_state.to_s
|
||||
end
|
||||
|
||||
def run_initial_state_actions
|
||||
initial = self.class.read_inheritable_attribute(:states)[self.class.initial_state.to_sym]
|
||||
initial.entering(self)
|
||||
initial.entered(self)
|
||||
end
|
||||
|
||||
# Returns the current state the object is in, as a Ruby symbol.
|
||||
def current_state
|
||||
self.send(self.class.state_column).to_sym
|
||||
end
|
||||
|
||||
# Returns what the next state for a given event would be, as a Ruby symbol.
|
||||
def next_state_for_event(event)
|
||||
ns = next_states_for_event(event)
|
||||
ns.empty? ? nil : ns.first.to
|
||||
end
|
||||
|
||||
def next_states_for_event(event)
|
||||
self.class.read_inheritable_attribute(:transition_table)[event.to_sym].select do |s|
|
||||
s.from == current_state
|
||||
end
|
||||
end
|
||||
|
||||
def run_transition_action(action)
|
||||
Symbol === action ? self.method(action).call : action.call(self)
|
||||
end
|
||||
private :run_transition_action
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
# Returns an array of all known states.
|
||||
def states
|
||||
read_inheritable_attribute(:states).keys
|
||||
end
|
||||
|
||||
# Define an event. This takes a block which describes all valid transitions
|
||||
# for this event.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class Order < ActiveRecord::Base
|
||||
# acts_as_state_machine :initial => :open
|
||||
#
|
||||
# state :open
|
||||
# state :closed
|
||||
#
|
||||
# event :close_order do
|
||||
# transitions :to => :closed, :from => :open
|
||||
# end
|
||||
# end
|
||||
#
|
||||
# +transitions+ takes a hash where <tt>:to</tt> is the state to transition
|
||||
# to and <tt>:from</tt> is a state (or Array of states) from which this
|
||||
# event can be fired.
|
||||
#
|
||||
# This creates an instance method used for firing the event. The method
|
||||
# created is the name of the event followed by an exclamation point (!).
|
||||
# Example: <tt>order.close_order!</tt>.
|
||||
def event(event, opts={}, &block)
|
||||
tt = read_inheritable_attribute(:transition_table)
|
||||
|
||||
et = read_inheritable_attribute(:event_table)
|
||||
e = et[event.to_sym] = SupportingClasses::Event.new(event, opts, tt, &block)
|
||||
define_method("#{event.to_s}!") { e.fire(self) }
|
||||
end
|
||||
|
||||
# Define a state of the system. +state+ can take an optional Proc object
|
||||
# which will be executed every time the system transitions into that
|
||||
# state. The proc will be passed the current object.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# class Order < ActiveRecord::Base
|
||||
# acts_as_state_machine :initial => :open
|
||||
#
|
||||
# state :open
|
||||
# state :closed, Proc.new { |o| Mailer.send_notice(o) }
|
||||
# end
|
||||
def state(name, opts={})
|
||||
state = SupportingClasses::State.new(name.to_sym, opts)
|
||||
read_inheritable_attribute(:states)[name.to_sym] = state
|
||||
|
||||
define_method("#{state.name}?") { current_state == state.name }
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.find to conveniently find all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +number+ - This is just :first or :all from ActiveRecord +find+
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +find+
|
||||
def find_in_state(number, state, *args)
|
||||
with_state_scope state do
|
||||
find(number, *args)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.count to conveniently count all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +find+
|
||||
def count_in_state(state, *args)
|
||||
with_state_scope state do
|
||||
count(*args)
|
||||
end
|
||||
end
|
||||
|
||||
# Wraps ActiveRecord::Base.calculate to conveniently calculate all records in
|
||||
# a given state. Options:
|
||||
#
|
||||
# * +state+ - The state to find
|
||||
# * +args+ - The rest of the args are passed down to ActiveRecord +calculate+
|
||||
def calculate_in_state(state, *args)
|
||||
with_state_scope state do
|
||||
calculate(*args)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def with_state_scope(state)
|
||||
raise InvalidState unless states.include?(state)
|
||||
|
||||
with_scope :find => {:conditions => ["#{table_name}.#{state_column} = ?", state.to_s]} do
|
||||
yield if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
224
vendor/plugins/acts_as_state_machine/test/acts_as_state_machine_test.rb
vendored
Normal file
224
vendor/plugins/acts_as_state_machine/test/acts_as_state_machine_test.rb
vendored
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
require File.dirname(__FILE__) + '/test_helper'
|
||||
|
||||
include ScottBarron::Acts::StateMachine
|
||||
|
||||
class ActsAsStateMachineTest < Test::Unit::TestCase
|
||||
fixtures :conversations
|
||||
|
||||
def test_no_initial_value_raises_exception
|
||||
assert_raise(NoInitialState) {
|
||||
Person.acts_as_state_machine({})
|
||||
}
|
||||
end
|
||||
|
||||
def test_initial_state_value
|
||||
assert_equal :needs_attention, Conversation.initial_state
|
||||
end
|
||||
|
||||
def test_column_was_set
|
||||
assert_equal 'state_machine', Conversation.state_column
|
||||
end
|
||||
|
||||
def test_initial_state
|
||||
c = Conversation.create
|
||||
assert_equal :needs_attention, c.current_state
|
||||
assert c.needs_attention?
|
||||
end
|
||||
|
||||
def test_states_were_set
|
||||
[:needs_attention, :read, :closed, :awaiting_response, :junk].each do |s|
|
||||
assert Conversation.states.include?(s)
|
||||
end
|
||||
end
|
||||
|
||||
def test_event_methods_created
|
||||
c = Conversation.create
|
||||
%w(new_message! view! reply! close! junk! unjunk!).each do |event|
|
||||
assert c.respond_to?(event)
|
||||
end
|
||||
end
|
||||
|
||||
def test_query_methods_created
|
||||
c = Conversation.create
|
||||
%w(needs_attention? read? closed? awaiting_response? junk?).each do |event|
|
||||
assert c.respond_to?(event)
|
||||
end
|
||||
end
|
||||
|
||||
def test_transition_table
|
||||
tt = Conversation.transition_table
|
||||
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :read, :to => :needs_attention))
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :closed, :to => :needs_attention))
|
||||
assert tt[:new_message].include?(SupportingClasses::StateTransition.new(:from => :awaiting_response, :to => :needs_attention))
|
||||
end
|
||||
|
||||
def test_next_state_for_event
|
||||
c = Conversation.create
|
||||
assert_equal :read, c.next_state_for_event(:view)
|
||||
end
|
||||
|
||||
def test_change_state
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
assert c.read?
|
||||
end
|
||||
|
||||
def test_can_go_from_read_to_closed_because_guard_passes
|
||||
c = Conversation.create
|
||||
c.can_close = true
|
||||
c.view!
|
||||
c.reply!
|
||||
c.close!
|
||||
assert_equal :closed, c.current_state
|
||||
end
|
||||
|
||||
def test_cannot_go_from_read_to_closed_because_of_guard
|
||||
c = Conversation.create
|
||||
c.can_close = false
|
||||
c.view!
|
||||
c.reply!
|
||||
c.close!
|
||||
assert_equal :read, c.current_state
|
||||
end
|
||||
|
||||
def test_ignore_invalid_events
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
c.junk!
|
||||
|
||||
# This is the invalid event
|
||||
c.new_message!
|
||||
assert_equal :junk, c.current_state
|
||||
end
|
||||
|
||||
def test_entry_action_executed
|
||||
c = Conversation.create
|
||||
c.read_enter = false
|
||||
c.view!
|
||||
assert c.read_enter
|
||||
end
|
||||
|
||||
def test_after_actions_executed
|
||||
c = Conversation.create
|
||||
|
||||
c.read_after_first = false
|
||||
c.read_after_second = false
|
||||
c.closed_after = false
|
||||
|
||||
c.view!
|
||||
assert c.read_after_first
|
||||
assert c.read_after_second
|
||||
|
||||
c.can_close = true
|
||||
c.close!
|
||||
|
||||
assert c.closed_after
|
||||
assert_equal :closed, c.current_state
|
||||
end
|
||||
|
||||
def test_after_actions_not_run_on_loopback_transition
|
||||
c = Conversation.create
|
||||
|
||||
c.view!
|
||||
c.read_after_first = false
|
||||
c.read_after_second = false
|
||||
c.view!
|
||||
|
||||
assert !c.read_after_first
|
||||
assert !c.read_after_second
|
||||
|
||||
c.can_close = true
|
||||
|
||||
c.close!
|
||||
c.closed_after = false
|
||||
c.close!
|
||||
|
||||
assert !c.closed_after
|
||||
end
|
||||
|
||||
def test_exit_action_executed
|
||||
c = Conversation.create
|
||||
c.read_exit = false
|
||||
c.view!
|
||||
c.junk!
|
||||
assert c.read_exit
|
||||
end
|
||||
|
||||
def test_entry_and_exit_not_run_on_loopback_transition
|
||||
c = Conversation.create
|
||||
c.view!
|
||||
c.read_enter = false
|
||||
c.read_exit = false
|
||||
c.view!
|
||||
assert !c.read_enter
|
||||
assert !c.read_exit
|
||||
end
|
||||
|
||||
def test_entry_and_after_actions_called_for_initial_state
|
||||
c = Conversation.create
|
||||
assert c.needs_attention_enter
|
||||
assert c.needs_attention_after
|
||||
end
|
||||
|
||||
def test_run_transition_action_is_private
|
||||
c = Conversation.create
|
||||
assert_raise(NoMethodError) { c.run_transition_action :foo }
|
||||
end
|
||||
|
||||
def test_find_all_in_state
|
||||
cs = Conversation.find_in_state(:all, :read)
|
||||
|
||||
assert_equal 2, cs.size
|
||||
end
|
||||
|
||||
def test_find_first_in_state
|
||||
c = Conversation.find_in_state(:first, :read)
|
||||
|
||||
assert_equal conversations(:first).id, c.id
|
||||
end
|
||||
|
||||
def test_find_all_in_state_with_conditions
|
||||
cs = Conversation.find_in_state(:all, :read, :conditions => ['subject = ?', conversations(:second).subject])
|
||||
|
||||
assert_equal 1, cs.size
|
||||
assert_equal conversations(:second).id, cs.first.id
|
||||
end
|
||||
|
||||
def test_find_first_in_state_with_conditions
|
||||
c = Conversation.find_in_state(:first, :read, :conditions => ['subject = ?', conversations(:second).subject])
|
||||
assert_equal conversations(:second).id, c.id
|
||||
end
|
||||
|
||||
def test_count_in_state
|
||||
cnt0 = Conversation.count(['state_machine = ?', 'read'])
|
||||
cnt = Conversation.count_in_state(:read)
|
||||
|
||||
assert_equal cnt0, cnt
|
||||
end
|
||||
|
||||
def test_count_in_state_with_conditions
|
||||
cnt0 = Conversation.count(['state_machine = ? AND subject = ?', 'read', 'Foo'])
|
||||
cnt = Conversation.count_in_state(:read, ['subject = ?', 'Foo'])
|
||||
|
||||
assert_equal cnt0, cnt
|
||||
end
|
||||
|
||||
def test_find_in_invalid_state_raises_exception
|
||||
assert_raise(InvalidState) {
|
||||
Conversation.find_in_state(:all, :dead)
|
||||
}
|
||||
end
|
||||
|
||||
def test_count_in_invalid_state_raises_exception
|
||||
assert_raise(InvalidState) {
|
||||
Conversation.count_in_state(:dead)
|
||||
}
|
||||
end
|
||||
|
||||
def test_can_access_events_via_event_table
|
||||
event = Conversation.event_table[:junk]
|
||||
assert_equal :junk, event.name
|
||||
assert_equal "finished", event.opts[:note]
|
||||
end
|
||||
end
|
||||
18
vendor/plugins/acts_as_state_machine/test/database.yml
vendored
Normal file
18
vendor/plugins/acts_as_state_machine/test/database.yml
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
sqlite:
|
||||
:adapter: sqlite
|
||||
:dbfile: state_machine.sqlite.db
|
||||
sqlite3:
|
||||
:adapter: sqlite3
|
||||
:dbfile: state_machine.sqlite3.db
|
||||
postgresql:
|
||||
:adapter: postgresql
|
||||
:username: postgres
|
||||
:password: postgres
|
||||
:database: state_machine_test
|
||||
:min_messages: ERROR
|
||||
mysql:
|
||||
:adapter: mysql
|
||||
:host: localhost
|
||||
:username: rails
|
||||
:password:
|
||||
:database: state_machine_test
|
||||
67
vendor/plugins/acts_as_state_machine/test/fixtures/conversation.rb
vendored
Normal file
67
vendor/plugins/acts_as_state_machine/test/fixtures/conversation.rb
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
class Conversation < ActiveRecord::Base
|
||||
attr_writer :can_close
|
||||
attr_accessor :read_enter, :read_exit, :read_after_first, :read_after_second,
|
||||
:closed_after, :needs_attention_enter, :needs_attention_after
|
||||
|
||||
acts_as_state_machine :initial => :needs_attention, :column => 'state_machine'
|
||||
|
||||
state :needs_attention, :enter => Proc.new { |o| o.needs_attention_enter = true },
|
||||
:after => Proc.new { |o| o.needs_attention_after = true }
|
||||
|
||||
state :read, :enter => :read_enter_action,
|
||||
:exit => Proc.new { |o| o.read_exit = true },
|
||||
:after => [:read_after_first_action, :read_after_second_action]
|
||||
|
||||
state :closed, :after => :closed_after_action
|
||||
state :awaiting_response
|
||||
state :junk
|
||||
|
||||
event :new_message do
|
||||
transitions :to => :needs_attention, :from => [:read, :closed, :awaiting_response]
|
||||
end
|
||||
|
||||
event :view do
|
||||
transitions :to => :read, :from => [:needs_attention, :read]
|
||||
end
|
||||
|
||||
event :reply do
|
||||
transitions :to => :awaiting_response, :from => [:read, :closed]
|
||||
end
|
||||
|
||||
event :close do
|
||||
transitions :to => :closed, :from => [:read, :awaiting_response], :guard => Proc.new {|o| o.can_close?}
|
||||
transitions :to => :read, :from => [:read, :awaiting_response], :guard => :always_true
|
||||
end
|
||||
|
||||
event :junk, :note => "finished" do
|
||||
transitions :to => :junk, :from => [:read, :closed, :awaiting_response]
|
||||
end
|
||||
|
||||
event :unjunk do
|
||||
transitions :to => :closed, :from => :junk
|
||||
end
|
||||
|
||||
def can_close?
|
||||
@can_close
|
||||
end
|
||||
|
||||
def read_enter_action
|
||||
self.read_enter = true
|
||||
end
|
||||
|
||||
def always_true
|
||||
true
|
||||
end
|
||||
|
||||
def read_after_first_action
|
||||
self.read_after_first = true
|
||||
end
|
||||
|
||||
def read_after_second_action
|
||||
self.read_after_second = true
|
||||
end
|
||||
|
||||
def closed_after_action
|
||||
self.closed_after = true
|
||||
end
|
||||
end
|
||||
11
vendor/plugins/acts_as_state_machine/test/fixtures/conversations.yml
vendored
Normal file
11
vendor/plugins/acts_as_state_machine/test/fixtures/conversations.yml
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
first:
|
||||
id: 1
|
||||
state_machine: read
|
||||
subject: This is a test
|
||||
closed: false
|
||||
|
||||
second:
|
||||
id: 2
|
||||
state_machine: read
|
||||
subject: Foo
|
||||
closed: false
|
||||
2
vendor/plugins/acts_as_state_machine/test/fixtures/person.rb
vendored
Normal file
2
vendor/plugins/acts_as_state_machine/test/fixtures/person.rb
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
class Person < ActiveRecord::Base
|
||||
end
|
||||
11
vendor/plugins/acts_as_state_machine/test/schema.rb
vendored
Normal file
11
vendor/plugins/acts_as_state_machine/test/schema.rb
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
ActiveRecord::Schema.define(:version => 1) do
|
||||
create_table :conversations, :force => true do |t|
|
||||
t.column :state_machine, :string
|
||||
t.column :subject, :string
|
||||
t.column :closed, :boolean
|
||||
end
|
||||
|
||||
create_table :people, :force => true do |t|
|
||||
t.column :name, :string
|
||||
end
|
||||
end
|
||||
38
vendor/plugins/acts_as_state_machine/test/test_helper.rb
vendored
Normal file
38
vendor/plugins/acts_as_state_machine/test/test_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
RAILS_ROOT = File.dirname(__FILE__)
|
||||
|
||||
require 'rubygems'
|
||||
require 'test/unit'
|
||||
require 'active_record'
|
||||
require 'active_record/fixtures'
|
||||
require 'active_support/binding_of_caller'
|
||||
require 'active_support/breakpoint'
|
||||
require "#{File.dirname(__FILE__)}/../init"
|
||||
|
||||
|
||||
config = YAML::load(IO.read(File.dirname(__FILE__) + '/database.yml'))
|
||||
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
||||
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'sqlite'])
|
||||
|
||||
load(File.dirname(__FILE__) + "/schema.rb") if File.exist?(File.dirname(__FILE__) + "/schema.rb")
|
||||
|
||||
Test::Unit::TestCase.fixture_path = File.dirname(__FILE__) + "/fixtures/"
|
||||
$LOAD_PATH.unshift(Test::Unit::TestCase.fixture_path)
|
||||
|
||||
class Test::Unit::TestCase #:nodoc:
|
||||
def create_fixtures(*table_names)
|
||||
if block_given?
|
||||
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names) { yield }
|
||||
else
|
||||
Fixtures.create_fixtures(Test::Unit::TestCase.fixture_path, table_names)
|
||||
end
|
||||
end
|
||||
|
||||
# Turn off transactional fixtures if you're working with MyISAM tables in MySQL
|
||||
self.use_transactional_fixtures = true
|
||||
|
||||
# Instantiated fixtures are slow, but give you @david where you otherwise would need people(:david)
|
||||
self.use_instantiated_fixtures = false
|
||||
|
||||
# Add more helper methods to be used by all tests here...
|
||||
end
|
||||
28
vendor/plugins/arts/README
vendored
Normal file
28
vendor/plugins/arts/README
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
ARTS is Another RJS Test System
|
||||
|
||||
For a complete tutorial, see http://glu.ttono.us/articles/2006/05/29/guide-test-driven-rjs-with-arts.
|
||||
|
||||
Usage:
|
||||
assert_rjs :alert, 'Hi!'
|
||||
assert_rjs :assign, 'a', '2'
|
||||
assert_rjs :call, 'foo', 'bar', 'baz'
|
||||
assert_rjs :draggable, 'draggable_item'
|
||||
assert_rjs :drop_receiving, 'receiving_item'
|
||||
assert_rjs :hide, "post_1", "post_2", "post_3"
|
||||
assert_rjs :insert_html, :bottom, 'posts'
|
||||
assert_rjs :redirect_to, :action => 'list'
|
||||
assert_rjs :remove, "post_1", "post_2", "post_3"
|
||||
assert_rjs :replace, 'completely_replaced_div'
|
||||
assert_rjs :replace, 'completely_replaced_div', '<p>This replaced the div</p>'
|
||||
assert_rjs :replace, 'completely_replaced_div', /replaced the div/
|
||||
assert_rjs :replace_html, 'replaceable_div', "This goes inside the div"
|
||||
assert_rjs :show, "post_1", "post_2", "post_3"
|
||||
assert_rjs :sortable, 'sortable_item'
|
||||
assert_rjs :toggle, "post_1", "post_2", "post_3"
|
||||
assert_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
|
||||
|
||||
For the square bracket syntax (page['some_id'].toggle) use :page followed by the id and then subsequent method calls. Assignment requires a '=' at the end of the method name followed by the value.
|
||||
|
||||
assert_rjs :page, 'some_id', :toggle
|
||||
assert_rjs :page, 'some_id', :style, :color=, 'red'
|
||||
|
||||
7
vendor/plugins/arts/about.yml
vendored
Normal file
7
vendor/plugins/arts/about.yml
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
author: Kevin Clark
|
||||
summary: RJS Assertion Plugin
|
||||
homepage: http://glu.ttono.us
|
||||
plugin:
|
||||
version: 0.6
|
||||
license: MIT
|
||||
rails_version: 1.1.2+
|
||||
3
vendor/plugins/arts/init.rb
vendored
Normal file
3
vendor/plugins/arts/init.rb
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# Give testing some culture
|
||||
require 'test/unit/testcase'
|
||||
Test::Unit::TestCase.send :include, Arts
|
||||
1
vendor/plugins/arts/install.rb
vendored
Normal file
1
vendor/plugins/arts/install.rb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
puts IO.read(File.join(File.dirname(__FILE__), 'README'))
|
||||
133
vendor/plugins/arts/lib/arts.rb
vendored
Normal file
133
vendor/plugins/arts/lib/arts.rb
vendored
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
module Arts
|
||||
include ActionView::Helpers::PrototypeHelper
|
||||
include ActionView::Helpers::ScriptaculousHelper
|
||||
include ActionView::Helpers::JavaScriptHelper
|
||||
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def assert_rjs(action, *args, &block)
|
||||
respond_to?("assert_rjs_#{action}") ?
|
||||
send("assert_rjs_#{action}", *args) :
|
||||
assert_response_contains(create_generator.send(action, *args, &block),
|
||||
generic_error(action, args))
|
||||
end
|
||||
|
||||
def assert_no_rjs(action, *args, &block)
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs(action, *args, &block) }
|
||||
end
|
||||
|
||||
def assert_rjs_insert_html(*args)
|
||||
position = args.shift
|
||||
item_id = args.shift
|
||||
|
||||
content = extract_matchable_content(args)
|
||||
|
||||
unless content.blank?
|
||||
case content
|
||||
when Regexp
|
||||
assert_match Regexp.new("new Insertion\.#{position.to_s.camelize}(.*#{item_id}.*,.*#{content.source}.*);"),
|
||||
@response.body
|
||||
when String
|
||||
assert_response_contains("new Insertion.#{position.to_s.camelize}(\"#{item_id}\", #{content});",
|
||||
"No insert_html call found for \n" +
|
||||
" position: '#{position}' id: '#{item_id}' \ncontent: \n" +
|
||||
"#{content}\n" +
|
||||
"in response:\n#{@response.body}")
|
||||
else
|
||||
raise "Invalid content type"
|
||||
end
|
||||
else
|
||||
assert_match Regexp.new("new Insertion\.#{position.to_s.camelize}(.*#{item_id}.*,.*?);"),
|
||||
@response.body
|
||||
end
|
||||
end
|
||||
|
||||
def assert_rjs_replace_html(*args)
|
||||
div = args.shift
|
||||
content = extract_matchable_content(args)
|
||||
|
||||
unless content.blank?
|
||||
case content
|
||||
when Regexp
|
||||
assert_match Regexp.new("Element.update(.*#{div}.*,.*#{content.source}.*);"),
|
||||
@response.body
|
||||
when String
|
||||
assert_response_contains("Element.update(\"#{div}\", #{content});",
|
||||
"No replace_html call found on div: '#{div}' and content: \n#{content}\n" +
|
||||
"in response:\n#{@response.body}")
|
||||
else
|
||||
raise "Invalid content type"
|
||||
end
|
||||
else
|
||||
assert_match Regexp.new("Element.update(.*#{div}.*,.*?);"), @response.body
|
||||
end
|
||||
end
|
||||
|
||||
def assert_rjs_replace(*args)
|
||||
div = args.shift
|
||||
content = extract_matchable_content(args)
|
||||
|
||||
unless content.blank?
|
||||
case content
|
||||
when Regexp
|
||||
assert_match Regexp.new("Element.replace(.*#{div}.*,.*#{content.source}.*);"),
|
||||
@response.body
|
||||
when String
|
||||
assert_response_contains("Element.replace(\"#{div}\", #{content});",
|
||||
"No replace call found on div: '#{div}' and content: \n#{content}\n" +
|
||||
"in response:\n#{@response.body}")
|
||||
else
|
||||
raise "Invalid content type"
|
||||
end
|
||||
else
|
||||
assert_match Regexp.new("Element.replace(.*#{div}.*,.*?);"), @response.body
|
||||
end
|
||||
end
|
||||
|
||||
# To deal with [] syntax. I hate JavaScriptProxy so.. SO very much
|
||||
def assert_rjs_page(*args)
|
||||
content = build_method_chain!(args)
|
||||
assert_match Regexp.new(Regexp.escape(content)), @response.body,
|
||||
"Content did not include:\n #{content.to_s}"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def assert_response_contains(str, message)
|
||||
assert @response.body.to_s.index(str), message
|
||||
end
|
||||
|
||||
def build_method_chain!(args)
|
||||
content = create_generator.send(:[], args.shift) # start $('some_id')....
|
||||
|
||||
while !args.empty?
|
||||
if (method = args.shift.to_s) =~ /(.*)=$/
|
||||
content = content.__send__(method, args.shift)
|
||||
break
|
||||
else
|
||||
content = content.__send__(method)
|
||||
content = content.__send__(:function_chain).first if args.empty?
|
||||
end
|
||||
end
|
||||
|
||||
content
|
||||
end
|
||||
|
||||
def create_generator
|
||||
block = Proc.new { |*args| yield *args if block_given? }
|
||||
JavaScriptGenerator.new self, &block
|
||||
end
|
||||
|
||||
def generic_error(action, args)
|
||||
"#{action} with args [#{args.join(" ")}] does not show up in response:\n#{@response.body}"
|
||||
end
|
||||
|
||||
def extract_matchable_content(args)
|
||||
if args.size == 1 and args.first.is_a? Regexp
|
||||
return args.first
|
||||
else
|
||||
return create_generator.send(:arguments_for_call, args)
|
||||
end
|
||||
end
|
||||
end
|
||||
402
vendor/plugins/arts/test/arts_test.rb
vendored
Normal file
402
vendor/plugins/arts/test/arts_test.rb
vendored
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require File.dirname(__FILE__) + '/../../../../config/environment'
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'breakpoint'
|
||||
|
||||
require 'action_controller/test_process'
|
||||
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Base.ignore_missing_templates = false
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
class ArtsController < ActionController::Base
|
||||
def alert
|
||||
render :update do |page|
|
||||
page.alert 'This is an alert'
|
||||
end
|
||||
end
|
||||
|
||||
def assign
|
||||
render :update do |page|
|
||||
page.assign 'a', '2'
|
||||
end
|
||||
end
|
||||
|
||||
def call
|
||||
render :update do |page|
|
||||
page.call 'foo', 'bar', 'baz'
|
||||
end
|
||||
end
|
||||
|
||||
def draggable
|
||||
render :update do |page|
|
||||
page.draggable 'my_image', :revert => true
|
||||
end
|
||||
end
|
||||
|
||||
def drop_receiving
|
||||
render :update do |page|
|
||||
page.drop_receiving "my_cart", :url => { :controller => "cart", :action => "add" }
|
||||
end
|
||||
end
|
||||
|
||||
def hide
|
||||
render :update do |page|
|
||||
page.hide 'some_div'
|
||||
end
|
||||
end
|
||||
|
||||
def insert_html
|
||||
render :update do |page|
|
||||
page.insert_html :bottom, 'content', 'Stuff in the content div'
|
||||
end
|
||||
end
|
||||
|
||||
def redirect
|
||||
render :update do |page|
|
||||
page.redirect_to :controller => 'sample', :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
def remove
|
||||
render :update do |page|
|
||||
page.remove 'offending_div'
|
||||
end
|
||||
end
|
||||
|
||||
def replace
|
||||
render :update do |page|
|
||||
page.replace 'person_45', '<div>This replaces person_45</div>'
|
||||
end
|
||||
end
|
||||
|
||||
def replace_html
|
||||
render :update do |page|
|
||||
page.replace_html 'person_45', 'This goes inside person_45'
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
render :update do |page|
|
||||
page.show 'post_1', 'post_2', 'post_3'
|
||||
end
|
||||
end
|
||||
|
||||
def sortable
|
||||
render :update do |page|
|
||||
page.sortable 'sortable_item'
|
||||
end
|
||||
end
|
||||
|
||||
def toggle
|
||||
render :update do |page|
|
||||
page.toggle "post_1", "post_2", "post_3"
|
||||
end
|
||||
end
|
||||
|
||||
def visual_effect
|
||||
render :update do |page|
|
||||
page.visual_effect :highlight, "posts", :duration => '1.0'
|
||||
end
|
||||
end
|
||||
|
||||
def page_with_one_chained_method
|
||||
render :update do |page|
|
||||
page['some_id'].toggle
|
||||
end
|
||||
end
|
||||
|
||||
def page_with_assignment
|
||||
render :update do |page|
|
||||
page['some_id'].style.color = 'red'
|
||||
end
|
||||
end
|
||||
|
||||
def rescue_errors(e) raise e end
|
||||
|
||||
end
|
||||
|
||||
class ArtsTest < Test::Unit::TestCase
|
||||
def setup
|
||||
@controller = ArtsController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
end
|
||||
|
||||
def test_alert
|
||||
get :alert
|
||||
|
||||
assert_nothing_raised { assert_rjs :alert, 'This is an alert' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :alert, 'This is not an alert'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :alert, 'This is not an alert' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :alert, 'This is an alert'
|
||||
end
|
||||
end
|
||||
|
||||
def test_assign
|
||||
get :assign
|
||||
|
||||
assert_nothing_raised { assert_rjs :assign, 'a', '2' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :assign, 'a', '3'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :assign, 'a', '3' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :assign, 'a', '2'
|
||||
end
|
||||
end
|
||||
|
||||
def test_call
|
||||
get :call
|
||||
|
||||
assert_nothing_raised { assert_rjs :call, 'foo', 'bar', 'baz' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :call, 'foo', 'bar'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :call, 'foo', 'bar' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :call, 'foo', 'bar', 'baz'
|
||||
end
|
||||
end
|
||||
|
||||
def test_draggable
|
||||
get :draggable
|
||||
|
||||
assert_nothing_raised { assert_rjs :draggable, 'my_image', :revert => true }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :draggable, 'not_my_image'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :draggable, 'not_my_image' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :draggable, 'my_image', :revert => true
|
||||
end
|
||||
end
|
||||
|
||||
def test_drop_receiving
|
||||
get :drop_receiving
|
||||
|
||||
assert_nothing_raised { assert_rjs :drop_receiving, "my_cart", :url => { :controller => "cart", :action => "add" } }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :drop_receiving, "my_cart"
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :drop_receiving, "my_cart" }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :drop_receiving, "my_cart", :url => { :controller => "cart", :action => "add" }
|
||||
end
|
||||
end
|
||||
|
||||
def test_hide
|
||||
get :hide
|
||||
|
||||
assert_nothing_raised { assert_rjs :hide, 'some_div' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :hide, 'some_other_div'
|
||||
end
|
||||
|
||||
assert_nothing_raised { assert_no_rjs :hide, 'not_some_div' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :hide, 'some_div'
|
||||
end
|
||||
end
|
||||
|
||||
def test_insert_html
|
||||
get :insert_html
|
||||
|
||||
|
||||
assert_nothing_raised do
|
||||
# No content matching
|
||||
assert_rjs :insert_html, :bottom, 'content'
|
||||
# Exact content matching
|
||||
assert_rjs :insert_html, :bottom, 'content', 'Stuff in the content div'
|
||||
# Regex matching
|
||||
assert_rjs :insert_html, :bottom, 'content', /in.*content/
|
||||
|
||||
assert_no_rjs :insert_html, :bottom, 'not_our_div'
|
||||
|
||||
assert_no_rjs :insert_html, :bottom, 'content', /in.*no content/
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :insert_html, :bottom, 'content'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :insert_html, :bottom, 'no_content'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :insert_html, :bottom, 'content', /in the/
|
||||
end
|
||||
end
|
||||
|
||||
def test_redirect_to
|
||||
get :redirect
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_rjs :redirect_to, :controller => 'sample', :action => 'index'
|
||||
assert_no_rjs :redirect_to, :controller => 'sample', :action => 'show'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :redirect_to, :controller => 'doesnt', :action => 'exist'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :redirect_to, :controller => 'sample', :action => 'index'
|
||||
end
|
||||
end
|
||||
|
||||
def test_remove
|
||||
get :remove
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_rjs :remove, 'offending_div'
|
||||
assert_no_rjs :remove, 'dancing_happy_div'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :remove, 'dancing_happy_div'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :remove, 'offending_div'
|
||||
end
|
||||
end
|
||||
|
||||
def test_replace
|
||||
get :replace
|
||||
|
||||
assert_nothing_raised do
|
||||
# No content matching
|
||||
assert_rjs :replace, 'person_45'
|
||||
# String content matching
|
||||
assert_rjs :replace, 'person_45', '<div>This replaces person_45</div>'
|
||||
# regexp content matching
|
||||
assert_rjs :replace, 'person_45', /<div>.*person_45.*<\/div>/
|
||||
|
||||
assert_no_rjs :replace, 'person_45', '<div>This replaces person_46</div>'
|
||||
|
||||
assert_no_rjs :replace, 'person_45', /person_46/
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :replace, 'person_45' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :replace, 'person_45', /person_45/ }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace, 'person_46' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace, 'person_45', 'bad stuff' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace, 'person_45', /not there/}
|
||||
end
|
||||
|
||||
def test_replace_html
|
||||
get :replace_html
|
||||
|
||||
assert_nothing_raised do
|
||||
# No content matching
|
||||
assert_rjs :replace_html, 'person_45'
|
||||
# String content matching
|
||||
assert_rjs :replace_html, 'person_45', 'This goes inside person_45'
|
||||
# Regexp content matching
|
||||
assert_rjs :replace_html, 'person_45', /goes inside/
|
||||
|
||||
assert_no_rjs :replace_html, 'person_46'
|
||||
|
||||
assert_no_rjs :replace_html, 'person_45', /doesn't go inside/
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :replace_html, 'person_45' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :replace_html, 'person_45', /goes/ }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace_html, 'person_46' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :replace_html, 'person_45', /gos inside/ }
|
||||
end
|
||||
|
||||
def test_show
|
||||
get :show
|
||||
assert_nothing_raised do
|
||||
assert_rjs :show, "post_1", "post_2", "post_3"
|
||||
assert_no_rjs :show, 'post_4'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :show, 'post_4' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :show, "post_1", "post_2", "post_3"
|
||||
end
|
||||
end
|
||||
|
||||
def test_sortable
|
||||
get :sortable
|
||||
assert_nothing_raised do
|
||||
assert_rjs :sortable, 'sortable_item'
|
||||
assert_no_rjs :sortable, 'non-sortable-item'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :sortable, 'non-sortable-item' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_no_rjs :sortable, 'sortable_item' }
|
||||
end
|
||||
|
||||
def test_toggle
|
||||
get :toggle
|
||||
assert_nothing_raised do
|
||||
assert_rjs :toggle, "post_1", "post_2", "post_3"
|
||||
assert_no_rjs :toggle, 'post_4'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) { assert_rjs :toggle, 'post_4' }
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :toggle, "post_1", "post_2", "post_3"
|
||||
end
|
||||
end
|
||||
|
||||
def test_visual_effect
|
||||
get :visual_effect
|
||||
assert_nothing_raised do
|
||||
assert_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
|
||||
assert_no_rjs :visual_effect, :highlight, "lists"
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :visual_effect, :highlight, "lists"
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :visual_effect, :highlight, "posts", :duration => '1.0'
|
||||
end
|
||||
end
|
||||
|
||||
# [] support
|
||||
|
||||
def test_page_with_one_chained_method
|
||||
get :page_with_one_chained_method
|
||||
assert_nothing_raised do
|
||||
assert_rjs :page, 'some_id', :toggle
|
||||
assert_no_rjs :page, 'some_other_id', :toggle
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_rjs :page, 'some_other_id', :toggle
|
||||
assert_no_rjs :page, 'some_id', :toggle
|
||||
end
|
||||
end
|
||||
|
||||
def test_page_with_assignment
|
||||
get :page_with_assignment
|
||||
|
||||
assert_nothing_raised do
|
||||
assert_rjs :page, 'some_id', :style, :color=, 'red'
|
||||
assert_no_rjs :page, 'some_id', :color=, 'red'
|
||||
end
|
||||
|
||||
assert_raises(Test::Unit::AssertionFailedError) do
|
||||
assert_no_rjs :page, 'some_id', :style, :color=, 'red'
|
||||
assert_rjs :page, 'some_other_id', :style, :color=, 'red'
|
||||
end
|
||||
end
|
||||
end
|
||||
122
vendor/plugins/asset_packager/CHANGELOG
vendored
Normal file
122
vendor/plugins/asset_packager/CHANGELOG
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
------------------------------------------------------------------------
|
||||
r52 | sbecker | 2007-11-04 01:38:21 -0400 (Sun, 04 Nov 2007) | 3 lines
|
||||
|
||||
* Allow configuration of which environments the helpers should merge scripts with the Synthesis::AssetPackage.merge_environments variable.
|
||||
* Refactored tests so they can all run together, and not depend on what the RAILS_ENV constant is.
|
||||
* Only add file extension if it was explicitly passed in, fixes other helpers in rails.
|
||||
------------------------------------------------------------------------
|
||||
r51 | sbecker | 2007-10-26 16:24:48 -0400 (Fri, 26 Oct 2007) | 1 line
|
||||
|
||||
* Updated jsmin.rb to latest version from 2007-07-20
|
||||
------------------------------------------------------------------------
|
||||
r50 | sbecker | 2007-10-23 23:16:07 -0400 (Tue, 23 Oct 2007) | 1 line
|
||||
|
||||
Updated CHANGELOG
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r49 | sbecker | 2007-10-23 23:13:27 -0400 (Tue, 23 Oct 2007) | 1 line
|
||||
|
||||
* Finally committed the subdirectory patch. (Thanks James Coglan!)
|
||||
------------------------------------------------------------------------
|
||||
r48 | sbecker | 2007-10-15 15:10:43 -0400 (Mon, 15 Oct 2007) | 1 line
|
||||
|
||||
* Speed up rake tasks and remove rails environment dependencies
|
||||
------------------------------------------------------------------------
|
||||
r43 | sbecker | 2007-07-02 15:30:29 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* Updated the docs regarding testing.
|
||||
------------------------------------------------------------------------
|
||||
r42 | sbecker | 2007-07-02 15:27:00 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* For production helper test, build packages once - on first setup.
|
||||
------------------------------------------------------------------------
|
||||
r41 | sbecker | 2007-07-02 15:14:13 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* Put build_all in test setup and delete_all in test teardown so all tests will pass the on first run of test suite.
|
||||
------------------------------------------------------------------------
|
||||
r40 | sbecker | 2007-07-02 14:55:28 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* Fix quotes, add contact info
|
||||
------------------------------------------------------------------------
|
||||
r39 | sbecker | 2007-07-02 14:53:52 -0400 (Mon, 02 Jul 2007) | 1 line
|
||||
|
||||
* Add note on how to run the tests for asset packager.
|
||||
------------------------------------------------------------------------
|
||||
r38 | sbecker | 2007-01-25 15:36:42 -0500 (Thu, 25 Jan 2007) | 1 line
|
||||
|
||||
added CHANGELOG w/ subversion log entries
|
||||
------------------------------------------------------------------------
|
||||
r37 | sbecker | 2007-01-25 15:34:39 -0500 (Thu, 25 Jan 2007) | 1 line
|
||||
|
||||
updated jsmin with new version from 2007-01-23
|
||||
------------------------------------------------------------------------
|
||||
r35 | sbecker | 2007-01-15 19:22:16 -0500 (Mon, 15 Jan 2007) | 1 line
|
||||
|
||||
require synthesis/asset_package in rake tasks, as Rails 1.2 seems to necessitate
|
||||
------------------------------------------------------------------------
|
||||
r34 | sbecker | 2007-01-05 12:22:09 -0500 (Fri, 05 Jan 2007) | 1 line
|
||||
|
||||
do a require before including in action view, because when running migrations, the plugin lib files don't automatically get required, causing the include to error out
|
||||
------------------------------------------------------------------------
|
||||
r33 | sbecker | 2006-12-23 02:03:41 -0500 (Sat, 23 Dec 2006) | 1 line
|
||||
|
||||
updating readme with various tweaks
|
||||
------------------------------------------------------------------------
|
||||
r32 | sbecker | 2006-12-23 02:03:12 -0500 (Sat, 23 Dec 2006) | 1 line
|
||||
|
||||
updating readme with various tweaks
|
||||
------------------------------------------------------------------------
|
||||
r31 | sbecker | 2006-12-23 01:52:25 -0500 (Sat, 23 Dec 2006) | 1 line
|
||||
|
||||
updated readme to show how to use different media for stylesheets
|
||||
------------------------------------------------------------------------
|
||||
r28 | sbecker | 2006-11-27 21:02:14 -0500 (Mon, 27 Nov 2006) | 1 line
|
||||
|
||||
updated compute_public_path, added check for images
|
||||
------------------------------------------------------------------------
|
||||
r27 | sbecker | 2006-11-10 18:28:29 -0500 (Fri, 10 Nov 2006) | 1 line
|
||||
|
||||
tolerate extra periods in source asset names. fixed subversion revision checking to be file specific, instead of repository specific.
|
||||
------------------------------------------------------------------------
|
||||
r26 | sbecker | 2006-06-24 17:04:27 -0400 (Sat, 24 Jun 2006) | 1 line
|
||||
|
||||
convert asset_packages_yml var to a class var
|
||||
------------------------------------------------------------------------
|
||||
r25 | sbecker | 2006-06-24 12:37:47 -0400 (Sat, 24 Jun 2006) | 1 line
|
||||
|
||||
Added ability to include assets by package name. In development, include all uncompressed asset files. In production, include the single compressed asset.
|
||||
------------------------------------------------------------------------
|
||||
r24 | sbecker | 2006-06-19 21:57:23 -0400 (Mon, 19 Jun 2006) | 1 line
|
||||
|
||||
Updates to README and about.yml
|
||||
------------------------------------------------------------------------
|
||||
r23 | sbecker | 2006-06-19 14:55:39 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
Modifying about.yml and README
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r21 | sbecker | 2006-06-19 12:18:32 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
added "formerly known as MergeJS"
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r20 | sbecker | 2006-06-19 12:14:46 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
Updating docs
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r19 | sbecker | 2006-06-19 11:26:08 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
removing compiled test assets from subversion
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r18 | sbecker | 2006-06-19 11:19:59 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
Initial import.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
r17 | sbecker | 2006-06-19 11:18:56 -0400 (Mon, 19 Jun 2006) | 2 lines
|
||||
|
||||
Creating directory.
|
||||
|
||||
------------------------------------------------------------------------
|
||||
173
vendor/plugins/asset_packager/README
vendored
Normal file
173
vendor/plugins/asset_packager/README
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
= AssetPackager
|
||||
|
||||
JavaScript and CSS Asset Compression for Production Rails Apps
|
||||
|
||||
== Description
|
||||
|
||||
When it comes time to deploy your new web application, instead of
|
||||
sending down a dozen JavaScript and CSS files full of formatting
|
||||
and comments, this Rails plugin makes it simple to merge and
|
||||
compress JavaScript and CSS down into one or more files, increasing
|
||||
speed and saving bandwidth.
|
||||
|
||||
When in development, it allows you to use your original versions
|
||||
and retain formatting and comments for readability and debugging.
|
||||
|
||||
Because not all browsers will dependably cache JavaScript and CSS
|
||||
files with query string parameters, AssetPackager writes a timestamp
|
||||
or subversion revision stamp (if available) into the merged file names.
|
||||
Therefore files are correctly cached by the browser AND your users
|
||||
always get the latest version when you re-deploy.
|
||||
|
||||
This code is released under the MIT license (like Ruby). You’re free
|
||||
to rip it up, enhance it, etc. And if you make any enhancements,
|
||||
I’d like to know so I can add them back in. Thanks!
|
||||
|
||||
* Formerly known as MergeJS.
|
||||
|
||||
== Credit
|
||||
|
||||
This Rails Plugin was inspired by Cal Henderson's article
|
||||
"Serving JavaScript Fast" on Vitamin:
|
||||
http://www.thinkvitamin.com/features/webapps/serving-javascript-fast
|
||||
|
||||
It also uses the Ruby JavaScript Minifier created by
|
||||
Douglas Crockford.
|
||||
http://www.crockford.com/javascript/jsmin.html
|
||||
|
||||
== Key Features
|
||||
|
||||
* Merges and compresses JavaScript and CSS when running in production.
|
||||
* Uses uncompressed originals when running in development.
|
||||
* Handles caching correctly. (No querystring parameters - filename timestamps)
|
||||
* Versions each package individually. Updates to files in one won't re-trigger downloading the others.
|
||||
* Uses subversion revision numbers instead of timestamps if within a subversion controlled directory.
|
||||
* Guarantees new version will get downloaded the next time you deploy.
|
||||
|
||||
== Components
|
||||
|
||||
* Rake Task for merging and compressing JavaScript and CSS files.
|
||||
* Helper functions for including these JavaScript and CSS files in your views.
|
||||
* YAML configuration file for mapping JavaScript and CSS files to merged versions.
|
||||
* Rake Task for auto-generating the YAML file from your existing JavaScript files.
|
||||
|
||||
== How to Use:
|
||||
|
||||
1. Download and install the plugin:
|
||||
./script/plugin install http://sbecker.net/shared/plugins/asset_packager
|
||||
|
||||
2. Run the rake task "asset:packager:create_yml" to generate the /config/asset_packages.yml
|
||||
file the first time. You will need to reorder files under 'base' so dependencies are loaded
|
||||
in correct order. Feel free to rename or create new file packages.
|
||||
|
||||
IMPORTANT: JavaScript files can break once compressed if each statement doesn't end with a semi-colon.
|
||||
The minifier puts multiple statements on one line, so if the semi-colon is missing, the statement may no
|
||||
longer makes sense and cause a syntax error.
|
||||
|
||||
Example from a fresh rails app after running the rake task. (Stylesheets is blank because a
|
||||
default rails app has no stylesheets yet.):
|
||||
|
||||
---
|
||||
javascripts:
|
||||
- base:
|
||||
- prototype
|
||||
- effects
|
||||
- dragdrop
|
||||
- controls
|
||||
- application
|
||||
stylesheets:
|
||||
- base: []
|
||||
|
||||
Example with multiple merged files:
|
||||
|
||||
---
|
||||
javascripts:
|
||||
- base:
|
||||
- prototype
|
||||
- effects
|
||||
- controls
|
||||
- dragdrop
|
||||
- application
|
||||
- secondary:
|
||||
- foo
|
||||
- bar
|
||||
stylesheets:
|
||||
- base:
|
||||
- screen
|
||||
- header
|
||||
- secondary:
|
||||
- foo
|
||||
- bar
|
||||
|
||||
3. Run the rake task "asset:packager:build_all" to generate the compressed, merged versions
|
||||
for each package. Whenever you rearrange the yaml file, you'll need to run this task again.
|
||||
Merging and compressing is expensive, so this is something we want to do once, not every time
|
||||
your app starts. Thats why its a rake task.
|
||||
|
||||
4. Use the helper functions whenever including these files in your application. See below for examples.
|
||||
|
||||
5. Potential warning: css compressor function currently removes CSS comments. This might blow
|
||||
away some CSS hackery. To disable comment removal, comment out /lib/synthesis/asset_package.rb line 176.
|
||||
|
||||
== JavaScript Examples
|
||||
|
||||
Example call:
|
||||
<%= javascript_include_merged 'prototype', 'effects', 'controls', 'dragdrop', 'application', 'foo', 'bar' %>
|
||||
|
||||
In development, this generates:
|
||||
<script type="text/javascript" src="/javascripts/prototype.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/effects.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/controls.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/dragdrop.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/application.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/foo.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/bar.js"></script>
|
||||
|
||||
In production, this generates:
|
||||
<script type="text/javascript" src="/javascripts/base_1150571523.js"></script>
|
||||
<script type="text/javascript" src="/javascripts/secondary_1150729166.js"></script>
|
||||
|
||||
Now supports symbols and :defaults as well:
|
||||
<%= javascript_include_merged :defaults %>
|
||||
<%= javascript_include_merged :foo, :bar %>
|
||||
|
||||
== Stylesheet Examples
|
||||
|
||||
Example call:
|
||||
<%= stylesheet_link_merged 'screen', 'header' %>
|
||||
|
||||
In development, this generates:
|
||||
<link href="/stylesheets/screen.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
<link href="/stylesheets/header.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
|
||||
In production this generates:
|
||||
<link href="/stylesheets/base_1150729166.css" media="screen" rel="Stylesheet" type="text/css" />
|
||||
|
||||
== Different CSS Media
|
||||
|
||||
All options for stylesheet_link_tag still work, so if you want to specify a different media type:
|
||||
<%= stylesheet_link_merged :secondary, 'media' => 'print' %>
|
||||
|
||||
== Running the tests
|
||||
|
||||
So you want to run the tests eh? Ok, then listen:
|
||||
|
||||
This plugin has a full suite of tests. But since they
|
||||
depend on rails, it has to be run in the context of a
|
||||
rails app, in the vendor/plugins directory. Observe:
|
||||
|
||||
> rails newtestapp
|
||||
> cd newtestapp
|
||||
> ./script/plugin install http://sbecker.net/shared/plugins/asset_packager
|
||||
> cd vendor/plugins/asset_packager/
|
||||
> rake # all tests pass
|
||||
|
||||
== License
|
||||
Copyright (c) 2006 Scott Becker - http://synthesis.sbecker.net
|
||||
Contact Email: becker.scott@gmail.com
|
||||
|
||||
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.
|
||||
22
vendor/plugins/asset_packager/Rakefile
vendored
Normal file
22
vendor/plugins/asset_packager/Rakefile
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the asset_packager plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the asset_packager plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'AssetPackager'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
8
vendor/plugins/asset_packager/about.yml
vendored
Normal file
8
vendor/plugins/asset_packager/about.yml
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
author: Scott Becker
|
||||
name: AssetPackager
|
||||
summary: JavaScript and CSS Asset Compression for Production Rails Apps
|
||||
homepage: http://synthesis.sbecker.net/pages/asset_packager
|
||||
plugin: http://sbecker.net/shared/plugins/asset_packager
|
||||
license: MIT
|
||||
version: 0.2
|
||||
rails_version: 1.1.2+
|
||||
2
vendor/plugins/asset_packager/init.rb
vendored
Normal file
2
vendor/plugins/asset_packager/init.rb
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
require 'synthesis/asset_package_helper'
|
||||
ActionView::Base.send :include, Synthesis::AssetPackageHelper
|
||||
1
vendor/plugins/asset_packager/install.rb
vendored
Normal file
1
vendor/plugins/asset_packager/install.rb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
# Install hook code here
|
||||
205
vendor/plugins/asset_packager/lib/jsmin.rb
vendored
Normal file
205
vendor/plugins/asset_packager/lib/jsmin.rb
vendored
Normal file
|
|
@ -0,0 +1,205 @@
|
|||
#!/usr/bin/ruby
|
||||
# jsmin.rb 2007-07-20
|
||||
# Author: Uladzislau Latynski
|
||||
# This work is a translation from C to Ruby of jsmin.c published by
|
||||
# Douglas Crockford. Permission is hereby granted to use the Ruby
|
||||
# version under the same conditions as the jsmin.c on which it is
|
||||
# based.
|
||||
#
|
||||
# /* jsmin.c
|
||||
# 2003-04-21
|
||||
#
|
||||
# Copyright (c) 2002 Douglas Crockford (www.crockford.com)
|
||||
#
|
||||
# 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 shall be used for Good, not Evil.
|
||||
#
|
||||
# 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.
|
||||
|
||||
EOF = -1
|
||||
$theA = ""
|
||||
$theB = ""
|
||||
|
||||
# isAlphanum -- return true if the character is a letter, digit, underscore,
|
||||
# dollar sign, or non-ASCII character
|
||||
def isAlphanum(c)
|
||||
return false if !c || c == EOF
|
||||
return ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') ||
|
||||
(c >= 'A' && c <= 'Z') || c == '_' || c == '$' ||
|
||||
c == '\\' || c[0] > 126)
|
||||
end
|
||||
|
||||
# get -- return the next character from stdin. Watch out for lookahead. If
|
||||
# the character is a control character, translate it to a space or linefeed.
|
||||
def get()
|
||||
c = $stdin.getc
|
||||
return EOF if(!c)
|
||||
c = c.chr
|
||||
return c if (c >= " " || c == "\n" || c.unpack("c") == EOF)
|
||||
return "\n" if (c == "\r")
|
||||
return " "
|
||||
end
|
||||
|
||||
# Get the next character without getting it.
|
||||
def peek()
|
||||
lookaheadChar = $stdin.getc
|
||||
$stdin.ungetc(lookaheadChar)
|
||||
return lookaheadChar.chr
|
||||
end
|
||||
|
||||
# mynext -- get the next character, excluding comments.
|
||||
# peek() is used to see if a '/' is followed by a '/' or '*'.
|
||||
def mynext()
|
||||
c = get
|
||||
if (c == "/")
|
||||
if(peek == "/")
|
||||
while(true)
|
||||
c = get
|
||||
if (c <= "\n")
|
||||
return c
|
||||
end
|
||||
end
|
||||
end
|
||||
if(peek == "*")
|
||||
get
|
||||
while(true)
|
||||
case get
|
||||
when "*"
|
||||
if (peek == "/")
|
||||
get
|
||||
return " "
|
||||
end
|
||||
when EOF
|
||||
raise "Unterminated comment"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
return c
|
||||
end
|
||||
|
||||
|
||||
# action -- do something! What you do is determined by the argument: 1
|
||||
# Output A. Copy B to A. Get the next B. 2 Copy B to A. Get the next B.
|
||||
# (Delete A). 3 Get the next B. (Delete B). action treats a string as a
|
||||
# single character. Wow! action recognizes a regular expression if it is
|
||||
# preceded by ( or , or =.
|
||||
def action(a)
|
||||
if(a==1)
|
||||
$stdout.write $theA
|
||||
end
|
||||
if(a==1 || a==2)
|
||||
$theA = $theB
|
||||
if ($theA == "\'" || $theA == "\"")
|
||||
while (true)
|
||||
$stdout.write $theA
|
||||
$theA = get
|
||||
break if ($theA == $theB)
|
||||
raise "Unterminated string literal" if ($theA <= "\n")
|
||||
if ($theA == "\\")
|
||||
$stdout.write $theA
|
||||
$theA = get
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
if(a==1 || a==2 || a==3)
|
||||
$theB = mynext
|
||||
if ($theB == "/" && ($theA == "(" || $theA == "," || $theA == "=" ||
|
||||
$theA == ":" || $theA == "[" || $theA == "!" ||
|
||||
$theA == "&" || $theA == "|" || $theA == "?" ||
|
||||
$theA == "{" || $theA == "}" || $theA == ";" ||
|
||||
$theA == "\n"))
|
||||
$stdout.write $theA
|
||||
$stdout.write $theB
|
||||
while (true)
|
||||
$theA = get
|
||||
if ($theA == "/")
|
||||
break
|
||||
elsif ($theA == "\\")
|
||||
$stdout.write $theA
|
||||
$theA = get
|
||||
elsif ($theA <= "\n")
|
||||
raise "Unterminated RegExp Literal"
|
||||
end
|
||||
$stdout.write $theA
|
||||
end
|
||||
$theB = mynext
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# jsmin -- Copy the input to the output, deleting the characters which are
|
||||
# insignificant to JavaScript. Comments will be removed. Tabs will be
|
||||
# replaced with spaces. Carriage returns will be replaced with linefeeds.
|
||||
# Most spaces and linefeeds will be removed.
|
||||
def jsmin
|
||||
$theA = "\n"
|
||||
action(3)
|
||||
while ($theA != EOF)
|
||||
case $theA
|
||||
when " "
|
||||
if (isAlphanum($theB))
|
||||
action(1)
|
||||
else
|
||||
action(2)
|
||||
end
|
||||
when "\n"
|
||||
case ($theB)
|
||||
when "{","[","(","+","-"
|
||||
action(1)
|
||||
when " "
|
||||
action(3)
|
||||
else
|
||||
if (isAlphanum($theB))
|
||||
action(1)
|
||||
else
|
||||
action(2)
|
||||
end
|
||||
end
|
||||
else
|
||||
case ($theB)
|
||||
when " "
|
||||
if (isAlphanum($theA))
|
||||
action(1)
|
||||
else
|
||||
action(3)
|
||||
end
|
||||
when "\n"
|
||||
case ($theA)
|
||||
when "}","]",")","+","-","\"","\\", "'", '"'
|
||||
action(1)
|
||||
else
|
||||
if (isAlphanum($theA))
|
||||
action(1)
|
||||
else
|
||||
action(3)
|
||||
end
|
||||
end
|
||||
else
|
||||
action(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ARGV.each do |anArg|
|
||||
$stdout.write "// #{anArg}\n"
|
||||
end
|
||||
|
||||
jsmin
|
||||
233
vendor/plugins/asset_packager/lib/synthesis/asset_package.rb
vendored
Normal file
233
vendor/plugins/asset_packager/lib/synthesis/asset_package.rb
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
require 'yaml'
|
||||
|
||||
module Synthesis
|
||||
class AssetPackage
|
||||
|
||||
# class variables
|
||||
@@asset_packages_yml = $asset_packages_yml ||
|
||||
(File.exists?("#{RAILS_ROOT}/config/asset_packages.yml") ? YAML.load_file("#{RAILS_ROOT}/config/asset_packages.yml") : nil)
|
||||
|
||||
# singleton methods
|
||||
class << self
|
||||
|
||||
def merge_environments=(environments)
|
||||
@@merge_environments = environments
|
||||
end
|
||||
|
||||
def merge_environments
|
||||
@@merge_environments ||= ["production"]
|
||||
end
|
||||
|
||||
def parse_path(path)
|
||||
/^(?:(.*)\/)?([^\/]+)$/.match(path).to_a
|
||||
end
|
||||
|
||||
def find_by_type(asset_type)
|
||||
@@asset_packages_yml[asset_type].map { |p| self.new(asset_type, p) }
|
||||
end
|
||||
|
||||
def find_by_target(asset_type, target)
|
||||
package_hash = @@asset_packages_yml[asset_type].find {|p| p.keys.first == target }
|
||||
package_hash ? self.new(asset_type, package_hash) : nil
|
||||
end
|
||||
|
||||
def find_by_source(asset_type, source)
|
||||
path_parts = parse_path(source)
|
||||
package_hash = @@asset_packages_yml[asset_type].find do |p|
|
||||
key = p.keys.first
|
||||
p[key].include?(path_parts[2]) && (parse_path(key)[1] == path_parts[1])
|
||||
end
|
||||
package_hash ? self.new(asset_type, package_hash) : nil
|
||||
end
|
||||
|
||||
def targets_from_sources(asset_type, sources)
|
||||
package_names = Array.new
|
||||
sources.each do |source|
|
||||
package = find_by_target(asset_type, source) || find_by_source(asset_type, source)
|
||||
package_names << (package ? package.current_file : source)
|
||||
end
|
||||
package_names.uniq
|
||||
end
|
||||
|
||||
def sources_from_targets(asset_type, targets)
|
||||
source_names = Array.new
|
||||
targets.each do |target|
|
||||
package = find_by_target(asset_type, target)
|
||||
source_names += (package ? package.sources.collect do |src|
|
||||
package.target_dir.gsub(/^(.+)$/, '\1/') + src
|
||||
end : target.to_a)
|
||||
end
|
||||
source_names.uniq
|
||||
end
|
||||
|
||||
def build_all
|
||||
@@asset_packages_yml.keys.each do |asset_type|
|
||||
@@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).build }
|
||||
end
|
||||
end
|
||||
|
||||
def delete_all
|
||||
@@asset_packages_yml.keys.each do |asset_type|
|
||||
@@asset_packages_yml[asset_type].each { |p| self.new(asset_type, p).delete_all_builds }
|
||||
end
|
||||
end
|
||||
|
||||
def create_yml
|
||||
unless File.exists?("#{RAILS_ROOT}/config/asset_packages.yml")
|
||||
asset_yml = Hash.new
|
||||
|
||||
asset_yml['javascripts'] = [{"base" => build_file_list("#{RAILS_ROOT}/public/javascripts", "js")}]
|
||||
asset_yml['stylesheets'] = [{"base" => build_file_list("#{RAILS_ROOT}/public/stylesheets", "css")}]
|
||||
|
||||
File.open("#{RAILS_ROOT}/config/asset_packages.yml", "w") do |out|
|
||||
YAML.dump(asset_yml, out)
|
||||
end
|
||||
|
||||
log "config/asset_packages.yml example file created!"
|
||||
log "Please reorder files under 'base' so dependencies are loaded in correct order."
|
||||
else
|
||||
log "config/asset_packages.yml already exists. Aborting task..."
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
# instance methods
|
||||
attr_accessor :asset_type, :target, :target_dir, :sources
|
||||
|
||||
def initialize(asset_type, package_hash)
|
||||
target_parts = self.class.parse_path(package_hash.keys.first)
|
||||
@target_dir = target_parts[1].to_s
|
||||
@target = target_parts[2].to_s
|
||||
@sources = package_hash[package_hash.keys.first]
|
||||
@asset_type = asset_type
|
||||
@asset_path = ($asset_base_path ? "#{$asset_base_path}/" : "#{RAILS_ROOT}/public/") +
|
||||
"#{@asset_type}#{@target_dir.gsub(/^(.+)$/, '/\1')}"
|
||||
@extension = get_extension
|
||||
@match_regex = Regexp.new("\\A#{@target}_\\d+.#{@extension}\\z")
|
||||
end
|
||||
|
||||
def current_file
|
||||
@target_dir.gsub(/^(.+)$/, '\1/') +
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.sort.reverse[0].chomp(".#{@extension}")
|
||||
end
|
||||
|
||||
def build
|
||||
delete_old_builds
|
||||
create_new_build
|
||||
end
|
||||
|
||||
def delete_old_builds
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.each do |x|
|
||||
File.delete("#{@asset_path}/#{x}") unless x.index(revision.to_s)
|
||||
end
|
||||
end
|
||||
|
||||
def delete_all_builds
|
||||
Dir.new(@asset_path).entries.delete_if { |x| ! (x =~ @match_regex) }.each do |x|
|
||||
File.delete("#{@asset_path}/#{x}")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def revision
|
||||
unless @revision
|
||||
revisions = [1]
|
||||
@sources.each do |source|
|
||||
revisions << get_file_revision("#{@asset_path}/#{source}.#{@extension}")
|
||||
end
|
||||
@revision = revisions.max
|
||||
end
|
||||
@revision
|
||||
end
|
||||
|
||||
def get_file_revision(path)
|
||||
if File.exists?(path)
|
||||
begin
|
||||
`svn info #{path}`[/Last Changed Rev: (.*?)\n/][/(\d+)/].to_i
|
||||
rescue # use filename timestamp if not in subversion
|
||||
File.mtime(path).to_i
|
||||
end
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
def create_new_build
|
||||
if File.exists?("#{@asset_path}/#{@target}_#{revision}.#{@extension}")
|
||||
log "Latest version already exists: #{@asset_path}/#{@target}_#{revision}.#{@extension}"
|
||||
else
|
||||
File.open("#{@asset_path}/#{@target}_#{revision}.#{@extension}", "w") {|f| f.write(compressed_file) }
|
||||
log "Created #{@asset_path}/#{@target}_#{revision}.#{@extension}"
|
||||
end
|
||||
end
|
||||
|
||||
def merged_file
|
||||
merged_file = ""
|
||||
@sources.each {|s|
|
||||
File.open("#{@asset_path}/#{s}.#{@extension}", "r") { |f|
|
||||
merged_file += f.read + "\n"
|
||||
}
|
||||
}
|
||||
merged_file
|
||||
end
|
||||
|
||||
def compressed_file
|
||||
case @asset_type
|
||||
when "javascripts" then compress_js(merged_file)
|
||||
when "stylesheets" then compress_css(merged_file)
|
||||
end
|
||||
end
|
||||
|
||||
def compress_js(source)
|
||||
jsmin_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/lib"
|
||||
tmp_path = "#{RAILS_ROOT}/tmp/#{@target}_#{revision}"
|
||||
|
||||
# write out to a temp file
|
||||
File.open("#{tmp_path}_uncompressed.js", "w") {|f| f.write(source) }
|
||||
|
||||
# compress file with JSMin library
|
||||
`ruby #{jsmin_path}/jsmin.rb <#{tmp_path}_uncompressed.js >#{tmp_path}_compressed.js \n`
|
||||
|
||||
# read it back in and trim it
|
||||
result = ""
|
||||
File.open("#{tmp_path}_compressed.js", "r") { |f| result += f.read.strip }
|
||||
|
||||
# delete temp files if they exist
|
||||
File.delete("#{tmp_path}_uncompressed.js") if File.exists?("#{tmp_path}_uncompressed.js")
|
||||
File.delete("#{tmp_path}_compressed.js") if File.exists?("#{tmp_path}_compressed.js")
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def compress_css(source)
|
||||
source.gsub!(/\s+/, " ") # collapse space
|
||||
source.gsub!(/\/\*(.*?)\*\/ /, "") # remove comments - caution, might want to remove this if using css hacks
|
||||
source.gsub!(/\} /, "}\n") # add line breaks
|
||||
source.gsub!(/\n$/, "") # remove last break
|
||||
source.gsub!(/ \{ /, " {") # trim inside brackets
|
||||
source.gsub!(/; \}/, "}") # trim inside brackets
|
||||
source
|
||||
end
|
||||
|
||||
def get_extension
|
||||
case @asset_type
|
||||
when "javascripts" then "js"
|
||||
when "stylesheets" then "css"
|
||||
end
|
||||
end
|
||||
|
||||
def log(message)
|
||||
puts message
|
||||
end
|
||||
|
||||
def self.build_file_list(path, extension)
|
||||
re = Regexp.new(".#{extension}\\z")
|
||||
file_list = Dir.new(path).entries.delete_if { |x| ! (x =~ re) }.map {|x| x.chomp(".#{extension}")}
|
||||
# reverse javascript entries so prototype comes first on a base rails app
|
||||
file_list.reverse! if extension == "js"
|
||||
file_list
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
67
vendor/plugins/asset_packager/lib/synthesis/asset_package_helper.rb
vendored
Normal file
67
vendor/plugins/asset_packager/lib/synthesis/asset_package_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
module Synthesis
|
||||
module AssetPackageHelper
|
||||
|
||||
def should_merge?
|
||||
AssetPackage.merge_environments.include?(RAILS_ENV)
|
||||
end
|
||||
|
||||
def javascript_include_merged(*sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
|
||||
if sources.include?(:defaults)
|
||||
sources = sources[0..(sources.index(:defaults))] +
|
||||
['prototype', 'effects', 'dragdrop', 'controls'] +
|
||||
(File.exists?("#{RAILS_ROOT}/public/javascripts/application.js") ? ['application'] : []) +
|
||||
sources[(sources.index(:defaults) + 1)..sources.length]
|
||||
sources.delete(:defaults)
|
||||
end
|
||||
|
||||
sources.collect!{|s| s.to_s}
|
||||
sources = (should_merge? ?
|
||||
AssetPackage.targets_from_sources("javascripts", sources) :
|
||||
AssetPackage.sources_from_targets("javascripts", sources))
|
||||
|
||||
sources.collect {|source| javascript_include_tag(source, options) }.join("\n")
|
||||
end
|
||||
|
||||
def stylesheet_link_merged(*sources)
|
||||
options = sources.last.is_a?(Hash) ? sources.pop.stringify_keys : { }
|
||||
|
||||
sources.collect!{|s| s.to_s}
|
||||
sources = (should_merge? ?
|
||||
AssetPackage.targets_from_sources("stylesheets", sources) :
|
||||
AssetPackage.sources_from_targets("stylesheets", sources))
|
||||
|
||||
sources.collect { |source|
|
||||
source = stylesheet_path(source)
|
||||
tag("link", { "rel" => "Stylesheet", "type" => "text/css", "media" => "screen", "href" => source }.merge(options))
|
||||
}.join("\n")
|
||||
end
|
||||
|
||||
private
|
||||
# rewrite compute_public_path to allow us to not include the query string timestamp
|
||||
# used by ActionView::Helpers::AssetTagHelper
|
||||
def compute_public_path(source, dir, ext=nil, add_asset_id=true)
|
||||
source = source.dup
|
||||
source << ".#{ext}" if File.extname(source).blank? && ext
|
||||
unless source =~ %r{^[-a-z]+://}
|
||||
source = "/#{dir}/#{source}" unless source[0] == ?/
|
||||
asset_id = rails_asset_id(source)
|
||||
source << '?' + asset_id if defined?(RAILS_ROOT) and add_asset_id and not asset_id.blank?
|
||||
source = "#{ActionController::Base.asset_host}#{@controller.request.relative_url_root}#{source}"
|
||||
end
|
||||
source
|
||||
end
|
||||
|
||||
# rewrite javascript path function to not include query string timestamp
|
||||
def javascript_path(source)
|
||||
compute_public_path(source, 'javascripts', 'js', false)
|
||||
end
|
||||
|
||||
# rewrite stylesheet path function to not include query string timestamp
|
||||
def stylesheet_path(source)
|
||||
compute_public_path(source, 'stylesheets', 'css', false)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
22
vendor/plugins/asset_packager/tasks/asset_packager_tasks.rake
vendored
Normal file
22
vendor/plugins/asset_packager/tasks/asset_packager_tasks.rake
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require File.dirname(__FILE__) + '/../lib/synthesis/asset_package'
|
||||
|
||||
namespace :asset do
|
||||
namespace :packager do
|
||||
|
||||
desc "Merge and compress assets"
|
||||
task :build_all do
|
||||
Synthesis::AssetPackage.build_all
|
||||
end
|
||||
|
||||
desc "Delete all asset builds"
|
||||
task :delete_all do
|
||||
Synthesis::AssetPackage.delete_all
|
||||
end
|
||||
|
||||
desc "Generate asset_packages.yml from existing assets"
|
||||
task :create_yml do
|
||||
Synthesis::AssetPackage.create_yml
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
107
vendor/plugins/asset_packager/test/asset_package_helper_development_test.rb
vendored
Normal file
107
vendor/plugins/asset_packager/test/asset_package_helper_development_test.rb
vendored
Normal file
|
|
@ -0,0 +1,107 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
ENV['RAILS_ENV'] = "development"
|
||||
require File.dirname(__FILE__) + '/../../../../config/environment'
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'mocha'
|
||||
|
||||
require 'action_controller/test_process'
|
||||
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Base.ignore_missing_templates = false
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
|
||||
$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
|
||||
|
||||
class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include Synthesis::AssetPackageHelper
|
||||
|
||||
def setup
|
||||
Synthesis::AssetPackage.any_instance.stubs(:log)
|
||||
|
||||
@controller = Class.new do
|
||||
attr_reader :request
|
||||
def initialize
|
||||
@request = Class.new do
|
||||
def relative_url_root
|
||||
""
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
end.new
|
||||
end
|
||||
|
||||
def build_js_expected_string(*sources)
|
||||
sources.map {|s| %(<script src="/javascripts/#{s}.js" type="text/javascript"></script>) }.join("\n")
|
||||
end
|
||||
|
||||
def build_css_expected_string(*sources)
|
||||
sources.map {|s| %(<link href="/stylesheets/#{s}.css" rel="Stylesheet" type="text/css" media="screen" />) }.join("\n")
|
||||
end
|
||||
|
||||
def test_js_basic
|
||||
assert_dom_equal build_js_expected_string("prototype"),
|
||||
javascript_include_merged("prototype")
|
||||
end
|
||||
|
||||
def test_js_multiple_packages
|
||||
assert_dom_equal build_js_expected_string("prototype", "foo"),
|
||||
javascript_include_merged("prototype", "foo")
|
||||
end
|
||||
|
||||
def test_js_unpackaged_file
|
||||
assert_dom_equal build_js_expected_string("prototype", "foo", "not_part_of_a_package"),
|
||||
javascript_include_merged("prototype", "foo", "not_part_of_a_package")
|
||||
end
|
||||
|
||||
def test_js_multiple_from_same_package
|
||||
assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "not_part_of_a_package", "foo"),
|
||||
javascript_include_merged("prototype", "effects", "controls", "not_part_of_a_package", "foo")
|
||||
end
|
||||
|
||||
def test_js_by_package_name
|
||||
assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "dragdrop"),
|
||||
javascript_include_merged(:base)
|
||||
end
|
||||
|
||||
def test_js_multiple_package_names
|
||||
assert_dom_equal build_js_expected_string("prototype", "effects", "controls", "dragdrop", "foo", "bar", "application"),
|
||||
javascript_include_merged(:base, :secondary)
|
||||
end
|
||||
|
||||
def test_css_basic
|
||||
assert_dom_equal build_css_expected_string("screen"),
|
||||
stylesheet_link_merged("screen")
|
||||
end
|
||||
|
||||
def test_css_multiple_packages
|
||||
assert_dom_equal build_css_expected_string("screen", "foo", "subdir/bar"),
|
||||
stylesheet_link_merged("screen", "foo", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_unpackaged_file
|
||||
assert_dom_equal build_css_expected_string("screen", "foo", "not_part_of_a_package", "subdir/bar"),
|
||||
stylesheet_link_merged("screen", "foo", "not_part_of_a_package", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_multiple_from_same_package
|
||||
assert_dom_equal build_css_expected_string("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar"),
|
||||
stylesheet_link_merged("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_by_package_name
|
||||
assert_dom_equal build_css_expected_string("screen", "header"),
|
||||
stylesheet_link_merged(:base)
|
||||
end
|
||||
|
||||
def test_css_multiple_package_names
|
||||
assert_dom_equal build_css_expected_string("screen", "header", "foo", "bar", "subdir/foo", "subdir/bar"),
|
||||
stylesheet_link_merged(:base, :secondary, "subdir/styles")
|
||||
end
|
||||
|
||||
end
|
||||
153
vendor/plugins/asset_packager/test/asset_package_helper_production_test.rb
vendored
Normal file
153
vendor/plugins/asset_packager/test/asset_package_helper_production_test.rb
vendored
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
$:.unshift(File.dirname(__FILE__) + '/../lib')
|
||||
|
||||
require File.dirname(__FILE__) + '/../../../../config/environment'
|
||||
require 'test/unit'
|
||||
require 'rubygems'
|
||||
require 'mocha'
|
||||
|
||||
require 'action_controller/test_process'
|
||||
|
||||
ActionController::Base.logger = nil
|
||||
ActionController::Base.ignore_missing_templates = false
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
|
||||
$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
|
||||
$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
|
||||
|
||||
class AssetPackageHelperProductionTest < Test::Unit::TestCase
|
||||
include ActionView::Helpers::TagHelper
|
||||
include ActionView::Helpers::AssetTagHelper
|
||||
include Synthesis::AssetPackageHelper
|
||||
|
||||
cattr_accessor :packages_built
|
||||
|
||||
def setup
|
||||
Synthesis::AssetPackage.any_instance.stubs(:log)
|
||||
self.stubs(:should_merge?).returns(true)
|
||||
|
||||
@controller = Class.new do
|
||||
|
||||
attr_reader :request
|
||||
def initialize
|
||||
@request = Class.new do
|
||||
def relative_url_root
|
||||
""
|
||||
end
|
||||
end.new
|
||||
end
|
||||
|
||||
end.new
|
||||
|
||||
build_packages_once
|
||||
end
|
||||
|
||||
def build_packages_once
|
||||
unless @@packages_built
|
||||
Synthesis::AssetPackage.build_all
|
||||
@@packages_built = true
|
||||
end
|
||||
end
|
||||
|
||||
def build_js_expected_string(*sources)
|
||||
sources.map {|s| %(<script src="/javascripts/#{s}.js" type="text/javascript"></script>) }.join("\n")
|
||||
end
|
||||
|
||||
def build_css_expected_string(*sources)
|
||||
sources.map {|s| %(<link href="/stylesheets/#{s}.css" rel="Stylesheet" type="text/css" media="screen" />) }.join("\n")
|
||||
end
|
||||
|
||||
def test_js_basic
|
||||
current_file = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
|
||||
assert_dom_equal build_js_expected_string(current_file),
|
||||
javascript_include_merged("prototype")
|
||||
end
|
||||
|
||||
def test_js_multiple_packages
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
|
||||
|
||||
assert_dom_equal build_js_expected_string(current_file1, current_file2),
|
||||
javascript_include_merged("prototype", "foo")
|
||||
end
|
||||
|
||||
def test_js_unpackaged_file
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
|
||||
|
||||
assert_dom_equal build_js_expected_string(current_file1, current_file2, "not_part_of_a_package"),
|
||||
javascript_include_merged("prototype", "foo", "not_part_of_a_package")
|
||||
end
|
||||
|
||||
def test_js_multiple_from_same_package
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("javascripts", "prototype").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("javascripts", "foo").current_file
|
||||
|
||||
assert_dom_equal build_js_expected_string(current_file1, "not_part_of_a_package", current_file2),
|
||||
javascript_include_merged("prototype", "effects", "controls", "not_part_of_a_package", "foo")
|
||||
end
|
||||
|
||||
def test_js_by_package_name
|
||||
package_name = Synthesis::AssetPackage.find_by_target("javascripts", "base").current_file
|
||||
assert_dom_equal build_js_expected_string(package_name),
|
||||
javascript_include_merged(:base)
|
||||
end
|
||||
|
||||
def test_js_multiple_package_names
|
||||
package_name1 = Synthesis::AssetPackage.find_by_target("javascripts", "base").current_file
|
||||
package_name2 = Synthesis::AssetPackage.find_by_target("javascripts", "secondary").current_file
|
||||
assert_dom_equal build_js_expected_string(package_name1, package_name2),
|
||||
javascript_include_merged(:base, :secondary)
|
||||
end
|
||||
|
||||
def test_css_basic
|
||||
current_file = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
|
||||
assert_dom_equal build_css_expected_string(current_file),
|
||||
stylesheet_link_merged("screen")
|
||||
end
|
||||
|
||||
def test_css_multiple_packages
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
|
||||
current_file3 = Synthesis::AssetPackage.find_by_source("stylesheets", "subdir/bar").current_file
|
||||
|
||||
assert_dom_equal build_css_expected_string(current_file1, current_file2, current_file3),
|
||||
stylesheet_link_merged("screen", "foo", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_unpackaged_file
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
|
||||
|
||||
assert_dom_equal build_css_expected_string(current_file1, current_file2, "not_part_of_a_package"),
|
||||
stylesheet_link_merged("screen", "foo", "not_part_of_a_package")
|
||||
end
|
||||
|
||||
def test_css_multiple_from_same_package
|
||||
current_file1 = Synthesis::AssetPackage.find_by_source("stylesheets", "screen").current_file
|
||||
current_file2 = Synthesis::AssetPackage.find_by_source("stylesheets", "foo").current_file
|
||||
current_file3 = Synthesis::AssetPackage.find_by_source("stylesheets", "subdir/bar").current_file
|
||||
|
||||
assert_dom_equal build_css_expected_string(current_file1, "not_part_of_a_package", current_file2, current_file3),
|
||||
stylesheet_link_merged("screen", "header", "not_part_of_a_package", "foo", "bar", "subdir/foo", "subdir/bar")
|
||||
end
|
||||
|
||||
def test_css_by_package_name
|
||||
package_name = Synthesis::AssetPackage.find_by_target("stylesheets", "base").current_file
|
||||
assert_dom_equal build_css_expected_string(package_name),
|
||||
stylesheet_link_merged(:base)
|
||||
end
|
||||
|
||||
def test_css_multiple_package_names
|
||||
package_name1 = Synthesis::AssetPackage.find_by_target("stylesheets", "base").current_file
|
||||
package_name2 = Synthesis::AssetPackage.find_by_target("stylesheets", "secondary").current_file
|
||||
package_name3 = Synthesis::AssetPackage.find_by_target("stylesheets", "subdir/styles").current_file
|
||||
assert_dom_equal build_css_expected_string(package_name1, package_name2, package_name3),
|
||||
stylesheet_link_merged(:base, :secondary, "subdir/styles")
|
||||
end
|
||||
|
||||
def test_image_tag
|
||||
timestamp = rails_asset_id("images/rails.png")
|
||||
assert_dom_equal %(<img alt="Rails" src="/images/rails.png?#{timestamp}" />), image_tag("rails")
|
||||
end
|
||||
|
||||
end
|
||||
92
vendor/plugins/asset_packager/test/asset_packager_test.rb
vendored
Normal file
92
vendor/plugins/asset_packager/test/asset_packager_test.rb
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
require File.dirname(__FILE__) + '/../../../../config/environment'
|
||||
require 'test/unit'
|
||||
require 'mocha'
|
||||
|
||||
$asset_packages_yml = YAML.load_file("#{RAILS_ROOT}/vendor/plugins/asset_packager/test/asset_packages.yml")
|
||||
$asset_base_path = "#{RAILS_ROOT}/vendor/plugins/asset_packager/test/assets"
|
||||
|
||||
class AssetPackagerTest < Test::Unit::TestCase
|
||||
include Synthesis
|
||||
|
||||
def setup
|
||||
Synthesis::AssetPackage.any_instance.stubs(:log)
|
||||
Synthesis::AssetPackage.build_all
|
||||
end
|
||||
|
||||
def teardown
|
||||
Synthesis::AssetPackage.delete_all
|
||||
end
|
||||
|
||||
def test_find_by_type
|
||||
js_asset_packages = Synthesis::AssetPackage.find_by_type("javascripts")
|
||||
assert_equal 2, js_asset_packages.length
|
||||
assert_equal "base", js_asset_packages[0].target
|
||||
assert_equal ["prototype", "effects", "controls", "dragdrop"], js_asset_packages[0].sources
|
||||
end
|
||||
|
||||
def test_find_by_target
|
||||
package = Synthesis::AssetPackage.find_by_target("javascripts", "base")
|
||||
assert_equal "base", package.target
|
||||
assert_equal ["prototype", "effects", "controls", "dragdrop"], package.sources
|
||||
end
|
||||
|
||||
def test_find_by_source
|
||||
package = Synthesis::AssetPackage.find_by_source("javascripts", "controls")
|
||||
assert_equal "base", package.target
|
||||
assert_equal ["prototype", "effects", "controls", "dragdrop"], package.sources
|
||||
end
|
||||
|
||||
def test_delete_and_build
|
||||
Synthesis::AssetPackage.delete_all
|
||||
js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.js/) }
|
||||
css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }
|
||||
css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }
|
||||
|
||||
assert_equal 0, js_package_names.length
|
||||
assert_equal 0, css_package_names.length
|
||||
assert_equal 0, css_subdir_package_names.length
|
||||
|
||||
Synthesis::AssetPackage.build_all
|
||||
js_package_names = Dir.new("#{$asset_base_path}/javascripts").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.js/) }.sort
|
||||
css_package_names = Dir.new("#{$asset_base_path}/stylesheets").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }.sort
|
||||
css_subdir_package_names = Dir.new("#{$asset_base_path}/stylesheets/subdir").entries.delete_if { |x| ! (x =~ /\A\w+_\d+.css/) }.sort
|
||||
|
||||
assert_equal 2, js_package_names.length
|
||||
assert_equal 2, css_package_names.length
|
||||
assert_equal 1, css_subdir_package_names.length
|
||||
assert js_package_names[0].match(/\Abase_\d+.js\z/)
|
||||
assert js_package_names[1].match(/\Asecondary_\d+.js\z/)
|
||||
assert css_package_names[0].match(/\Abase_\d+.css\z/)
|
||||
assert css_package_names[1].match(/\Asecondary_\d+.css\z/)
|
||||
assert css_subdir_package_names[0].match(/\Astyles_\d+.css\z/)
|
||||
end
|
||||
|
||||
def test_js_names_from_sources
|
||||
package_names = Synthesis::AssetPackage.targets_from_sources("javascripts", ["prototype", "effects", "noexist1", "controls", "foo", "noexist2"])
|
||||
assert_equal 4, package_names.length
|
||||
assert package_names[0].match(/\Abase_\d+\z/)
|
||||
assert_equal package_names[1], "noexist1"
|
||||
assert package_names[2].match(/\Asecondary_\d+\z/)
|
||||
assert_equal package_names[3], "noexist2"
|
||||
end
|
||||
|
||||
def test_css_names_from_sources
|
||||
package_names = Synthesis::AssetPackage.targets_from_sources("stylesheets", ["header", "screen", "noexist1", "foo", "noexist2"])
|
||||
assert_equal 4, package_names.length
|
||||
assert package_names[0].match(/\Abase_\d+\z/)
|
||||
assert_equal package_names[1], "noexist1"
|
||||
assert package_names[2].match(/\Asecondary_\d+\z/)
|
||||
assert_equal package_names[3], "noexist2"
|
||||
end
|
||||
|
||||
def test_should_return_merge_environments_when_set
|
||||
Synthesis::AssetPackage.merge_environments = ["staging", "production"]
|
||||
assert_equal ["staging", "production"], Synthesis::AssetPackage.merge_environments
|
||||
end
|
||||
|
||||
def test_should_only_return_production_merge_environment_when_not_set
|
||||
assert_equal ["production"], Synthesis::AssetPackage.merge_environments
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
20
vendor/plugins/asset_packager/test/asset_packages.yml
vendored
Normal file
20
vendor/plugins/asset_packager/test/asset_packages.yml
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
javascripts:
|
||||
- base:
|
||||
- prototype
|
||||
- effects
|
||||
- controls
|
||||
- dragdrop
|
||||
- secondary:
|
||||
- foo
|
||||
- bar
|
||||
- application
|
||||
stylesheets:
|
||||
- base:
|
||||
- screen
|
||||
- header
|
||||
- secondary:
|
||||
- foo
|
||||
- bar
|
||||
- subdir/styles:
|
||||
- foo
|
||||
- bar
|
||||
2
vendor/plugins/asset_packager/test/assets/javascripts/application.js
vendored
Executable file
2
vendor/plugins/asset_packager/test/assets/javascripts/application.js
vendored
Executable file
|
|
@ -0,0 +1,2 @@
|
|||
// Place your application-specific JavaScript functions and classes here
|
||||
// This file is automatically included by javascript_include_tag :defaults
|
||||
4
vendor/plugins/asset_packager/test/assets/javascripts/bar.js
vendored
Executable file
4
vendor/plugins/asset_packager/test/assets/javascripts/bar.js
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
bar bar bar
|
||||
bar bar bar
|
||||
bar bar bar
|
||||
|
||||
815
vendor/plugins/asset_packager/test/assets/javascripts/controls.js
vendored
Executable file
815
vendor/plugins/asset_packager/test/assets/javascripts/controls.js
vendored
Executable file
|
|
@ -0,0 +1,815 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
|
||||
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
|
||||
// Contributors:
|
||||
// Richard Livsey
|
||||
// Rahul Bhargava
|
||||
// Rob Wills
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
// 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.
|
||||
|
||||
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,
|
||||
'<iframe id="' + this.update.id + '_iefix" '+
|
||||
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
|
||||
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
|
||||
this.iefix = $(this.update.id+'_iefix');
|
||||
}
|
||||
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
|
||||
},
|
||||
|
||||
fixIEOverlapping: function() {
|
||||
Position.clone(this.update, this.iefix);
|
||||
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;
|
||||
},
|
||||
|
||||
markNext: function() {
|
||||
if(this.index < this.entryCount-1) this.index++
|
||||
else this.index = 0;
|
||||
},
|
||||
|
||||
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.firstChild);
|
||||
|
||||
if(this.update.firstChild && this.update.firstChild.childNodes) {
|
||||
this.entryCount =
|
||||
this.update.firstChild.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;
|
||||
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<this.options.tokens.length; i++) {
|
||||
var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
|
||||
if (thisTokenPos > 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("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
|
||||
elem.substr(entry.length) + "</li>");
|
||||
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("<li>" + elem.substr(0, foundPos) + "<strong>" +
|
||||
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
|
||||
foundPos + entry.length) + "</li>");
|
||||
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 "<ul>" + ret.join('') + "</ul>";
|
||||
}
|
||||
}, 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({
|
||||
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);
|
||||
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(/<br/i) || string.match(/<p>/i);
|
||||
},
|
||||
convertHTMLLineBreaks: function(string) {
|
||||
return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/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 = "value";
|
||||
textField.value = text;
|
||||
textField.style.backgroundColor = this.options.highlightcolor;
|
||||
textField.className = 'editor_field';
|
||||
var size = this.options.size || this.options.cols || 0;
|
||||
if (size != 0) textField.size = size;
|
||||
if (this.options.submitOnBlur)
|
||||
textField.onblur = this.onSubmit.bind(this);
|
||||
this.editField = textField;
|
||||
} else {
|
||||
this.options.textarea = true;
|
||||
var textArea = document.createElement("textarea");
|
||||
textArea.obj = this;
|
||||
textArea.name = "value";
|
||||
textArea.value = this.convertHTMLLineBreaks(text);
|
||||
textArea.rows = this.options.rows;
|
||||
textArea.cols = this.options.cols || 40;
|
||||
textArea.className = 'editor_field';
|
||||
if (this.options.submitOnBlur)
|
||||
textArea.onblur = this.onSubmit.bind(this);
|
||||
this.editField = textArea;
|
||||
}
|
||||
|
||||
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();
|
||||
},
|
||||
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(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));
|
||||
}
|
||||
};
|
||||
913
vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js
vendored
Executable file
913
vendor/plugins/asset_packager/test/assets/javascripts/dragdrop.js
vendored
Executable file
|
|
@ -0,0 +1,913 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
/*--------------------------------------------------------------------------*/
|
||||
|
||||
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) {
|
||||
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.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);
|
||||
});
|
||||
},
|
||||
|
||||
_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.prototype = {
|
||||
initialize: function(element) {
|
||||
var options = Object.extend({
|
||||
handle: false,
|
||||
starteffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:1.0, to:0.7});
|
||||
},
|
||||
reverteffect: function(element, top_offset, left_offset) {
|
||||
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
|
||||
element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
|
||||
},
|
||||
endeffect: function(element) {
|
||||
new Effect.Opacity(element, {duration:0.2, from:0.7, to:1.0});
|
||||
},
|
||||
zindex: 1000,
|
||||
revert: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
|
||||
}, arguments[1] || {});
|
||||
|
||||
this.element = $(element);
|
||||
|
||||
if(options.handle && (typeof options.handle == 'string')) {
|
||||
var h = Element.childrenWithClassName(this.element, options.handle, true);
|
||||
if(h.length>0) this.handle = h[0];
|
||||
}
|
||||
if(!this.handle) this.handle = $(options.handle);
|
||||
if(!this.handle) this.handle = this.element;
|
||||
|
||||
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
|
||||
options.scroll = $(options.scroll);
|
||||
|
||||
Element.makePositioned(this.element); // fix IE
|
||||
|
||||
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(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;
|
||||
|
||||
if(this.element._revert) {
|
||||
this.element._revert.cancel();
|
||||
this.element._revert = null;
|
||||
}
|
||||
|
||||
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;
|
||||
p[1] += this.options.scroll.scrollTop;
|
||||
p.push(p[0]+this.options.scroll.offsetWidth);
|
||||
p.push(p[1]+this.options.scroll.offsetHeight);
|
||||
}
|
||||
var speed = [0,0];
|
||||
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
|
||||
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
|
||||
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
|
||||
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
|
||||
this.startScrolling(speed);
|
||||
}
|
||||
|
||||
// fix AppleWebKit rendering
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
|
||||
Event.stop(event);
|
||||
},
|
||||
|
||||
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);
|
||||
var d = this.currentDelta();
|
||||
pos[0] -= d[0]; pos[1] -= d[1];
|
||||
|
||||
if(this.options.scroll && (this.options.scroll != window)) {
|
||||
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
|
||||
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
|
||||
}
|
||||
|
||||
var p = [0,1].map(function(i){
|
||||
return (point[i]-pos[i]-this.offset[i])
|
||||
}.bind(this));
|
||||
|
||||
if(this.options.snap) {
|
||||
if(typeof this.options.snap == 'function') {
|
||||
p = this.options.snap(p[0],p[1]);
|
||||
} 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) {
|
||||
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);
|
||||
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 = {
|
||||
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,
|
||||
hoverclass: null,
|
||||
ghosting: false,
|
||||
scroll: false,
|
||||
scrollSensitivity: 20,
|
||||
scrollSpeed: 15,
|
||||
format: /^[^_]*_(.*)$/,
|
||||
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,
|
||||
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
|
||||
//greedy: !options.dropOnEmpty
|
||||
}
|
||||
|
||||
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 ?
|
||||
Element.childrenWithClassName(e, 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});
|
||||
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) Element.hide(Sortable._marker);
|
||||
},
|
||||
|
||||
mark: function(dropon, position) {
|
||||
// mark on ghosting only
|
||||
var sortable = Sortable.options(dropon.parentNode);
|
||||
if(sortable && !sortable.ghosting) return;
|
||||
|
||||
if(!Sortable._marker) {
|
||||
Sortable._marker = $('dropmarker') || document.createElement('DIV');
|
||||
Element.hide(Sortable._marker);
|
||||
Element.addClassName(Sortable._marker, 'dropmarker');
|
||||
Sortable._marker.style.position = 'absolute';
|
||||
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
|
||||
}
|
||||
var offsets = Position.cumulativeOffset(dropon);
|
||||
Sortable._marker.style.left = offsets[0] + 'px';
|
||||
Sortable._marker.style.top = offsets[1] + 'px';
|
||||
|
||||
if(position=='after')
|
||||
if(sortable.overlap == 'horizontal')
|
||||
Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
|
||||
else
|
||||
Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
|
||||
|
||||
Element.show(Sortable._marker);
|
||||
},
|
||||
|
||||
_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: new Array,
|
||||
position: parent.children.length,
|
||||
container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
|
||||
}
|
||||
|
||||
/* 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;
|
||||
},
|
||||
|
||||
/* Finds the first element of the given tag type within a parent element.
|
||||
Used for finding the first LI[ST] within a L[IST]I[TEM].*/
|
||||
_findChildrenElement: function (element, containerTag) {
|
||||
if (element && element.hasChildNodes)
|
||||
for (var i = 0; i < element.childNodes.length; ++i)
|
||||
if (element.childNodes[i].tagName == containerTag)
|
||||
return element.childNodes[i];
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
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: new Array,
|
||||
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) + "=" +
|
||||
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) {
|
||||
if (type == 'vertical' || type == 'height')
|
||||
return element.offsetHeight;
|
||||
else
|
||||
return element.offsetWidth;
|
||||
}
|
||||
958
vendor/plugins/asset_packager/test/assets/javascripts/effects.js
vendored
Executable file
958
vendor/plugins/asset_packager/test/assets/javascripts/effects.js
vendored
Executable file
|
|
@ -0,0 +1,958 @@
|
|||
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
|
||||
// Contributors:
|
||||
// Justin Palmer (http://encytemedia.com/)
|
||||
// Mark Pilgrim (http://diveintomark.org/)
|
||||
// Martin Bialasinki
|
||||
//
|
||||
// See scriptaculous.js for full license.
|
||||
|
||||
// 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(element, {fontSize: (percent/100) + 'em'});
|
||||
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
|
||||
}
|
||||
|
||||
Element.getOpacity = function(element){
|
||||
var opacity;
|
||||
if (opacity = Element.getStyle(element, 'opacity'))
|
||||
return parseFloat(opacity);
|
||||
if (opacity = (Element.getStyle(element, '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(element, { opacity:
|
||||
(/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
|
||||
0.999999 : null });
|
||||
if(/MSIE/.test(navigator.userAgent))
|
||||
Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
|
||||
} else {
|
||||
if(value < 0.00001) value = 0;
|
||||
Element.setStyle(element, {opacity: value});
|
||||
if(/MSIE/.test(navigator.userAgent))
|
||||
Element.setStyle(element,
|
||||
{ filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
|
||||
'alpha(opacity='+value*100+')' });
|
||||
}
|
||||
}
|
||||
|
||||
Element.getInlineOpacity = function(element){
|
||||
return $(element).style.opacity || '';
|
||||
}
|
||||
|
||||
Element.childrenWithClassName = function(element, className, findFirst) {
|
||||
var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
|
||||
var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
|
||||
return (c.className && c.className.match(classNameRegExp));
|
||||
});
|
||||
if(!results) results = [];
|
||||
return results;
|
||||
}
|
||||
|
||||
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 = {
|
||||
tagifyText: function(element) {
|
||||
var tagifyStyle = 'position:relative';
|
||||
if(/MSIE/.test(navigator.userAgent)) 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 = {}
|
||||
|
||||
Effect.Transitions.linear = function(pos) {
|
||||
return pos;
|
||||
}
|
||||
Effect.Transitions.sinoidal = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI)/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.reverse = function(pos) {
|
||||
return 1-pos;
|
||||
}
|
||||
Effect.Transitions.flicker = function(pos) {
|
||||
return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
|
||||
}
|
||||
Effect.Transitions.wobble = function(pos) {
|
||||
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
|
||||
}
|
||||
Effect.Transitions.pulse = function(pos) {
|
||||
return (Math.floor(pos*10) % 2 == 0 ?
|
||||
(pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
|
||||
}
|
||||
Effect.Transitions.none = function(pos) {
|
||||
return 0;
|
||||
}
|
||||
Effect.Transitions.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 '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:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
|
||||
}
|
||||
}
|
||||
|
||||
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.Opacity = Class.create();
|
||||
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element) {
|
||||
this.element = $(element);
|
||||
// make this work on IE on elements without 'layout'
|
||||
if(/MSIE/.test(navigator.userAgent) && (!this.element.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);
|
||||
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: this.options.x * position + this.originalLeft + 'px',
|
||||
top: this.options.y * position + this.originalTop + 'px'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// for backwards compatibility
|
||||
Effect.MoveBy = function(element, toTop, toLeft) {
|
||||
return new Effect.Move(element,
|
||||
Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
|
||||
};
|
||||
|
||||
Effect.Scale = Class.create();
|
||||
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
|
||||
initialize: function(element, percent) {
|
||||
this.element = $(element)
|
||||
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','%'].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 + 'px';
|
||||
if(this.options.scaleY) d.height = 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);
|
||||
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();
|
||||
effect.element.setStyle({opacity: oldOpacity});
|
||||
}}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Appear = function(element) {
|
||||
element = $(element);
|
||||
var options = Object.extend({
|
||||
from: (element.getStyle('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);
|
||||
effect.element.show();
|
||||
}}, arguments[1] || {});
|
||||
return new Effect.Opacity(element,options);
|
||||
}
|
||||
|
||||
Effect.Puff = function(element) {
|
||||
element = $(element);
|
||||
var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
|
||||
return new Effect.Parallel(
|
||||
[ new Effect.Scale(element, 200,
|
||||
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
|
||||
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
|
||||
Object.extend({ duration: 1.0,
|
||||
beforeSetupInternal: function(effect) {
|
||||
effect.effects[0].element.setStyle({position: 'absolute'}); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide();
|
||||
effect.effects[0].element.setStyle(oldStyle); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.BlindUp = function(element) {
|
||||
element = $(element);
|
||||
element.makeClipping();
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
restoreAfterFinish: true,
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.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();
|
||||
effect.element.setStyle({height: '0px'});
|
||||
effect.element.show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping();
|
||||
}
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.SwitchOff = function(element) {
|
||||
element = $(element);
|
||||
var oldOpacity = element.getInlineOpacity();
|
||||
return new Effect.Appear(element, {
|
||||
duration: 0.4,
|
||||
from: 0,
|
||||
transition: Effect.Transitions.flicker,
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Scale(effect.element, 1, {
|
||||
duration: 0.3, scaleFromCenter: true,
|
||||
scaleX: false, scaleContent: false, restoreAfterFinish: true,
|
||||
beforeSetup: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.makeClipping();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.undoPositioned();
|
||||
effect.element.setStyle({opacity: oldOpacity});
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.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();
|
||||
effect.element.setStyle(oldStyle);
|
||||
}}) }}) }}) }}) }}) }});
|
||||
}
|
||||
|
||||
Effect.SlideDown = function(element) {
|
||||
element = $(element);
|
||||
element.cleanWhitespace();
|
||||
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
|
||||
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
|
||||
var elementDimensions = element.getDimensions();
|
||||
return new Effect.Scale(element, 100, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleFrom: 0,
|
||||
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
|
||||
restoreAfterFinish: true,
|
||||
afterSetup: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.firstChild.makePositioned();
|
||||
if(window.opera) effect.element.setStyle({top: ''});
|
||||
effect.element.makeClipping();
|
||||
effect.element.setStyle({height: '0px'});
|
||||
effect.element.show(); },
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.firstChild.setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' });
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.undoClipping();
|
||||
// IE will crash if child is undoPositioned first
|
||||
if(/MSIE/.test(navigator.userAgent)){
|
||||
effect.element.undoPositioned();
|
||||
effect.element.firstChild.undoPositioned();
|
||||
}else{
|
||||
effect.element.firstChild.undoPositioned();
|
||||
effect.element.undoPositioned();
|
||||
}
|
||||
effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
Effect.SlideUp = function(element) {
|
||||
element = $(element);
|
||||
element.cleanWhitespace();
|
||||
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
|
||||
return new Effect.Scale(element, 0,
|
||||
Object.extend({ scaleContent: false,
|
||||
scaleX: false,
|
||||
scaleMode: 'box',
|
||||
scaleFrom: 100,
|
||||
restoreAfterFinish: true,
|
||||
beforeStartInternal: function(effect) {
|
||||
effect.element.makePositioned();
|
||||
effect.element.firstChild.makePositioned();
|
||||
if(window.opera) effect.element.setStyle({top: ''});
|
||||
effect.element.makeClipping();
|
||||
effect.element.show(); },
|
||||
afterUpdateInternal: function(effect) {
|
||||
effect.element.firstChild.setStyle({bottom:
|
||||
(effect.dims[0] - effect.element.clientHeight) + 'px' }); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.firstChild.undoPositioned();
|
||||
effect.element.undoPositioned();
|
||||
effect.element.setStyle({bottom: oldInnerBottom}); }
|
||||
}, arguments[1] || {})
|
||||
);
|
||||
}
|
||||
|
||||
// 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(effect.element); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide(effect.element);
|
||||
effect.element.undoClipping(effect.element); }
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
effect.element.makeClipping();
|
||||
effect.element.makePositioned();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Parallel(
|
||||
[ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
|
||||
new Effect.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'});
|
||||
effect.effects[0].element.show();
|
||||
},
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.undoClipping();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.setStyle(oldStyle);
|
||||
}
|
||||
}, options)
|
||||
)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
effect.effects[0].element.makeClipping(); },
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.effects[0].element.hide();
|
||||
effect.effects[0].element.undoClipping();
|
||||
effect.effects[0].element.undoPositioned();
|
||||
effect.effects[0].element.setStyle(oldStyle); }
|
||||
}, options)
|
||||
);
|
||||
}
|
||||
|
||||
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)) };
|
||||
reverser.bind(transition);
|
||||
return new Effect.Opacity(element,
|
||||
Object.extend(Object.extend({ duration: 3.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(element);
|
||||
return new Effect.Scale(element, 5, Object.extend({
|
||||
scaleContent: false,
|
||||
scaleX: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
new Effect.Scale(element, 1, {
|
||||
scaleContent: false,
|
||||
scaleY: false,
|
||||
afterFinishInternal: function(effect) {
|
||||
effect.element.hide();
|
||||
effect.element.undoClipping();
|
||||
effect.element.setStyle(oldStyle);
|
||||
} });
|
||||
}}, arguments[1] || {}));
|
||||
};
|
||||
|
||||
['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
|
||||
'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].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();
|
||||
4
vendor/plugins/asset_packager/test/assets/javascripts/foo.js
vendored
Executable file
4
vendor/plugins/asset_packager/test/assets/javascripts/foo.js
vendored
Executable file
|
|
@ -0,0 +1,4 @@
|
|||
foo foo foo
|
||||
foo foo foo
|
||||
foo foo foo
|
||||
|
||||
2006
vendor/plugins/asset_packager/test/assets/javascripts/prototype.js
vendored
Executable file
2006
vendor/plugins/asset_packager/test/assets/javascripts/prototype.js
vendored
Executable file
File diff suppressed because it is too large
Load diff
16
vendor/plugins/asset_packager/test/assets/stylesheets/bar.css
vendored
Normal file
16
vendor/plugins/asset_packager/test/assets/stylesheets/bar.css
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#bar1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#bar2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#bar3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/foo.css
vendored
Normal file
16
vendor/plugins/asset_packager/test/assets/stylesheets/foo.css
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#foo1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#foo2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#foo3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/header.css
vendored
Executable file
16
vendor/plugins/asset_packager/test/assets/stylesheets/header.css
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#header1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#header2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#header3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/screen.css
vendored
Executable file
16
vendor/plugins/asset_packager/test/assets/stylesheets/screen.css
vendored
Executable file
|
|
@ -0,0 +1,16 @@
|
|||
#screen1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#screen2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#screen3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/subdir/bar.css
vendored
Normal file
16
vendor/plugins/asset_packager/test/assets/stylesheets/subdir/bar.css
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#bar1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#bar2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#bar3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
16
vendor/plugins/asset_packager/test/assets/stylesheets/subdir/foo.css
vendored
Normal file
16
vendor/plugins/asset_packager/test/assets/stylesheets/subdir/foo.css
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#foo1 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#foo2 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
#foo3 {
|
||||
background: #fff;
|
||||
color: #000;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
152
vendor/plugins/classic_pagination/CHANGELOG
vendored
Normal file
152
vendor/plugins/classic_pagination/CHANGELOG
vendored
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
* Exported the changelog of Pagination code for historical reference.
|
||||
|
||||
* Imported some patches from Rails Trac (others closed as "wontfix"):
|
||||
#8176, #7325, #7028, #4113. Documentation is much cleaner now and there
|
||||
are some new unobtrusive features!
|
||||
|
||||
* Extracted Pagination from Rails trunk (r6795)
|
||||
|
||||
#
|
||||
# ChangeLog for /trunk/actionpack/lib/action_controller/pagination.rb
|
||||
#
|
||||
# Generated by Trac 0.10.3
|
||||
# 05/20/07 23:48:02
|
||||
#
|
||||
|
||||
09/03/06 23:28:54 david [4953]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Docs and deprecation
|
||||
|
||||
08/07/06 12:40:14 bitsweat [4715]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Deprecate direct usage of @params. Update ActionView::Base for
|
||||
instance var deprecation.
|
||||
|
||||
06/21/06 02:16:11 rick [4476]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Fix indent in pagination documentation. Closes #4990. [Kevin Clark]
|
||||
|
||||
04/25/06 17:42:48 marcel [4268]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Remove all remaining references to @params in the documentation.
|
||||
|
||||
03/16/06 06:38:08 rick [3899]
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
trivial documentation patch for #pagination_links [Francois
|
||||
Beausoleil] closes #4258
|
||||
|
||||
02/20/06 03:15:22 david [3620]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/test/activerecord/pagination_test.rb (modified)
|
||||
* trunk/activerecord/CHANGELOG (modified)
|
||||
* trunk/activerecord/lib/active_record/base.rb (modified)
|
||||
* trunk/activerecord/test/base_test.rb (modified)
|
||||
Added :count option to pagination that'll make it possible for the
|
||||
ActiveRecord::Base.count call to using something else than * for the
|
||||
count. Especially important for count queries using DISTINCT #3839
|
||||
[skaes]. Added :select option to Base.count that'll allow you to
|
||||
select something else than * to be counted on. Especially important
|
||||
for count queries using DISTINCT (closes #3839) [skaes].
|
||||
|
||||
02/09/06 09:17:40 nzkoz [3553]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/test/active_record_unit.rb (added)
|
||||
* trunk/actionpack/test/activerecord (added)
|
||||
* trunk/actionpack/test/activerecord/active_record_assertions_test.rb (added)
|
||||
* trunk/actionpack/test/activerecord/pagination_test.rb (added)
|
||||
* trunk/actionpack/test/controller/active_record_assertions_test.rb (deleted)
|
||||
* trunk/actionpack/test/fixtures/companies.yml (added)
|
||||
* trunk/actionpack/test/fixtures/company.rb (added)
|
||||
* trunk/actionpack/test/fixtures/db_definitions (added)
|
||||
* trunk/actionpack/test/fixtures/db_definitions/sqlite.sql (added)
|
||||
* trunk/actionpack/test/fixtures/developer.rb (added)
|
||||
* trunk/actionpack/test/fixtures/developers_projects.yml (added)
|
||||
* trunk/actionpack/test/fixtures/developers.yml (added)
|
||||
* trunk/actionpack/test/fixtures/project.rb (added)
|
||||
* trunk/actionpack/test/fixtures/projects.yml (added)
|
||||
* trunk/actionpack/test/fixtures/replies.yml (added)
|
||||
* trunk/actionpack/test/fixtures/reply.rb (added)
|
||||
* trunk/actionpack/test/fixtures/topic.rb (added)
|
||||
* trunk/actionpack/test/fixtures/topics.yml (added)
|
||||
* Fix pagination problems when using include
|
||||
* Introduce Unit Tests for pagination
|
||||
* Allow count to work with :include by using count distinct.
|
||||
|
||||
[Kevin Clark & Jeremy Hopple]
|
||||
|
||||
11/05/05 02:10:29 bitsweat [2878]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Update paginator docs. Closes #2744.
|
||||
|
||||
10/16/05 15:42:03 minam [2649]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Update/clean up AP documentation (rdoc)
|
||||
|
||||
08/31/05 00:13:10 ulysses [2078]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Add option to specify the singular name used by pagination. Closes
|
||||
#1960
|
||||
|
||||
08/23/05 14:24:15 minam [2041]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
Add support for :include with pagination (subject to existing
|
||||
constraints for :include with :limit and :offset) #1478
|
||||
[michael@schubert.cx]
|
||||
|
||||
07/15/05 20:27:38 david [1839]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
More pagination speed #1334 [Stefan Kaes]
|
||||
|
||||
07/14/05 08:02:01 david [1832]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
* trunk/actionpack/test/controller/addresses_render_test.rb (modified)
|
||||
Made pagination faster #1334 [Stefan Kaes]
|
||||
|
||||
04/13/05 05:40:22 david [1159]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/activerecord/lib/active_record/base.rb (modified)
|
||||
Fixed pagination to work with joins #1034 [scott@sigkill.org]
|
||||
|
||||
04/02/05 09:11:17 david [1067]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/lib/action_controller/scaffolding.rb (modified)
|
||||
* trunk/actionpack/lib/action_controller/templates/scaffolds/list.rhtml (modified)
|
||||
* trunk/railties/lib/rails_generator/generators/components/scaffold/templates/controller.rb (modified)
|
||||
* trunk/railties/lib/rails_generator/generators/components/scaffold/templates/view_list.rhtml (modified)
|
||||
Added pagination for scaffolding (10 items per page) #964
|
||||
[mortonda@dgrmm.net]
|
||||
|
||||
03/31/05 14:46:11 david [1048]
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
Improved the message display on the exception handler pages #963
|
||||
[Johan Sorensen]
|
||||
|
||||
03/27/05 00:04:07 david [1017]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
Fixed that pagination_helper would ignore :params #947 [Sebastian
|
||||
Kanthak]
|
||||
|
||||
03/22/05 13:09:44 david [976]
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
Fixed documentation and prepared for 0.11.0 release
|
||||
|
||||
03/21/05 14:35:36 david [967]
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (modified)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (modified)
|
||||
Tweaked the documentation
|
||||
|
||||
03/20/05 23:12:05 david [949]
|
||||
* trunk/actionpack/CHANGELOG (modified)
|
||||
* trunk/actionpack/lib/action_controller.rb (modified)
|
||||
* trunk/actionpack/lib/action_controller/pagination.rb (added)
|
||||
* trunk/actionpack/lib/action_view/helpers/pagination_helper.rb (added)
|
||||
* trunk/activesupport/lib/active_support/core_ext/kernel.rb (added)
|
||||
Added pagination support through both a controller and helper add-on
|
||||
#817 [Sam Stephenson]
|
||||
18
vendor/plugins/classic_pagination/README
vendored
Normal file
18
vendor/plugins/classic_pagination/README
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
Pagination
|
||||
==========
|
||||
|
||||
To install:
|
||||
|
||||
script/plugin install svn://errtheblog.com/svn/plugins/classic_pagination
|
||||
|
||||
This code was extracted from Rails trunk after the release 1.2.3.
|
||||
WARNING: this code is dead. It is unmaintained, untested and full of cruft.
|
||||
|
||||
There is a much better pagination plugin called will_paginate.
|
||||
Install it like this and glance through the README:
|
||||
|
||||
script/plugin install svn://errtheblog.com/svn/plugins/will_paginate
|
||||
|
||||
It doesn't have the same API, but is in fact much nicer. You can
|
||||
have both plugins installed until you change your controller/view code that
|
||||
handles pagination. Then, simply uninstall classic_pagination.
|
||||
22
vendor/plugins/classic_pagination/Rakefile
vendored
Normal file
22
vendor/plugins/classic_pagination/Rakefile
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
require 'rake'
|
||||
require 'rake/testtask'
|
||||
require 'rake/rdoctask'
|
||||
|
||||
desc 'Default: run unit tests.'
|
||||
task :default => :test
|
||||
|
||||
desc 'Test the classic_pagination plugin.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the classic_pagination plugin.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
rdoc.rdoc_dir = 'rdoc'
|
||||
rdoc.title = 'Pagination'
|
||||
rdoc.options << '--line-numbers' << '--inline-source'
|
||||
rdoc.rdoc_files.include('README')
|
||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||
end
|
||||
33
vendor/plugins/classic_pagination/init.rb
vendored
Normal file
33
vendor/plugins/classic_pagination/init.rb
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
#--
|
||||
# Copyright (c) 2004-2006 David Heinemeier Hansson
|
||||
#
|
||||
# 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.
|
||||
#++
|
||||
|
||||
require 'pagination'
|
||||
require 'pagination_helper'
|
||||
|
||||
ActionController::Base.class_eval do
|
||||
include ActionController::Pagination
|
||||
end
|
||||
|
||||
ActionView::Base.class_eval do
|
||||
include ActionView::Helpers::PaginationHelper
|
||||
end
|
||||
1
vendor/plugins/classic_pagination/install.rb
vendored
Normal file
1
vendor/plugins/classic_pagination/install.rb
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
puts "\n\n" + File.read(File.dirname(__FILE__) + '/README')
|
||||
405
vendor/plugins/classic_pagination/lib/pagination.rb
vendored
Normal file
405
vendor/plugins/classic_pagination/lib/pagination.rb
vendored
Normal file
|
|
@ -0,0 +1,405 @@
|
|||
module ActionController
|
||||
# === Action Pack pagination for Active Record collections
|
||||
#
|
||||
# The Pagination module aids in the process of paging large collections of
|
||||
# Active Record objects. It offers macro-style automatic fetching of your
|
||||
# model for multiple views, or explicit fetching for single actions. And if
|
||||
# the magic isn't flexible enough for your needs, you can create your own
|
||||
# paginators with a minimal amount of code.
|
||||
#
|
||||
# The Pagination module can handle as much or as little as you wish. In the
|
||||
# controller, have it automatically query your model for pagination; or,
|
||||
# if you prefer, create Paginator objects yourself.
|
||||
#
|
||||
# Pagination is included automatically for all controllers.
|
||||
#
|
||||
# For help rendering pagination links, see
|
||||
# ActionView::Helpers::PaginationHelper.
|
||||
#
|
||||
# ==== Automatic pagination for every action in a controller
|
||||
#
|
||||
# class PersonController < ApplicationController
|
||||
# model :person
|
||||
#
|
||||
# paginate :people, :order => 'last_name, first_name',
|
||||
# :per_page => 20
|
||||
#
|
||||
# # ...
|
||||
# end
|
||||
#
|
||||
# Each action in this controller now has access to a <tt>@people</tt>
|
||||
# instance variable, which is an ordered collection of model objects for the
|
||||
# current page (at most 20, sorted by last name and first name), and a
|
||||
# <tt>@person_pages</tt> Paginator instance. The current page is determined
|
||||
# by the <tt>params[:page]</tt> variable.
|
||||
#
|
||||
# ==== Pagination for a single action
|
||||
#
|
||||
# def list
|
||||
# @person_pages, @people =
|
||||
# paginate :people, :order => 'last_name, first_name'
|
||||
# end
|
||||
#
|
||||
# Like the previous example, but explicitly creates <tt>@person_pages</tt>
|
||||
# and <tt>@people</tt> for a single action, and uses the default of 10 items
|
||||
# per page.
|
||||
#
|
||||
# ==== Custom/"classic" pagination
|
||||
#
|
||||
# def list
|
||||
# @person_pages = Paginator.new self, Person.count, 10, params[:page]
|
||||
# @people = Person.find :all, :order => 'last_name, first_name',
|
||||
# :limit => @person_pages.items_per_page,
|
||||
# :offset => @person_pages.current.offset
|
||||
# end
|
||||
#
|
||||
# Explicitly creates the paginator from the previous example and uses
|
||||
# Paginator#to_sql to retrieve <tt>@people</tt> from the model.
|
||||
#
|
||||
module Pagination
|
||||
unless const_defined?(:OPTIONS)
|
||||
# A hash holding options for controllers using macro-style pagination
|
||||
OPTIONS = Hash.new
|
||||
|
||||
# The default options for pagination
|
||||
DEFAULT_OPTIONS = {
|
||||
:class_name => nil,
|
||||
:singular_name => nil,
|
||||
:per_page => 10,
|
||||
:conditions => nil,
|
||||
:order_by => nil,
|
||||
:order => nil,
|
||||
:join => nil,
|
||||
:joins => nil,
|
||||
:count => nil,
|
||||
:include => nil,
|
||||
:select => nil,
|
||||
:group => nil,
|
||||
:parameter => 'page'
|
||||
}
|
||||
else
|
||||
DEFAULT_OPTIONS[:group] = nil
|
||||
end
|
||||
|
||||
def self.included(base) #:nodoc:
|
||||
super
|
||||
base.extend(ClassMethods)
|
||||
end
|
||||
|
||||
def self.validate_options!(collection_id, options, in_action) #:nodoc:
|
||||
options.merge!(DEFAULT_OPTIONS) {|key, old, new| old}
|
||||
|
||||
valid_options = DEFAULT_OPTIONS.keys
|
||||
valid_options << :actions unless in_action
|
||||
|
||||
unknown_option_keys = options.keys - valid_options
|
||||
raise ActionController::ActionControllerError,
|
||||
"Unknown options: #{unknown_option_keys.join(', ')}" unless
|
||||
unknown_option_keys.empty?
|
||||
|
||||
options[:singular_name] ||= Inflector.singularize(collection_id.to_s)
|
||||
options[:class_name] ||= Inflector.camelize(options[:singular_name])
|
||||
end
|
||||
|
||||
# Returns a paginator and a collection of Active Record model instances
|
||||
# for the paginator's current page. This is designed to be used in a
|
||||
# single action; to automatically paginate multiple actions, consider
|
||||
# ClassMethods#paginate.
|
||||
#
|
||||
# +options+ are:
|
||||
# <tt>:singular_name</tt>:: the singular name to use, if it can't be inferred by singularizing the collection name
|
||||
# <tt>:class_name</tt>:: the class name to use, if it can't be inferred by
|
||||
# camelizing the singular name
|
||||
# <tt>:per_page</tt>:: the maximum number of items to include in a
|
||||
# single page. Defaults to 10
|
||||
# <tt>:conditions</tt>:: optional conditions passed to Model.find(:all, *params) and
|
||||
# Model.count
|
||||
# <tt>:order</tt>:: optional order parameter passed to Model.find(:all, *params)
|
||||
# <tt>:order_by</tt>:: (deprecated, used :order) optional order parameter passed to Model.find(:all, *params)
|
||||
# <tt>:joins</tt>:: optional joins parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:join</tt>:: (deprecated, used :joins or :include) optional join parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:include</tt>:: optional eager loading parameter passed to Model.find(:all, *params)
|
||||
# and Model.count
|
||||
# <tt>:select</tt>:: :select parameter passed to Model.find(:all, *params)
|
||||
#
|
||||
# <tt>:count</tt>:: parameter passed as :select option to Model.count(*params)
|
||||
#
|
||||
# <tt>:group</tt>:: :group parameter passed to Model.find(:all, *params). It forces the use of DISTINCT instead of plain COUNT to come up with the total number of records
|
||||
#
|
||||
def paginate(collection_id, options={})
|
||||
Pagination.validate_options!(collection_id, options, true)
|
||||
paginator_and_collection_for(collection_id, options)
|
||||
end
|
||||
|
||||
# These methods become class methods on any controller
|
||||
module ClassMethods
|
||||
# Creates a +before_filter+ which automatically paginates an Active
|
||||
# Record model for all actions in a controller (or certain actions if
|
||||
# specified with the <tt>:actions</tt> option).
|
||||
#
|
||||
# +options+ are the same as PaginationHelper#paginate, with the addition
|
||||
# of:
|
||||
# <tt>:actions</tt>:: an array of actions for which the pagination is
|
||||
# active. Defaults to +nil+ (i.e., every action)
|
||||
def paginate(collection_id, options={})
|
||||
Pagination.validate_options!(collection_id, options, false)
|
||||
module_eval do
|
||||
before_filter :create_paginators_and_retrieve_collections
|
||||
OPTIONS[self] ||= Hash.new
|
||||
OPTIONS[self][collection_id] = options
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_paginators_and_retrieve_collections #:nodoc:
|
||||
Pagination::OPTIONS[self.class].each do |collection_id, options|
|
||||
next unless options[:actions].include? action_name if
|
||||
options[:actions]
|
||||
|
||||
paginator, collection =
|
||||
paginator_and_collection_for(collection_id, options)
|
||||
|
||||
paginator_name = "@#{options[:singular_name]}_pages"
|
||||
self.instance_variable_set(paginator_name, paginator)
|
||||
|
||||
collection_name = "@#{collection_id.to_s}"
|
||||
self.instance_variable_set(collection_name, collection)
|
||||
end
|
||||
end
|
||||
|
||||
# Returns the total number of items in the collection to be paginated for
|
||||
# the +model+ and given +conditions+. Override this method to implement a
|
||||
# custom counter.
|
||||
def count_collection_for_pagination(model, options)
|
||||
model.count(:conditions => options[:conditions],
|
||||
:joins => options[:join] || options[:joins],
|
||||
:include => options[:include],
|
||||
:select => (options[:group] ? "DISTINCT #{options[:group]}" : options[:count]))
|
||||
end
|
||||
|
||||
# Returns a collection of items for the given +model+ and +options[conditions]+,
|
||||
# ordered by +options[order]+, for the current page in the given +paginator+.
|
||||
# Override this method to implement a custom finder.
|
||||
def find_collection_for_pagination(model, options, paginator)
|
||||
model.find(:all, :conditions => options[:conditions],
|
||||
:order => options[:order_by] || options[:order],
|
||||
:joins => options[:join] || options[:joins], :include => options[:include],
|
||||
:select => options[:select], :limit => options[:per_page],
|
||||
:group => options[:group], :offset => paginator.current.offset)
|
||||
end
|
||||
|
||||
protected :create_paginators_and_retrieve_collections,
|
||||
:count_collection_for_pagination,
|
||||
:find_collection_for_pagination
|
||||
|
||||
def paginator_and_collection_for(collection_id, options) #:nodoc:
|
||||
klass = options[:class_name].constantize
|
||||
page = params[options[:parameter]]
|
||||
count = count_collection_for_pagination(klass, options)
|
||||
paginator = Paginator.new(self, count, options[:per_page], page)
|
||||
collection = find_collection_for_pagination(klass, options, paginator)
|
||||
|
||||
return paginator, collection
|
||||
end
|
||||
|
||||
private :paginator_and_collection_for
|
||||
|
||||
# A class representing a paginator for an Active Record collection.
|
||||
class Paginator
|
||||
include Enumerable
|
||||
|
||||
# Creates a new Paginator on the given +controller+ for a set of items
|
||||
# of size +item_count+ and having +items_per_page+ items per page.
|
||||
# Raises ArgumentError if items_per_page is out of bounds (i.e., less
|
||||
# than or equal to zero). The page CGI parameter for links defaults to
|
||||
# "page" and can be overridden with +page_parameter+.
|
||||
def initialize(controller, item_count, items_per_page, current_page=1)
|
||||
raise ArgumentError, 'must have at least one item per page' if
|
||||
items_per_page <= 0
|
||||
|
||||
@controller = controller
|
||||
@item_count = item_count || 0
|
||||
@items_per_page = items_per_page
|
||||
@pages = {}
|
||||
|
||||
self.current_page = current_page
|
||||
end
|
||||
attr_reader :controller, :item_count, :items_per_page
|
||||
|
||||
# Sets the current page number of this paginator. If +page+ is a Page
|
||||
# object, its +number+ attribute is used as the value; if the page does
|
||||
# not belong to this Paginator, an ArgumentError is raised.
|
||||
def current_page=(page)
|
||||
if page.is_a? Page
|
||||
raise ArgumentError, 'Page/Paginator mismatch' unless
|
||||
page.paginator == self
|
||||
end
|
||||
page = page.to_i
|
||||
@current_page_number = has_page_number?(page) ? page : 1
|
||||
end
|
||||
|
||||
# Returns a Page object representing this paginator's current page.
|
||||
def current_page
|
||||
@current_page ||= self[@current_page_number]
|
||||
end
|
||||
alias current :current_page
|
||||
|
||||
# Returns a new Page representing the first page in this paginator.
|
||||
def first_page
|
||||
@first_page ||= self[1]
|
||||
end
|
||||
alias first :first_page
|
||||
|
||||
# Returns a new Page representing the last page in this paginator.
|
||||
def last_page
|
||||
@last_page ||= self[page_count]
|
||||
end
|
||||
alias last :last_page
|
||||
|
||||
# Returns the number of pages in this paginator.
|
||||
def page_count
|
||||
@page_count ||= @item_count.zero? ? 1 :
|
||||
(q,r=@item_count.divmod(@items_per_page); r==0? q : q+1)
|
||||
end
|
||||
|
||||
alias length :page_count
|
||||
|
||||
# Returns true if this paginator contains the page of index +number+.
|
||||
def has_page_number?(number)
|
||||
number >= 1 and number <= page_count
|
||||
end
|
||||
|
||||
# Returns a new Page representing the page with the given index
|
||||
# +number+.
|
||||
def [](number)
|
||||
@pages[number] ||= Page.new(self, number)
|
||||
end
|
||||
|
||||
# Successively yields all the paginator's pages to the given block.
|
||||
def each(&block)
|
||||
page_count.times do |n|
|
||||
yield self[n+1]
|
||||
end
|
||||
end
|
||||
|
||||
# A class representing a single page in a paginator.
|
||||
class Page
|
||||
include Comparable
|
||||
|
||||
# Creates a new Page for the given +paginator+ with the index
|
||||
# +number+. If +number+ is not in the range of valid page numbers or
|
||||
# is not a number at all, it defaults to 1.
|
||||
def initialize(paginator, number)
|
||||
@paginator = paginator
|
||||
@number = number.to_i
|
||||
@number = 1 unless @paginator.has_page_number? @number
|
||||
end
|
||||
attr_reader :paginator, :number
|
||||
alias to_i :number
|
||||
|
||||
# Compares two Page objects and returns true when they represent the
|
||||
# same page (i.e., their paginators are the same and they have the
|
||||
# same page number).
|
||||
def ==(page)
|
||||
return false if page.nil?
|
||||
@paginator == page.paginator and
|
||||
@number == page.number
|
||||
end
|
||||
|
||||
# Compares two Page objects and returns -1 if the left-hand page comes
|
||||
# before the right-hand page, 0 if the pages are equal, and 1 if the
|
||||
# left-hand page comes after the right-hand page. Raises ArgumentError
|
||||
# if the pages do not belong to the same Paginator object.
|
||||
def <=>(page)
|
||||
raise ArgumentError unless @paginator == page.paginator
|
||||
@number <=> page.number
|
||||
end
|
||||
|
||||
# Returns the item offset for the first item in this page.
|
||||
def offset
|
||||
@paginator.items_per_page * (@number - 1)
|
||||
end
|
||||
|
||||
# Returns the number of the first item displayed.
|
||||
def first_item
|
||||
offset + 1
|
||||
end
|
||||
|
||||
# Returns the number of the last item displayed.
|
||||
def last_item
|
||||
[@paginator.items_per_page * @number, @paginator.item_count].min
|
||||
end
|
||||
|
||||
# Returns true if this page is the first page in the paginator.
|
||||
def first?
|
||||
self == @paginator.first
|
||||
end
|
||||
|
||||
# Returns true if this page is the last page in the paginator.
|
||||
def last?
|
||||
self == @paginator.last
|
||||
end
|
||||
|
||||
# Returns a new Page object representing the page just before this
|
||||
# page, or nil if this is the first page.
|
||||
def previous
|
||||
if first? then nil else @paginator[@number - 1] end
|
||||
end
|
||||
|
||||
# Returns a new Page object representing the page just after this
|
||||
# page, or nil if this is the last page.
|
||||
def next
|
||||
if last? then nil else @paginator[@number + 1] end
|
||||
end
|
||||
|
||||
# Returns a new Window object for this page with the specified
|
||||
# +padding+.
|
||||
def window(padding=2)
|
||||
Window.new(self, padding)
|
||||
end
|
||||
|
||||
# Returns the limit/offset array for this page.
|
||||
def to_sql
|
||||
[@paginator.items_per_page, offset]
|
||||
end
|
||||
|
||||
def to_param #:nodoc:
|
||||
@number.to_s
|
||||
end
|
||||
end
|
||||
|
||||
# A class for representing ranges around a given page.
|
||||
class Window
|
||||
# Creates a new Window object for the given +page+ with the specified
|
||||
# +padding+.
|
||||
def initialize(page, padding=2)
|
||||
@paginator = page.paginator
|
||||
@page = page
|
||||
self.padding = padding
|
||||
end
|
||||
attr_reader :paginator, :page
|
||||
|
||||
# Sets the window's padding (the number of pages on either side of the
|
||||
# window page).
|
||||
def padding=(padding)
|
||||
@padding = padding < 0 ? 0 : padding
|
||||
# Find the beginning and end pages of the window
|
||||
@first = @paginator.has_page_number?(@page.number - @padding) ?
|
||||
@paginator[@page.number - @padding] : @paginator.first
|
||||
@last = @paginator.has_page_number?(@page.number + @padding) ?
|
||||
@paginator[@page.number + @padding] : @paginator.last
|
||||
end
|
||||
attr_reader :padding, :first, :last
|
||||
|
||||
# Returns an array of Page objects in the current window.
|
||||
def pages
|
||||
(@first.number..@last.number).to_a.collect! {|n| @paginator[n]}
|
||||
end
|
||||
alias to_a :pages
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
135
vendor/plugins/classic_pagination/lib/pagination_helper.rb
vendored
Normal file
135
vendor/plugins/classic_pagination/lib/pagination_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
module ActionView
|
||||
module Helpers
|
||||
# Provides methods for linking to ActionController::Pagination objects using a simple generator API. You can optionally
|
||||
# also build your links manually using ActionView::Helpers::AssetHelper#link_to like so:
|
||||
#
|
||||
# <%= link_to "Previous page", { :page => paginator.current.previous } if paginator.current.previous %>
|
||||
# <%= link_to "Next page", { :page => paginator.current.next } if paginator.current.next %>
|
||||
module PaginationHelper
|
||||
unless const_defined?(:DEFAULT_OPTIONS)
|
||||
DEFAULT_OPTIONS = {
|
||||
:name => :page,
|
||||
:window_size => 2,
|
||||
:always_show_anchors => true,
|
||||
:link_to_current_page => false,
|
||||
:params => {}
|
||||
}
|
||||
end
|
||||
|
||||
# Creates a basic HTML link bar for the given +paginator+. Links will be created
|
||||
# for the next and/or previous page and for a number of other pages around the current
|
||||
# pages position. The +html_options+ hash is passed to +link_to+ when the links are created.
|
||||
#
|
||||
# ==== Options
|
||||
# <tt>:name</tt>:: the routing name for this paginator
|
||||
# (defaults to +page+)
|
||||
# <tt>:prefix</tt>:: prefix for pagination links
|
||||
# (i.e. Older Pages: 1 2 3 4)
|
||||
# <tt>:suffix</tt>:: suffix for pagination links
|
||||
# (i.e. 1 2 3 4 <- Older Pages)
|
||||
# <tt>:window_size</tt>:: the number of pages to show around
|
||||
# the current page (defaults to <tt>2</tt>)
|
||||
# <tt>:always_show_anchors</tt>:: whether or not the first and last
|
||||
# pages should always be shown
|
||||
# (defaults to +true+)
|
||||
# <tt>:link_to_current_page</tt>:: whether or not the current page
|
||||
# should be linked to (defaults to
|
||||
# +false+)
|
||||
# <tt>:params</tt>:: any additional routing parameters
|
||||
# for page URLs
|
||||
#
|
||||
# ==== Examples
|
||||
# # We'll assume we have a paginator setup in @person_pages...
|
||||
#
|
||||
# pagination_links(@person_pages)
|
||||
# # => 1 <a href="/?page=2/">2</a> <a href="/?page=3/">3</a> ... <a href="/?page=10/">10</a>
|
||||
#
|
||||
# pagination_links(@person_pages, :link_to_current_page => true)
|
||||
# # => <a href="/?page=1/">1</a> <a href="/?page=2/">2</a> <a href="/?page=3/">3</a> ... <a href="/?page=10/">10</a>
|
||||
#
|
||||
# pagination_links(@person_pages, :always_show_anchors => false)
|
||||
# # => 1 <a href="/?page=2/">2</a> <a href="/?page=3/">3</a>
|
||||
#
|
||||
# pagination_links(@person_pages, :window_size => 1)
|
||||
# # => 1 <a href="/?page=2/">2</a> ... <a href="/?page=10/">10</a>
|
||||
#
|
||||
# pagination_links(@person_pages, :params => { :viewer => "flash" })
|
||||
# # => 1 <a href="/?page=2&viewer=flash/">2</a> <a href="/?page=3&viewer=flash/">3</a> ...
|
||||
# # <a href="/?page=10&viewer=flash/">10</a>
|
||||
def pagination_links(paginator, options={}, html_options={})
|
||||
name = options[:name] || DEFAULT_OPTIONS[:name]
|
||||
params = (options[:params] || DEFAULT_OPTIONS[:params]).clone
|
||||
|
||||
prefix = options[:prefix] || ''
|
||||
suffix = options[:suffix] || ''
|
||||
|
||||
pagination_links_each(paginator, options, prefix, suffix) do |n|
|
||||
params[name] = n
|
||||
link_to(n.to_s, params, html_options)
|
||||
end
|
||||
end
|
||||
|
||||
# Iterate through the pages of a given +paginator+, invoking a
|
||||
# block for each page number that needs to be rendered as a link.
|
||||
#
|
||||
# ==== Options
|
||||
# <tt>:window_size</tt>:: the number of pages to show around
|
||||
# the current page (defaults to +2+)
|
||||
# <tt>:always_show_anchors</tt>:: whether or not the first and last
|
||||
# pages should always be shown
|
||||
# (defaults to +true+)
|
||||
# <tt>:link_to_current_page</tt>:: whether or not the current page
|
||||
# should be linked to (defaults to
|
||||
# +false+)
|
||||
#
|
||||
# ==== Example
|
||||
# # Turn paginated links into an Ajax call
|
||||
# pagination_links_each(paginator, page_options) do |link|
|
||||
# options = { :url => {:action => 'list'}, :update => 'results' }
|
||||
# html_options = { :href => url_for(:action => 'list') }
|
||||
#
|
||||
# link_to_remote(link.to_s, options, html_options)
|
||||
# end
|
||||
def pagination_links_each(paginator, options, prefix = nil, suffix = nil)
|
||||
options = DEFAULT_OPTIONS.merge(options)
|
||||
link_to_current_page = options[:link_to_current_page]
|
||||
always_show_anchors = options[:always_show_anchors]
|
||||
|
||||
current_page = paginator.current_page
|
||||
window_pages = current_page.window(options[:window_size]).pages
|
||||
return if window_pages.length <= 1 unless link_to_current_page
|
||||
|
||||
first, last = paginator.first, paginator.last
|
||||
|
||||
html = ''
|
||||
|
||||
html << prefix if prefix
|
||||
|
||||
if always_show_anchors and not (wp_first = window_pages[0]).first?
|
||||
html << yield(first.number)
|
||||
html << ' ... ' if wp_first.number - first.number > 1
|
||||
html << ' '
|
||||
end
|
||||
|
||||
window_pages.each do |page|
|
||||
if current_page == page && !link_to_current_page
|
||||
html << page.number.to_s
|
||||
else
|
||||
html << yield(page.number)
|
||||
end
|
||||
html << ' '
|
||||
end
|
||||
|
||||
if always_show_anchors and not (wp_last = window_pages[-1]).last?
|
||||
html << ' ... ' if last.number - wp_last.number > 1
|
||||
html << yield(last.number)
|
||||
end
|
||||
|
||||
html << suffix if suffix
|
||||
|
||||
html
|
||||
end
|
||||
|
||||
end # PaginationHelper
|
||||
end # Helpers
|
||||
end # ActionView
|
||||
24
vendor/plugins/classic_pagination/test/fixtures/companies.yml
vendored
Normal file
24
vendor/plugins/classic_pagination/test/fixtures/companies.yml
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
thirty_seven_signals:
|
||||
id: 1
|
||||
name: 37Signals
|
||||
rating: 4
|
||||
|
||||
TextDrive:
|
||||
id: 2
|
||||
name: TextDrive
|
||||
rating: 4
|
||||
|
||||
PlanetArgon:
|
||||
id: 3
|
||||
name: Planet Argon
|
||||
rating: 4
|
||||
|
||||
Google:
|
||||
id: 4
|
||||
name: Google
|
||||
rating: 4
|
||||
|
||||
Ionist:
|
||||
id: 5
|
||||
name: Ioni.st
|
||||
rating: 4
|
||||
9
vendor/plugins/classic_pagination/test/fixtures/company.rb
vendored
Normal file
9
vendor/plugins/classic_pagination/test/fixtures/company.rb
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
class Company < ActiveRecord::Base
|
||||
attr_protected :rating
|
||||
set_sequence_name :companies_nonstd_seq
|
||||
|
||||
validates_presence_of :name
|
||||
def validate
|
||||
errors.add('rating', 'rating should not be 2') if rating == 2
|
||||
end
|
||||
end
|
||||
7
vendor/plugins/classic_pagination/test/fixtures/developer.rb
vendored
Normal file
7
vendor/plugins/classic_pagination/test/fixtures/developer.rb
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
class Developer < ActiveRecord::Base
|
||||
has_and_belongs_to_many :projects
|
||||
end
|
||||
|
||||
class DeVeLoPeR < ActiveRecord::Base
|
||||
set_table_name "developers"
|
||||
end
|
||||
21
vendor/plugins/classic_pagination/test/fixtures/developers.yml
vendored
Normal file
21
vendor/plugins/classic_pagination/test/fixtures/developers.yml
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
david:
|
||||
id: 1
|
||||
name: David
|
||||
salary: 80000
|
||||
|
||||
jamis:
|
||||
id: 2
|
||||
name: Jamis
|
||||
salary: 150000
|
||||
|
||||
<% for digit in 3..10 %>
|
||||
dev_<%= digit %>:
|
||||
id: <%= digit %>
|
||||
name: fixture_<%= digit %>
|
||||
salary: 100000
|
||||
<% end %>
|
||||
|
||||
poor_jamis:
|
||||
id: 11
|
||||
name: Jamis
|
||||
salary: 9000
|
||||
13
vendor/plugins/classic_pagination/test/fixtures/developers_projects.yml
vendored
Normal file
13
vendor/plugins/classic_pagination/test/fixtures/developers_projects.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
david_action_controller:
|
||||
developer_id: 1
|
||||
project_id: 2
|
||||
joined_on: 2004-10-10
|
||||
|
||||
david_active_record:
|
||||
developer_id: 1
|
||||
project_id: 1
|
||||
joined_on: 2004-10-10
|
||||
|
||||
jamis_active_record:
|
||||
developer_id: 2
|
||||
project_id: 1
|
||||
3
vendor/plugins/classic_pagination/test/fixtures/project.rb
vendored
Normal file
3
vendor/plugins/classic_pagination/test/fixtures/project.rb
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Project < ActiveRecord::Base
|
||||
has_and_belongs_to_many :developers, :uniq => true
|
||||
end
|
||||
7
vendor/plugins/classic_pagination/test/fixtures/projects.yml
vendored
Normal file
7
vendor/plugins/classic_pagination/test/fixtures/projects.yml
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
action_controller:
|
||||
id: 2
|
||||
name: Active Controller
|
||||
|
||||
active_record:
|
||||
id: 1
|
||||
name: Active Record
|
||||
13
vendor/plugins/classic_pagination/test/fixtures/replies.yml
vendored
Normal file
13
vendor/plugins/classic_pagination/test/fixtures/replies.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
witty_retort:
|
||||
id: 1
|
||||
topic_id: 1
|
||||
content: Birdman is better!
|
||||
created_at: <%= 6.hours.ago.to_s(:db) %>
|
||||
updated_at: nil
|
||||
|
||||
another:
|
||||
id: 2
|
||||
topic_id: 2
|
||||
content: Nuh uh!
|
||||
created_at: <%= 1.hour.ago.to_s(:db) %>
|
||||
updated_at: nil
|
||||
5
vendor/plugins/classic_pagination/test/fixtures/reply.rb
vendored
Normal file
5
vendor/plugins/classic_pagination/test/fixtures/reply.rb
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
class Reply < ActiveRecord::Base
|
||||
belongs_to :topic, :include => [:replies]
|
||||
|
||||
validates_presence_of :content
|
||||
end
|
||||
42
vendor/plugins/classic_pagination/test/fixtures/schema.sql
vendored
Normal file
42
vendor/plugins/classic_pagination/test/fixtures/schema.sql
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
CREATE TABLE 'companies' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'name' TEXT DEFAULT NULL,
|
||||
'rating' INTEGER DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE 'replies' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'content' text,
|
||||
'created_at' datetime,
|
||||
'updated_at' datetime,
|
||||
'topic_id' integer
|
||||
);
|
||||
|
||||
CREATE TABLE 'topics' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'title' varchar(255),
|
||||
'subtitle' varchar(255),
|
||||
'content' text,
|
||||
'created_at' datetime,
|
||||
'updated_at' datetime
|
||||
);
|
||||
|
||||
CREATE TABLE 'developers' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'name' TEXT DEFAULT NULL,
|
||||
'salary' INTEGER DEFAULT 70000,
|
||||
'created_at' DATETIME DEFAULT NULL,
|
||||
'updated_at' DATETIME DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE 'projects' (
|
||||
'id' INTEGER PRIMARY KEY NOT NULL,
|
||||
'name' TEXT DEFAULT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE 'developers_projects' (
|
||||
'developer_id' INTEGER NOT NULL,
|
||||
'project_id' INTEGER NOT NULL,
|
||||
'joined_on' DATE DEFAULT NULL,
|
||||
'access_level' INTEGER DEFAULT 1
|
||||
);
|
||||
3
vendor/plugins/classic_pagination/test/fixtures/topic.rb
vendored
Normal file
3
vendor/plugins/classic_pagination/test/fixtures/topic.rb
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
class Topic < ActiveRecord::Base
|
||||
has_many :replies, :include => [:user], :dependent => :destroy
|
||||
end
|
||||
22
vendor/plugins/classic_pagination/test/fixtures/topics.yml
vendored
Normal file
22
vendor/plugins/classic_pagination/test/fixtures/topics.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
futurama:
|
||||
id: 1
|
||||
title: Isnt futurama awesome?
|
||||
subtitle: It really is, isnt it.
|
||||
content: I like futurama
|
||||
created_at: <%= 1.day.ago.to_s(:db) %>
|
||||
updated_at:
|
||||
|
||||
harvey_birdman:
|
||||
id: 2
|
||||
title: Harvey Birdman is the king of all men
|
||||
subtitle: yup
|
||||
content: It really is
|
||||
created_at: <%= 2.hours.ago.to_s(:db) %>
|
||||
updated_at:
|
||||
|
||||
rails:
|
||||
id: 3
|
||||
title: Rails is nice
|
||||
subtitle: It makes me happy
|
||||
content: except when I have to hack internals to fix pagination. even then really.
|
||||
created_at: <%= 20.minutes.ago.to_s(:db) %>
|
||||
117
vendor/plugins/classic_pagination/test/helper.rb
vendored
Normal file
117
vendor/plugins/classic_pagination/test/helper.rb
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
require 'test/unit'
|
||||
|
||||
unless defined?(ActiveRecord)
|
||||
plugin_root = File.join(File.dirname(__FILE__), '..')
|
||||
|
||||
# first look for a symlink to a copy of the framework
|
||||
if framework_root = ["#{plugin_root}/rails", "#{plugin_root}/../../rails"].find { |p| File.directory? p }
|
||||
puts "found framework root: #{framework_root}"
|
||||
# this allows for a plugin to be tested outside an app
|
||||
$:.unshift "#{framework_root}/activesupport/lib", "#{framework_root}/activerecord/lib", "#{framework_root}/actionpack/lib"
|
||||
else
|
||||
# is the plugin installed in an application?
|
||||
app_root = plugin_root + '/../../..'
|
||||
|
||||
if File.directory? app_root + '/config'
|
||||
puts 'using config/boot.rb'
|
||||
ENV['RAILS_ENV'] = 'test'
|
||||
require File.expand_path(app_root + '/config/boot')
|
||||
else
|
||||
# simply use installed gems if available
|
||||
puts 'using rubygems'
|
||||
require 'rubygems'
|
||||
gem 'actionpack'; gem 'activerecord'
|
||||
end
|
||||
end
|
||||
|
||||
%w(action_pack active_record action_controller active_record/fixtures action_controller/test_process).each {|f| require f}
|
||||
|
||||
Dependencies.load_paths.unshift "#{plugin_root}/lib"
|
||||
end
|
||||
|
||||
# Define the connector
|
||||
class ActiveRecordTestConnector
|
||||
cattr_accessor :able_to_connect
|
||||
cattr_accessor :connected
|
||||
|
||||
# Set our defaults
|
||||
self.connected = false
|
||||
self.able_to_connect = true
|
||||
|
||||
class << self
|
||||
def setup
|
||||
unless self.connected || !self.able_to_connect
|
||||
setup_connection
|
||||
load_schema
|
||||
require_fixture_models
|
||||
self.connected = true
|
||||
end
|
||||
rescue Exception => e # errors from ActiveRecord setup
|
||||
$stderr.puts "\nSkipping ActiveRecord assertion tests: #{e}"
|
||||
#$stderr.puts " #{e.backtrace.join("\n ")}\n"
|
||||
self.able_to_connect = false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def setup_connection
|
||||
if Object.const_defined?(:ActiveRecord)
|
||||
defaults = { :database => ':memory:' }
|
||||
begin
|
||||
options = defaults.merge :adapter => 'sqlite3', :timeout => 500
|
||||
ActiveRecord::Base.establish_connection(options)
|
||||
ActiveRecord::Base.configurations = { 'sqlite3_ar_integration' => options }
|
||||
ActiveRecord::Base.connection
|
||||
rescue Exception # errors from establishing a connection
|
||||
$stderr.puts 'SQLite 3 unavailable; trying SQLite 2.'
|
||||
options = defaults.merge :adapter => 'sqlite'
|
||||
ActiveRecord::Base.establish_connection(options)
|
||||
ActiveRecord::Base.configurations = { 'sqlite2_ar_integration' => options }
|
||||
ActiveRecord::Base.connection
|
||||
end
|
||||
|
||||
Object.send(:const_set, :QUOTED_TYPE, ActiveRecord::Base.connection.quote_column_name('type')) unless Object.const_defined?(:QUOTED_TYPE)
|
||||
else
|
||||
raise "Can't setup connection since ActiveRecord isn't loaded."
|
||||
end
|
||||
end
|
||||
|
||||
# Load actionpack sqlite tables
|
||||
def load_schema
|
||||
File.read(File.dirname(__FILE__) + "/fixtures/schema.sql").split(';').each do |sql|
|
||||
ActiveRecord::Base.connection.execute(sql) unless sql.blank?
|
||||
end
|
||||
end
|
||||
|
||||
def require_fixture_models
|
||||
Dir.glob(File.dirname(__FILE__) + "/fixtures/*.rb").each {|f| require f}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Test case for inheritance
|
||||
class ActiveRecordTestCase < Test::Unit::TestCase
|
||||
# Set our fixture path
|
||||
if ActiveRecordTestConnector.able_to_connect
|
||||
self.fixture_path = "#{File.dirname(__FILE__)}/fixtures/"
|
||||
self.use_transactional_fixtures = false
|
||||
end
|
||||
|
||||
def self.fixtures(*args)
|
||||
super if ActiveRecordTestConnector.connected
|
||||
end
|
||||
|
||||
def run(*args)
|
||||
super if ActiveRecordTestConnector.connected
|
||||
end
|
||||
|
||||
# Default so Test::Unit::TestCase doesn't complain
|
||||
def test_truth
|
||||
end
|
||||
end
|
||||
|
||||
ActiveRecordTestConnector.setup
|
||||
ActionController::Routing::Routes.reload rescue nil
|
||||
ActionController::Routing::Routes.draw do |map|
|
||||
map.connect ':controller/:action/:id'
|
||||
end
|
||||
38
vendor/plugins/classic_pagination/test/pagination_helper_test.rb
vendored
Normal file
38
vendor/plugins/classic_pagination/test/pagination_helper_test.rb
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
require File.dirname(__FILE__) + '/helper'
|
||||
require File.dirname(__FILE__) + '/../init'
|
||||
|
||||
class PaginationHelperTest < Test::Unit::TestCase
|
||||
include ActionController::Pagination
|
||||
include ActionView::Helpers::PaginationHelper
|
||||
include ActionView::Helpers::UrlHelper
|
||||
include ActionView::Helpers::TagHelper
|
||||
|
||||
def setup
|
||||
@controller = Class.new do
|
||||
attr_accessor :url, :request
|
||||
def url_for(options, *parameters_for_method_reference)
|
||||
url
|
||||
end
|
||||
end
|
||||
@controller = @controller.new
|
||||
@controller.url = "http://www.example.com"
|
||||
end
|
||||
|
||||
def test_pagination_links
|
||||
total, per_page, page = 30, 10, 1
|
||||
output = pagination_links Paginator.new(@controller, total, per_page, page)
|
||||
assert_equal "1 <a href=\"http://www.example.com\">2</a> <a href=\"http://www.example.com\">3</a> ", output
|
||||
end
|
||||
|
||||
def test_pagination_links_with_prefix
|
||||
total, per_page, page = 30, 10, 1
|
||||
output = pagination_links Paginator.new(@controller, total, per_page, page), :prefix => 'Newer '
|
||||
assert_equal "Newer 1 <a href=\"http://www.example.com\">2</a> <a href=\"http://www.example.com\">3</a> ", output
|
||||
end
|
||||
|
||||
def test_pagination_links_with_suffix
|
||||
total, per_page, page = 30, 10, 1
|
||||
output = pagination_links Paginator.new(@controller, total, per_page, page), :suffix => 'Older'
|
||||
assert_equal "1 <a href=\"http://www.example.com\">2</a> <a href=\"http://www.example.com\">3</a> Older", output
|
||||
end
|
||||
end
|
||||
177
vendor/plugins/classic_pagination/test/pagination_test.rb
vendored
Normal file
177
vendor/plugins/classic_pagination/test/pagination_test.rb
vendored
Normal file
|
|
@ -0,0 +1,177 @@
|
|||
require File.dirname(__FILE__) + '/helper'
|
||||
require File.dirname(__FILE__) + '/../init'
|
||||
|
||||
class PaginationTest < ActiveRecordTestCase
|
||||
fixtures :topics, :replies, :developers, :projects, :developers_projects
|
||||
|
||||
class PaginationController < ActionController::Base
|
||||
if respond_to? :view_paths=
|
||||
self.view_paths = [ "#{File.dirname(__FILE__)}/../fixtures/" ]
|
||||
else
|
||||
self.template_root = [ "#{File.dirname(__FILE__)}/../fixtures/" ]
|
||||
end
|
||||
|
||||
def simple_paginate
|
||||
@topic_pages, @topics = paginate(:topics)
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_per_page
|
||||
@topic_pages, @topics = paginate(:topics, :per_page => 1)
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_order
|
||||
@topic_pages, @topics = paginate(:topics, :order => 'created_at asc')
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_order_by
|
||||
@topic_pages, @topics = paginate(:topics, :order_by => 'created_at asc')
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_include_and_order
|
||||
@topic_pages, @topics = paginate(:topics, :include => :replies, :order => 'replies.created_at asc, topics.created_at asc')
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_conditions
|
||||
@topic_pages, @topics = paginate(:topics, :conditions => ["created_at > ?", 30.minutes.ago])
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_class_name
|
||||
@developer_pages, @developers = paginate(:developers, :class_name => "DeVeLoPeR")
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_singular_name
|
||||
@developer_pages, @developers = paginate()
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_joins
|
||||
@developer_pages, @developers = paginate(:developers,
|
||||
:joins => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
|
||||
:conditions => 'project_id=1')
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_join
|
||||
@developer_pages, @developers = paginate(:developers,
|
||||
:join => 'LEFT JOIN developers_projects ON developers.id = developers_projects.developer_id',
|
||||
:conditions => 'project_id=1')
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_join_and_count
|
||||
@developer_pages, @developers = paginate(:developers,
|
||||
:join => 'd LEFT JOIN developers_projects ON d.id = developers_projects.developer_id',
|
||||
:conditions => 'project_id=1',
|
||||
:count => "d.id")
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def paginate_with_join_and_group
|
||||
@developer_pages, @developers = paginate(:developers,
|
||||
:join => 'INNER JOIN developers_projects ON developers.id = developers_projects.developer_id',
|
||||
:group => 'developers.id')
|
||||
render :nothing => true
|
||||
end
|
||||
|
||||
def rescue_errors(e) raise e end
|
||||
|
||||
def rescue_action(e) raise end
|
||||
|
||||
end
|
||||
|
||||
def setup
|
||||
@controller = PaginationController.new
|
||||
@request = ActionController::TestRequest.new
|
||||
@response = ActionController::TestResponse.new
|
||||
super
|
||||
end
|
||||
|
||||
# Single Action Pagination Tests
|
||||
|
||||
def test_simple_paginate
|
||||
get :simple_paginate
|
||||
assert_equal 1, assigns(:topic_pages).page_count
|
||||
assert_equal 3, assigns(:topics).size
|
||||
end
|
||||
|
||||
def test_paginate_with_per_page
|
||||
get :paginate_with_per_page
|
||||
assert_equal 1, assigns(:topics).size
|
||||
assert_equal 3, assigns(:topic_pages).page_count
|
||||
end
|
||||
|
||||
def test_paginate_with_order
|
||||
get :paginate_with_order
|
||||
expected = [topics(:futurama),
|
||||
topics(:harvey_birdman),
|
||||
topics(:rails)]
|
||||
assert_equal expected, assigns(:topics)
|
||||
assert_equal 1, assigns(:topic_pages).page_count
|
||||
end
|
||||
|
||||
def test_paginate_with_order_by
|
||||
get :paginate_with_order
|
||||
expected = assigns(:topics)
|
||||
get :paginate_with_order_by
|
||||
assert_equal expected, assigns(:topics)
|
||||
assert_equal 1, assigns(:topic_pages).page_count
|
||||
end
|
||||
|
||||
def test_paginate_with_conditions
|
||||
get :paginate_with_conditions
|
||||
expected = [topics(:rails)]
|
||||
assert_equal expected, assigns(:topics)
|
||||
assert_equal 1, assigns(:topic_pages).page_count
|
||||
end
|
||||
|
||||
def test_paginate_with_class_name
|
||||
get :paginate_with_class_name
|
||||
|
||||
assert assigns(:developers).size > 0
|
||||
assert_equal DeVeLoPeR, assigns(:developers).first.class
|
||||
end
|
||||
|
||||
def test_paginate_with_joins
|
||||
get :paginate_with_joins
|
||||
assert_equal 2, assigns(:developers).size
|
||||
developer_names = assigns(:developers).map { |d| d.name }
|
||||
assert developer_names.include?('David')
|
||||
assert developer_names.include?('Jamis')
|
||||
end
|
||||
|
||||
def test_paginate_with_join_and_conditions
|
||||
get :paginate_with_joins
|
||||
expected = assigns(:developers)
|
||||
get :paginate_with_join
|
||||
assert_equal expected, assigns(:developers)
|
||||
end
|
||||
|
||||
def test_paginate_with_join_and_count
|
||||
get :paginate_with_joins
|
||||
expected = assigns(:developers)
|
||||
get :paginate_with_join_and_count
|
||||
assert_equal expected, assigns(:developers)
|
||||
end
|
||||
|
||||
def test_paginate_with_include_and_order
|
||||
get :paginate_with_include_and_order
|
||||
expected = Topic.find(:all, :include => 'replies', :order => 'replies.created_at asc, topics.created_at asc', :limit => 10)
|
||||
assert_equal expected, assigns(:topics)
|
||||
end
|
||||
|
||||
def test_paginate_with_join_and_group
|
||||
get :paginate_with_join_and_group
|
||||
assert_equal 2, assigns(:developers).size
|
||||
assert_equal 2, assigns(:developer_pages).item_count
|
||||
developer_names = assigns(:developers).map { |d| d.name }
|
||||
assert developer_names.include?('David')
|
||||
assert developer_names.include?('Jamis')
|
||||
end
|
||||
end
|
||||
2
vendor/plugins/extra_validations/init.rb
vendored
Normal file
2
vendor/plugins/extra_validations/init.rb
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
require 'extra_validations'
|
||||
ActiveRecord::Base.extend ExtraValidations
|
||||
29
vendor/plugins/extra_validations/lib/extra_validations.rb
vendored
Normal file
29
vendor/plugins/extra_validations/lib/extra_validations.rb
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
module ExtraValidations
|
||||
|
||||
# Validates the value of the specified attribute by checking for a forbidden string
|
||||
#
|
||||
# class Person < ActiveRecord::Base
|
||||
# validates_does_not_contain :first_name, :string => ','
|
||||
# end
|
||||
#
|
||||
# A string must be provided or else an exception will be raised.
|
||||
#
|
||||
# Configuration options:
|
||||
# * <tt>message</tt> - A custom error message (default is: "is invalid")
|
||||
# * <tt>string</tt> - The string to verify is not included (note: must be supplied!)
|
||||
# * <tt>on</tt> Specifies when this validation is active (default is :save, other options :create, :update)
|
||||
# * <tt>if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
||||
# occur (e.g. :if => :allow_validation, or :if => Proc.new { |user| user.signup_step > 2 }). The
|
||||
# method, proc or string should return or evaluate to a true or false value.
|
||||
def validates_does_not_contain(*attr_names)
|
||||
configuration = { :message => ActiveRecord::Errors.default_error_messages[:invalid], :on => :save, :string => nil }
|
||||
configuration.update(attr_names.pop) if attr_names.last.is_a?(Hash)
|
||||
|
||||
raise(ArgumentError, "A string must be supplied as the :string option of the configuration hash") unless configuration[:string].is_a?(String)
|
||||
|
||||
validates_each(attr_names, configuration) do |record, attr_name, value|
|
||||
record.errors.add(attr_name, configuration[:message]) if value.to_s =~ Regexp.new(Regexp.escape(configuration[:string]))
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
35
vendor/plugins/flashobject_helper/README
vendored
Normal file
35
vendor/plugins/flashobject_helper/README
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
FlashObject v.1.0
|
||||
====================
|
||||
This FlashObject Helper is based on the flash view helper of the plugin http://laszlo-plugin.rubyforge.org/
|
||||
|
||||
It's only copy the FlashObject.js (version 1.5) on your public/javascript directory and register it in the AssetTagHelper of rails,
|
||||
so for include the javascript in your page you only can add this (if you havn't yet):
|
||||
|
||||
<%= javascript_include_tag :defaults %>
|
||||
|
||||
or if you only need the flashobject add this:
|
||||
|
||||
<%= javascript_include_tag "flashobject" %>
|
||||
|
||||
The is very simple for add a Flash in your page add this:
|
||||
|
||||
<%= flashobject_tag "/source/of/your/flash.swf" %>
|
||||
|
||||
It's simple.
|
||||
|
||||
You can add some options if you want:
|
||||
|
||||
- div_id: the HTML +id+ of the +div+ element that is used to contain the Flash object; default "flashcontent"
|
||||
- flash_id: the +id+ of the Flash object itself.
|
||||
- background_color: the background color of the Flash object; default white
|
||||
- flash_version: the version of the Flash player that is required; default "7"
|
||||
- size: the size of the Flash object, in the form "100x100". Defaults to "100%x100%"
|
||||
- variables: a Hash of initialization variables that are passed to the object; default <tt>{:lzproxied => false}</tt>
|
||||
- parameters: a Hash of parameters that configure the display of the object; default <tt>{:scale => 'noscale'}</tt>
|
||||
- fallback_html: HTML text that is displayed when the Flash player is not available.
|
||||
|
||||
Example:
|
||||
<%= flashobject_tag "/source/of/your/flash.swf", :size => "350x320" %>
|
||||
|
||||
|
||||
Vist my website: http://blog.lipsiasoft.com
|
||||
4
vendor/plugins/flashobject_helper/init.rb
vendored
Normal file
4
vendor/plugins/flashobject_helper/init.rb
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
# Author:: Davide D'Agostino Aka DAddYE
|
||||
# WebSite:: http://www.lipsiasoft.com
|
||||
|
||||
require 'flashobject_view_helper'
|
||||
5
vendor/plugins/flashobject_helper/install.rb
vendored
Normal file
5
vendor/plugins/flashobject_helper/install.rb
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'fileutils'
|
||||
|
||||
flashobject = File.dirname(__FILE__) + '/../../../public/javascripts/flashobject.js'
|
||||
FileUtils.cp File.dirname(__FILE__) + '/javascripts/flashobject.js', flashobject unless File.exist?(flashobject)
|
||||
puts IO.read(File.join(File.dirname(__FILE__), 'README'))
|
||||
8
vendor/plugins/flashobject_helper/javascripts/flashobject.js
vendored
Normal file
8
vendor/plugins/flashobject_helper/javascripts/flashobject.js
vendored
Normal file
File diff suppressed because one or more lines are too long
85
vendor/plugins/flashobject_helper/lib/flashobject_view_helper.rb
vendored
Normal file
85
vendor/plugins/flashobject_helper/lib/flashobject_view_helper.rb
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
# Author:: Davide D'Agostino aka DAddYE
|
||||
# WebSite:: http://www.lipsiasoft.com
|
||||
require 'action_view'
|
||||
|
||||
module ActionView #:nodoc:
|
||||
module Helpers # :nodoc:
|
||||
module FlashObjectHelper # :nodoc:
|
||||
def self.included(base)
|
||||
base.class_eval do
|
||||
include InstanceMethods
|
||||
end
|
||||
end
|
||||
module InstanceMethods
|
||||
# Returns a set of tags that display a Flash object within an
|
||||
# HTML page.
|
||||
#
|
||||
# Options:
|
||||
# * <tt>:div_id</tt> - the HTML +id+ of the +div+ element that is used to contain the Flash object; default "flashcontent"
|
||||
# * <tt>:flash_id</tt> - the +id+ of the Flash object itself.
|
||||
# * <tt>:background_color</tt> - the background color of the Flash object; default white
|
||||
# * <tt>:flash_version</tt> - the version of the Flash player that is required; default "7"
|
||||
# * <tt>:size</tt> - the size of the Flash object, in the form "100x100". Defaults to "100%x100%"
|
||||
# * <tt>:variables</tt> - a Hash of initialization variables that are passed to the object; default <tt>{:lzproxied => false}</tt>
|
||||
# * <tt>:parameters</tt> - a Hash of parameters that configure the display of the object; default <tt>{:scale => 'noscale'}</tt>
|
||||
# * <tt>:fallback_html</tt> - HTML text that is displayed when the Flash player is not available.
|
||||
#
|
||||
# The following options are for developers. They default to true in
|
||||
# development mode, and false otherwise.
|
||||
# * <tt>:check_for_javascript_include</tt> - if true, the return value will cause the browser to display a diagnostic message if the FlashObject JavaScript was not included.
|
||||
# * <tt>:verify_file_exists</tt> - if true, the return value will cause the browser to display a diagnostic message if the Flash object does not exist.
|
||||
#
|
||||
# (This method is called flashobject_tag instead of flashobject_tag
|
||||
# because it returns a *sequence* of HTML tags: a +div+, followed by
|
||||
# a +script+.)
|
||||
def flashobject_tag source, options={}
|
||||
source = flash_path(source)
|
||||
query_params = '?' + options[:query_params].map{ |key, value| "#{key}=#{value}" }.join('&') if options[:query_params]
|
||||
div_id = options[:div_id] || "flashcontent_#{rand(1_100)}"
|
||||
flash_id = options[:flash_id] || File.basename(source, '.swf') + "_#{rand(1_100)}"
|
||||
width, height = (options[:size]||'100%x100%').scan(/^(\d*%?)x(\d*%?)$/).first
|
||||
background_color = options[:background_color] || '#ffffff'
|
||||
flash_version = options[:flash_version] || 7
|
||||
class_name = options[:class_name] || 'flash'
|
||||
variables = options.fetch(:variables, {})
|
||||
parameters = options.fetch(:parameters, {:scale => 'noscale'})
|
||||
fallback_html = options[:fallback_html] || %q{<p>Requires the Flash plugin. If the plugin is already installed, click <a href="?detectflash=false">here</a>.</p>}
|
||||
if options.fetch(:check_for_javascript_include, ENV['RAILS_ENV'] == 'development')
|
||||
check_for_javascript ="if (typeof FlashObject == 'undefined') document.getElementById('#{div_id}').innerHTML = '<strong>Warning:</strong> FlashObject is undefined. Did you forget to execute <tt>rake update_javascripts</tt>, or to include <tt><%= javascript_include_tag :defaults %></tt> in your view file?';"
|
||||
end
|
||||
return <<-"EOF"
|
||||
<div id="#{div_id}" class="#{class_name}" style="height: #{height}">
|
||||
#{fallback_html}
|
||||
</div>
|
||||
<script type="text/javascript">//<![CDATA[
|
||||
#{check_for_javascript}
|
||||
var fo = new FlashObject("#{source}#{query_params}", "#{flash_id}", "#{width}", "#{height}", "#{flash_version}", "#{background_color}");
|
||||
#{parameters.map{|k,v|%Q[fo.addParam("#{k}", "#{v}");]}.join("\n")}
|
||||
#{variables.map{|k,v|%Q[fo.addVariable("#{k}", "#{v}");]}.join("\n")}
|
||||
fo.write("#{div_id}");
|
||||
//]]>
|
||||
</script>
|
||||
EOF
|
||||
end
|
||||
|
||||
# Computes the path to a flash asset in the public swf directory.
|
||||
# If the +source+ filename has no extension, .swf will be appended.
|
||||
# Full paths from the document root will be passed through.
|
||||
#
|
||||
# flash_path "movie" # => /swf/movie.swf
|
||||
# flash_path "dir/movie.swf" # => /swf/dir/movie.swf
|
||||
# flash_path "/dir/movie" # => /dir/movie.swf
|
||||
def flash_path(source)
|
||||
compute_public_path(source, 'swf', 'swf', false)
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ActionView::Base.class_eval do
|
||||
include ActionView::Helpers::FlashObjectHelper
|
||||
end
|
||||
|
||||
ActionView::Helpers::AssetTagHelper.register_javascript_include_default 'flashobject'
|
||||
81
vendor/plugins/has_many_polymorphs/CHANGELOG
vendored
Normal file
81
vendor/plugins/has_many_polymorphs/CHANGELOG
vendored
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
|
||||
v2.11. Rails 1.2.6 tagging generator compatibility; change test suite to use included integration app; include commenting generator (not well tested) [Josh Stephenson].
|
||||
|
||||
v2.10. Add :parent_conditions option; bugfix for nullified conditions; bugfix for self-referential tagging generator; allow setting of has_many_polymorphs_options hash in Configuration's after_initialize if you need to adjust the autoload behavior; clear error message on missing or improperly namespaced models; fix .build on double-sided relationships; add :namespace key for easier set up of Camping apps or other unusual class structures.
|
||||
|
||||
v2.9. Gem version renumbering; my apologies if this messes anyone up.
|
||||
|
||||
v2.8. RDoc documentation; repository relocation; Rakefile cleanup; remove deprecated plugin-specific class caching.
|
||||
|
||||
v2.7.5. Various bugfixes; Postgres problems may remain on edge.
|
||||
|
||||
v2.7.3. Use new :source and :source_type options in 1.2.3 (David Lemstra); fix pluralization bug; add some tests; experimental tagging generator.
|
||||
|
||||
v2.7.2. Deprecate has_many_polymorphs_cache_classes= option because it doesn't really work. Use config.cache_classes= instead to cache all reloadable items.
|
||||
|
||||
v2.7.1. Dispatcher.to_prepare didn't fire in the console; now using a config.after_initialize wrapper instead.
|
||||
|
||||
v2.7. Dependency injection framework elimates having to care about load order.
|
||||
|
||||
v2.6. Make the logger act sane for the gem version.
|
||||
|
||||
v2.5.2. Allow :skip_duplicates on double relationships.
|
||||
|
||||
v2.5.1. Renamed :ignore_duplicates to :skip_duplicates to better express its non-passive behavior; made sure not to load target set on push unless necessary.
|
||||
|
||||
v2.5. Activerecord compatibility branch becomes trunk: extra options now supported for double polymorphism; conditions nulled-out and propogated to child relationships; more tests; new :ignore_duplicates option on macro can be set to false if you want << to push duplicate associations.
|
||||
|
||||
v2.4.1. Code split into multiple files; tests added for pluralization check; Rails 1.1.6 no longer supported.
|
||||
|
||||
v2.4. Unlimited mixed class association extensions for both single and double targets and joins.
|
||||
|
||||
v2.3. Gem version
|
||||
|
||||
v2.2. API change; prefix on methods is now singular when using :rename_individual_collections.
|
||||
|
||||
v2.1. Add configuration option to cache polymorphic classes in development mode.
|
||||
|
||||
v2.0. Collection methods (push, delete, clear) now on individual collections.
|
||||
|
||||
v1.9.2. Disjoint collection sides bugfix, don't raise on new records.
|
||||
|
||||
v1.9.1. Double classify bugfix.
|
||||
|
||||
v1.9. Large changes to properly support double polymorphism.
|
||||
|
||||
v1.8.2. Bugfix to make sure the type gets checked on doubly polymorphic parents.
|
||||
|
||||
v1.8.1. Bugfix for sqlite3 child attribute retrieval.
|
||||
|
||||
v1.8. Bugfix for instantiating attributes of namespaced models.
|
||||
|
||||
v1.7.1. Bugfix for double polymorphic relationships.
|
||||
|
||||
v1.7. Double polymorphic relationships (includes new API method).
|
||||
|
||||
v1.6. Namespaced model support.
|
||||
|
||||
v1.5. Bugfix for Postgres and Mysql under 1.1.6; refactored tests (hildofur); properly handles legacy table names set with set_table_name().
|
||||
|
||||
v1.4. STI support added (use the child class names, not the base class).
|
||||
|
||||
v1.3. Bug regarding table names with underscores in SQL query fixed.
|
||||
|
||||
v1.2. License change, again.
|
||||
|
||||
v1.1. File_column bug fixed.
|
||||
|
||||
v1.0. Tests written; after_find and after_initialize now correctly called.
|
||||
|
||||
v0.5. SQL performance enhancements added.
|
||||
|
||||
v0.4. Rewrote singletons as full-fledged proxy class so that marshalling works (e.g. in the session).
|
||||
|
||||
v0.3. Caching added.
|
||||
|
||||
v0.2. Fixed dependency reloading problem in development mode.
|
||||
|
||||
v0.1. License change.
|
||||
|
||||
v0. Added :dependent support on the join table; no changelog before this version.
|
||||
|
||||
184
vendor/plugins/has_many_polymorphs/LICENSE
vendored
Normal file
184
vendor/plugins/has_many_polymorphs/LICENSE
vendored
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
Academic Free License (AFL) v. 3.0
|
||||
|
||||
This Academic Free License (the "License") applies to any original work
|
||||
of authorship (the "Original Work") whose owner (the "Licensor") has
|
||||
placed the following licensing notice adjacent to the copyright notice
|
||||
for the Original Work:
|
||||
|
||||
Licensed under the Academic Free License version 3.0
|
||||
|
||||
1) Grant of Copyright License. Licensor grants You a worldwide,
|
||||
royalty-free, non-exclusive, sublicensable license, for the duration of
|
||||
the copyright, to do the following:
|
||||
|
||||
a) to reproduce the Original Work in copies, either alone or as part of
|
||||
a collective work;
|
||||
|
||||
b) to translate, adapt, alter, transform, modify, or arrange the
|
||||
Original Work, thereby creating derivative works ("Derivative Works")
|
||||
based upon the Original Work;
|
||||
|
||||
c) to distribute or communicate copies of the Original Work and
|
||||
Derivative Works to the public, under any license of your choice that
|
||||
does not contradict the terms and conditions, including Licensor's
|
||||
reserved rights and remedies, in this Academic Free License;
|
||||
|
||||
d) to perform the Original Work publicly; and
|
||||
|
||||
e) to display the Original Work publicly.
|
||||
|
||||
2) Grant of Patent License. Licensor grants You a worldwide,
|
||||
royalty-free, non-exclusive, sublicensable license, under patent claims
|
||||
owned or controlled by the Licensor that are embodied in the Original
|
||||
Work as furnished by the Licensor, for the duration of the patents, to
|
||||
make, use, sell, offer for sale, have made, and import the Original Work
|
||||
and Derivative Works.
|
||||
|
||||
3) Grant of Source Code License. The term "Source Code" means the
|
||||
preferred form of the Original Work for making modifications to it and
|
||||
all available documentation describing how to modify the Original Work.
|
||||
Licensor agrees to provide a machine-readable copy of the Source Code of
|
||||
the Original Work along with each copy of the Original Work that
|
||||
Licensor distributes. Licensor reserves the right to satisfy this
|
||||
obligation by placing a machine-readable copy of the Source Code in an
|
||||
information repository reasonably calculated to permit inexpensive and
|
||||
convenient access by You for as long as Licensor continues to distribute
|
||||
the Original Work.
|
||||
|
||||
4) Exclusions From License Grant. Neither the names of Licensor, nor the
|
||||
names of any contributors to the Original Work, nor any of their
|
||||
trademarks or service marks, may be used to endorse or promote products
|
||||
derived from this Original Work without express prior permission of the
|
||||
Licensor. Except as expressly stated herein, nothing in this License
|
||||
grants any license to Licensor's trademarks, copyrights, patents, trade
|
||||
secrets or any other intellectual property. No patent license is granted
|
||||
to make, use, sell, offer for sale, have made, or import embodiments of
|
||||
any patent claims other than the licensed claims defined in Section 2.
|
||||
No license is granted to the trademarks of Licensor even if such marks
|
||||
are included in the Original Work. Nothing in this License shall be
|
||||
interpreted to prohibit Licensor from licensing under terms different
|
||||
from this License any Original Work that Licensor otherwise would have a
|
||||
right to license.
|
||||
|
||||
5) External Deployment. The term "External Deployment" means the use,
|
||||
distribution, or communication of the Original Work or Derivative Works
|
||||
in any way such that the Original Work or Derivative Works may be used
|
||||
by anyone other than You, whether those works are distributed or
|
||||
communicated to those persons or made available as an application
|
||||
intended for use over a network. As an express condition for the grants
|
||||
of license hereunder, You must treat any External Deployment by You of
|
||||
the Original Work or a Derivative Work as a distribution under section
|
||||
1(c).
|
||||
|
||||
6) Attribution Rights. You must retain, in the Source Code of any
|
||||
Derivative Works that You create, all copyright, patent, or trademark
|
||||
notices from the Source Code of the Original Work, as well as any
|
||||
notices of licensing and any descriptive text identified therein as an
|
||||
"Attribution Notice." You must cause the Source Code for any Derivative
|
||||
Works that You create to carry a prominent Attribution Notice reasonably
|
||||
calculated to inform recipients that You have modified the Original
|
||||
Work.
|
||||
|
||||
7) Warranty of Provenance and Disclaimer of Warranty. Licensor warrants
|
||||
that the copyright in and to the Original Work and the patent rights
|
||||
granted herein by Licensor are owned by the Licensor or are sublicensed
|
||||
to You under the terms of this License with the permission of the
|
||||
contributor(s) of those copyrights and patent rights. Except as
|
||||
expressly stated in the immediately preceding sentence, the Original
|
||||
Work is provided under this License on an "AS IS" BASIS and WITHOUT
|
||||
WARRANTY, either express or implied, including, without limitation, the
|
||||
warranties of non-infringement, merchantability or fitness for a
|
||||
particular purpose. THE ENTIRE RISK AS TO THE QUALITY OF THE ORIGINAL
|
||||
WORK IS WITH YOU. This DISCLAIMER OF WARRANTY constitutes an essential
|
||||
part of this License. No license to the Original Work is granted by this
|
||||
License except under this disclaimer.
|
||||
|
||||
8) Limitation of Liability. Under no circumstances and under no legal
|
||||
theory, whether in tort (including negligence), contract, or otherwise,
|
||||
shall the Licensor be liable to anyone for any indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or the use of the Original Work including,
|
||||
without limitation, damages for loss of goodwill, work stoppage,
|
||||
computer failure or malfunction, or any and all other commercial damages
|
||||
or losses. This limitation of liability shall not apply to the extent
|
||||
applicable law prohibits such limitation.
|
||||
|
||||
9) Acceptance and Termination. If, at any time, You expressly assented
|
||||
to this License, that assent indicates your clear and irrevocable
|
||||
acceptance of this License and all of its terms and conditions. If You
|
||||
distribute or communicate copies of the Original Work or a Derivative
|
||||
Work, You must make a reasonable effort under the circumstances to
|
||||
obtain the express assent of recipients to the terms of this License.
|
||||
This License conditions your rights to undertake the activities listed
|
||||
in Section 1, including your right to create Derivative Works based upon
|
||||
the Original Work, and doing so without honoring these terms and
|
||||
conditions is prohibited by copyright law and international treaty.
|
||||
Nothing in this License is intended to affect copyright exceptions and
|
||||
limitations (including "fair use" or "fair dealing"). This License shall
|
||||
terminate immediately and You may no longer exercise any of the rights
|
||||
granted to You by this License upon your failure to honor the conditions
|
||||
in Section 1(c).
|
||||
|
||||
10) Termination for Patent Action. This License shall terminate
|
||||
automatically and You may no longer exercise any of the rights granted
|
||||
to You by this License as of the date You commence an action, including
|
||||
a cross-claim or counterclaim, against Licensor or any licensee alleging
|
||||
that the Original Work infringes a patent. This termination provision
|
||||
shall not apply for an action alleging patent infringement by
|
||||
combinations of the Original Work with other software or hardware.
|
||||
|
||||
11) Jurisdiction, Venue and Governing Law. Any action or suit relating
|
||||
to this License may be brought only in the courts of a jurisdiction
|
||||
wherein the Licensor resides or in which Licensor conducts its primary
|
||||
business, and under the laws of that jurisdiction excluding its
|
||||
conflict-of-law provisions. The application of the United Nations
|
||||
Convention on Contracts for the International Sale of Goods is expressly
|
||||
excluded. Any use of the Original Work outside the scope of this License
|
||||
or after its termination shall be subject to the requirements and
|
||||
penalties of copyright or patent law in the appropriate jurisdiction.
|
||||
This section shall survive the termination of this License.
|
||||
|
||||
12) Attorneys' Fees. In any action to enforce the terms of this License
|
||||
or seeking damages relating thereto, the prevailing party shall be
|
||||
entitled to recover its costs and expenses, including, without
|
||||
limitation, reasonable attorneys' fees and costs incurred in connection
|
||||
with such action, including any appeal of such action. This section
|
||||
shall survive the termination of this License.
|
||||
|
||||
13) Miscellaneous. If any provision of this License is held to be
|
||||
unenforceable, such provision shall be reformed only to the extent
|
||||
necessary to make it enforceable.
|
||||
|
||||
14) Definition of "You" in This License. "You" throughout this License,
|
||||
whether in upper or lower case, means an individual or a legal entity
|
||||
exercising rights under, and complying with all of the terms of, this
|
||||
License. For legal entities, "You" includes any entity that controls, is
|
||||
controlled by, or is under common control with you. For purposes of this
|
||||
definition, "control" means (i) the power, direct or indirect, to cause
|
||||
the direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
15) Right to Use. You may use the Original Work in all ways not
|
||||
otherwise restricted or conditioned by this License or by law, and
|
||||
Licensor promises not to interfere with or be responsible for such uses
|
||||
by You.
|
||||
|
||||
16) Modification of This License. This License is Copyright (c) 2005
|
||||
Lawrence Rosen. Permission is granted to copy, distribute, or
|
||||
communicate this License without modification. Nothing in this License
|
||||
permits You to modify this License as applied to the Original Work or to
|
||||
Derivative Works. However, You may modify the text of this License and
|
||||
copy, distribute or communicate your modified version (the "Modified
|
||||
License") and apply it to other original works of authorship subject to
|
||||
the following conditions: (i) You may not indicate in any way that your
|
||||
Modified License is the "Academic Free License" or "AFL" and you may not
|
||||
use those names in the name of your Modified License; (ii) You must
|
||||
replace the notice specified in the first paragraph above with the
|
||||
notice "Licensed under <insert your license name here>" or with a notice
|
||||
of your own that is not confusingly similar to the notice in this
|
||||
License; and (iii) You may not claim that your original works are open
|
||||
source software unless your Modified License has been approved by Open
|
||||
Source Initiative (OSI) and You comply with its license review and
|
||||
certification process.
|
||||
|
||||
192
vendor/plugins/has_many_polymorphs/Manifest
vendored
Normal file
192
vendor/plugins/has_many_polymorphs/Manifest
vendored
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
CHANGELOG
|
||||
examples/hmph.rb
|
||||
generators/commenting/commenting_generator.rb
|
||||
generators/commenting/templates/comment.rb
|
||||
generators/commenting/templates/comment_test.rb
|
||||
generators/commenting/templates/commenting.rb
|
||||
generators/commenting/templates/commenting_extensions.rb
|
||||
generators/commenting/templates/commenting_test.rb
|
||||
generators/commenting/templates/commentings.yml
|
||||
generators/commenting/templates/comments.yml
|
||||
generators/commenting/templates/migration.rb
|
||||
generators/tagging/tagging_generator.rb
|
||||
generators/tagging/templates/migration.rb
|
||||
generators/tagging/templates/tag.rb
|
||||
generators/tagging/templates/tag_test.rb
|
||||
generators/tagging/templates/tagging.rb
|
||||
generators/tagging/templates/tagging_extensions.rb
|
||||
generators/tagging/templates/tagging_test.rb
|
||||
generators/tagging/templates/taggings.yml
|
||||
generators/tagging/templates/tags.yml
|
||||
init.rb
|
||||
lib/has_many_polymorphs/association.rb
|
||||
lib/has_many_polymorphs/autoload.rb
|
||||
lib/has_many_polymorphs/base.rb
|
||||
lib/has_many_polymorphs/class_methods.rb
|
||||
lib/has_many_polymorphs/configuration.rb
|
||||
lib/has_many_polymorphs/debugging_tools.rb
|
||||
lib/has_many_polymorphs/dependencies.rb
|
||||
lib/has_many_polymorphs/rake_task_redefine_task.rb
|
||||
lib/has_many_polymorphs/reflection.rb
|
||||
lib/has_many_polymorphs/support_methods.rb
|
||||
lib/has_many_polymorphs.rb
|
||||
LICENSE
|
||||
Manifest
|
||||
README
|
||||
test/fixtures/bow_wows.yml
|
||||
test/fixtures/cats.yml
|
||||
test/fixtures/eaters_foodstuffs.yml
|
||||
test/fixtures/fish.yml
|
||||
test/fixtures/frogs.yml
|
||||
test/fixtures/keep_your_enemies_close.yml
|
||||
test/fixtures/little_whale_pupils.yml
|
||||
test/fixtures/people.yml
|
||||
test/fixtures/petfoods.yml
|
||||
test/fixtures/whales.yml
|
||||
test/fixtures/wild_boars.yml
|
||||
test/integration/app/app/controllers/addresses_controller.rb
|
||||
test/integration/app/app/controllers/application.rb
|
||||
test/integration/app/app/controllers/sellers_controller.rb
|
||||
test/integration/app/app/controllers/states_controller.rb
|
||||
test/integration/app/app/controllers/users_controller.rb
|
||||
test/integration/app/app/helpers/addresses_helper.rb
|
||||
test/integration/app/app/helpers/application_helper.rb
|
||||
test/integration/app/app/helpers/sellers_helper.rb
|
||||
test/integration/app/app/helpers/states_helper.rb
|
||||
test/integration/app/app/helpers/users_helper.rb
|
||||
test/integration/app/app/models/address.rb
|
||||
test/integration/app/app/models/citation.rb
|
||||
test/integration/app/app/models/citations_item.rb
|
||||
test/integration/app/app/models/seller.rb
|
||||
test/integration/app/app/models/state.rb
|
||||
test/integration/app/app/models/user.rb
|
||||
test/integration/app/app/views/addresses/edit.html.erb
|
||||
test/integration/app/app/views/addresses/index.html.erb
|
||||
test/integration/app/app/views/addresses/new.html.erb
|
||||
test/integration/app/app/views/addresses/show.html.erb
|
||||
test/integration/app/app/views/layouts/addresses.html.erb
|
||||
test/integration/app/app/views/layouts/sellers.html.erb
|
||||
test/integration/app/app/views/layouts/states.html.erb
|
||||
test/integration/app/app/views/layouts/users.html.erb
|
||||
test/integration/app/app/views/sellers/edit.html.erb
|
||||
test/integration/app/app/views/sellers/index.html.erb
|
||||
test/integration/app/app/views/sellers/new.html.erb
|
||||
test/integration/app/app/views/sellers/show.html.erb
|
||||
test/integration/app/app/views/states/edit.html.erb
|
||||
test/integration/app/app/views/states/index.html.erb
|
||||
test/integration/app/app/views/states/new.html.erb
|
||||
test/integration/app/app/views/states/show.html.erb
|
||||
test/integration/app/app/views/users/edit.html.erb
|
||||
test/integration/app/app/views/users/index.html.erb
|
||||
test/integration/app/app/views/users/new.html.erb
|
||||
test/integration/app/app/views/users/show.html.erb
|
||||
test/integration/app/config/boot.rb
|
||||
test/integration/app/config/database.yml
|
||||
test/integration/app/config/environment.rb
|
||||
test/integration/app/config/environments/development.rb
|
||||
test/integration/app/config/environments/production.rb
|
||||
test/integration/app/config/environments/test.rb
|
||||
test/integration/app/config/locomotive.yml
|
||||
test/integration/app/config/routes.rb
|
||||
test/integration/app/config/ultrasphinx/default.base
|
||||
test/integration/app/config/ultrasphinx/development.conf.canonical
|
||||
test/integration/app/db/migrate/001_create_users.rb
|
||||
test/integration/app/db/migrate/002_create_sellers.rb
|
||||
test/integration/app/db/migrate/003_create_addresses.rb
|
||||
test/integration/app/db/migrate/004_create_states.rb
|
||||
test/integration/app/db/migrate/005_add_capitalization_to_seller.rb
|
||||
test/integration/app/db/migrate/006_add_deleted_to_user.rb
|
||||
test/integration/app/db/migrate/007_add_lat_and_long_to_address.rb
|
||||
test/integration/app/db/migrate/008_create_citations.rb
|
||||
test/integration/app/db/migrate/009_create_citations_items.rb
|
||||
test/integration/app/db/schema.rb
|
||||
test/integration/app/doc/README_FOR_APP
|
||||
test/integration/app/generated_models/aquatic_fish.rb
|
||||
test/integration/app/generated_models/aquatic_pupils_whale.rb
|
||||
test/integration/app/generated_models/aquatic_whale.rb
|
||||
test/integration/app/generated_models/beautiful_fight_relationship.rb
|
||||
test/integration/app/generated_models/citation.rb
|
||||
test/integration/app/generated_models/citations_item.rb
|
||||
test/integration/app/generated_models/dog.rb
|
||||
test/integration/app/generated_models/eaters_foodstuff.rb
|
||||
test/integration/app/generated_models/frog.rb
|
||||
test/integration/app/generated_models/kitten.rb
|
||||
test/integration/app/generated_models/parentship.rb
|
||||
test/integration/app/generated_models/person.rb
|
||||
test/integration/app/generated_models/petfood.rb
|
||||
test/integration/app/generated_models/polymorph_test_some_model.rb
|
||||
test/integration/app/generated_models/seller.rb
|
||||
test/integration/app/generated_models/tabby.rb
|
||||
test/integration/app/generated_models/user.rb
|
||||
test/integration/app/generated_models/wild_boar.rb
|
||||
test/integration/app/generators/commenting_generator_test.rb
|
||||
test/integration/app/public/404.html
|
||||
test/integration/app/public/500.html
|
||||
test/integration/app/public/dispatch.cgi
|
||||
test/integration/app/public/dispatch.fcgi
|
||||
test/integration/app/public/dispatch.rb
|
||||
test/integration/app/public/favicon.ico
|
||||
test/integration/app/public/images/rails.png
|
||||
test/integration/app/public/index.html
|
||||
test/integration/app/public/javascripts/application.js
|
||||
test/integration/app/public/javascripts/controls.js
|
||||
test/integration/app/public/javascripts/dragdrop.js
|
||||
test/integration/app/public/javascripts/effects.js
|
||||
test/integration/app/public/javascripts/prototype.js
|
||||
test/integration/app/public/robots.txt
|
||||
test/integration/app/public/stylesheets/scaffold.css
|
||||
test/integration/app/Rakefile
|
||||
test/integration/app/README
|
||||
test/integration/app/script/about
|
||||
test/integration/app/script/breakpointer
|
||||
test/integration/app/script/console
|
||||
test/integration/app/script/destroy
|
||||
test/integration/app/script/generate
|
||||
test/integration/app/script/performance/benchmarker
|
||||
test/integration/app/script/performance/profiler
|
||||
test/integration/app/script/plugin
|
||||
test/integration/app/script/process/inspector
|
||||
test/integration/app/script/process/reaper
|
||||
test/integration/app/script/process/spawner
|
||||
test/integration/app/script/runner
|
||||
test/integration/app/script/server
|
||||
test/integration/app/test/fixtures/addresses.yml
|
||||
test/integration/app/test/fixtures/citations.yml
|
||||
test/integration/app/test/fixtures/citations_items.yml
|
||||
test/integration/app/test/fixtures/sellers.yml
|
||||
test/integration/app/test/fixtures/states.yml
|
||||
test/integration/app/test/fixtures/users.yml
|
||||
test/integration/app/test/functional/addresses_controller_test.rb
|
||||
test/integration/app/test/functional/sellers_controller_test.rb
|
||||
test/integration/app/test/functional/states_controller_test.rb
|
||||
test/integration/app/test/functional/users_controller_test.rb
|
||||
test/integration/app/test/test_helper.rb
|
||||
test/integration/app/test/unit/address_test.rb
|
||||
test/integration/app/test/unit/citation_test.rb
|
||||
test/integration/app/test/unit/citations_item_test.rb
|
||||
test/integration/app/test/unit/seller_test.rb
|
||||
test/integration/app/test/unit/state_test.rb
|
||||
test/integration/app/test/unit/user_test.rb
|
||||
test/models/aquatic/fish.rb
|
||||
test/models/aquatic/pupils_whale.rb
|
||||
test/models/aquatic/whale.rb
|
||||
test/models/beautiful_fight_relationship.rb
|
||||
test/models/canine.rb
|
||||
test/models/cat.rb
|
||||
test/models/dog.rb
|
||||
test/models/eaters_foodstuff.rb
|
||||
test/models/frog.rb
|
||||
test/models/kitten.rb
|
||||
test/models/parentship.rb
|
||||
test/models/person.rb
|
||||
test/models/petfood.rb
|
||||
test/models/tabby.rb
|
||||
test/models/wild_boar.rb
|
||||
test/modules/extension_module.rb
|
||||
test/modules/other_extension_module.rb
|
||||
test/schema.rb
|
||||
test/setup.rb
|
||||
test/test_all.rb
|
||||
test/test_helper.rb
|
||||
test/unit/polymorph_test.rb
|
||||
TODO
|
||||
174
vendor/plugins/has_many_polymorphs/README
vendored
Normal file
174
vendor/plugins/has_many_polymorphs/README
vendored
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
|
||||
Has_many_polymorphs
|
||||
|
||||
An ActiveRecord plugin for self-referential and double-sided polymorphic associations.
|
||||
|
||||
== License
|
||||
|
||||
Copyright 2007 Cloudburst, LLC. Licensed under the AFL 3. See the included LICENSE file.
|
||||
|
||||
The public certificate for this gem is at http://rubyforge.org/frs/download.php/25331/evan_weaver-original-public_cert.pem.
|
||||
|
||||
== Description
|
||||
|
||||
This plugin lets you define self-referential and double-sided polymorphic associations in your models. It is an extension of <tt>has_many :through</tt>.
|
||||
|
||||
“Polymorphic” means an association can freely point to any of several unrelated model classes, instead of being tied to one particular class.
|
||||
|
||||
== Features
|
||||
|
||||
* self-references
|
||||
* double-sided polymorphism
|
||||
* efficient database usage
|
||||
* STI support
|
||||
* namespace support
|
||||
* automatic individual and reverse associations
|
||||
|
||||
The plugin also includes a generator for a tagging system, a common use case (see below).
|
||||
|
||||
== Requirements
|
||||
|
||||
* Rails 1.2.3 or greater
|
||||
|
||||
= Usage
|
||||
|
||||
== Installation
|
||||
|
||||
To install the Rails plugin, run:
|
||||
script/plugin install svn://rubyforge.org/var/svn/fauna/has_many_polymorphs/trunk
|
||||
|
||||
There's also a gem version. To install it instead, run:
|
||||
sudo gem install has_many_polymorphs
|
||||
|
||||
If you are using the gem, make sure to add <tt>require 'has_many_polymorphs'</tt> to <tt>environment.rb</tt>, before Rails::Initializer block.
|
||||
|
||||
== Configuration
|
||||
|
||||
Setup the parent model as so:
|
||||
|
||||
class Kennel < ActiveRecord::Base
|
||||
has_many_polymorphs :guests, :from => [:dogs, :cats, :birds]
|
||||
end
|
||||
|
||||
The join model:
|
||||
|
||||
class GuestsKennel < ActiveRecord::Base
|
||||
belongs_to :kennel
|
||||
belongs_to :guest, :polymorphic => true
|
||||
end
|
||||
|
||||
One of the child models:
|
||||
|
||||
class Dog < ActiveRecord::Base
|
||||
# nothing
|
||||
end
|
||||
|
||||
See ActiveRecord::Associations::PolymorphicClassMethods for more configuration options.
|
||||
|
||||
== Helper methods example
|
||||
|
||||
>> k = Kennel.find(1)
|
||||
#<Kennel id: 1, name: "Happy Paws">
|
||||
>> k.guests.map(&:class)
|
||||
[Dog, Cat, Cat, Bird]
|
||||
|
||||
>> k.guests.push(Cat.create); k.cats.size
|
||||
3
|
||||
>> k.guests << Cat.create; k.cats.size
|
||||
4
|
||||
>> k.guests.size
|
||||
6
|
||||
|
||||
>> d = k.dogs.first
|
||||
#<Dog id: 3, name: "Rover">
|
||||
>> d.kennels
|
||||
[#<Kennel id: 1, name: "Happy Paws">]
|
||||
|
||||
>> k.guests.delete(d); k.dogs.size
|
||||
0
|
||||
>> k.guests.size
|
||||
5
|
||||
|
||||
Note that the parent method is always plural, even if there is only one parent (<tt>Dog#kennels</tt>, not <tt>Dog#kennel</tt>).
|
||||
|
||||
See ActiveRecord::Associations::PolymorphicAssociation for more helper method details.
|
||||
|
||||
= Extras
|
||||
|
||||
== Double-sided polymorphism
|
||||
|
||||
Double-sided relationships are defined on the join model:
|
||||
|
||||
class Devouring < ActiveRecord::Base
|
||||
belongs_to :guest, :polymorphic => true
|
||||
belongs_to :eaten, :polymorphic => true
|
||||
|
||||
acts_as_double_polymorphic_join(
|
||||
:guests =>[:dogs, :cats],
|
||||
:eatens => [:cats, :birds]
|
||||
)
|
||||
end
|
||||
|
||||
Now, dogs and cats can eat birds and cats. Birds can't eat anything (they aren't <tt>guests</tt>) and dogs can't be eaten by anything (since they aren't <tt>eatens</tt>). The keys stand for what the models are, not what they do.
|
||||
|
||||
In this case, each guest/eaten relationship is called a Devouring.
|
||||
|
||||
See ActiveRecord::Associations::PolymorphicClassMethods for more.
|
||||
|
||||
== Tagging generator
|
||||
|
||||
Has_many_polymorphs includes a tagging system generator. Run:
|
||||
script/generate tagging Dog Cat [...MoreModels...]
|
||||
|
||||
This adds a migration and new Tag and Tagging models in <tt>app/models</tt>. It configures Tag with an appropriate <tt>has_many_polymorphs</tt> call against the models you list at the command line. It also adds the file <tt>lib/tagging_extensions.rb</tt> and <tt>requires</tt> it in <tt>environment.rb</tt>.
|
||||
|
||||
Tests will also be generated.
|
||||
|
||||
Once you've run the generator, you can tag records as follows:
|
||||
|
||||
>> d = Dog.create(:name => "Rover")
|
||||
#<Dog id: 3, name: "Rover">
|
||||
>> d.tag_list
|
||||
""
|
||||
>> d.tag_with "fierce loud"
|
||||
#<Dog id: 3, name: "Rover">
|
||||
>> d.tag_list
|
||||
"fierce loud"
|
||||
>> c = Cat.create(:name => "Chloe")
|
||||
#<Cat id: 1, name: "Chloe">
|
||||
>> c.tag_with "fierce cute"
|
||||
#<Cat id: 1, name: "Chloe">
|
||||
>> c.tag_list
|
||||
"cute fierce"
|
||||
>> Tag.find_by_name("fierce").taggables
|
||||
[#<Cat id: 1, name: "Chloe">, #<Dog id: 3, name: "Rover">]
|
||||
|
||||
The generator accepts the optional flag <tt>--skip-migration</tt> to skip generating a migration (for example, if you are converting from <tt>acts_as_taggable</tt>). It also accepts the flag <tt>--self-referential</tt> if you want to be able to tag tags.
|
||||
|
||||
See ActiveRecord::Base::TaggingExtensions, Tag, and Tagging for more.
|
||||
|
||||
== Troubleshooting
|
||||
|
||||
Some debugging tools are available in <tt>lib/has_many_polymorphs/debugging_tools.rb</tt>.
|
||||
|
||||
If you are having trouble, think very carefully about how your model classes, key columns, and table names relate. You may have to explicitly specify options on your join model such as <tt>:class_name</tt>, <tt>:foreign_key</tt>, or <tt>:as</tt>. The included tests are a good place to look for examples.
|
||||
|
||||
Note that because of the way Rails reloads model classes, the plugin can sometimes bog down your development server. Set <tt>config.cache_classes = true</tt> in <tt>config/environments/development.rb</tt> to avoid this.
|
||||
|
||||
== Reporting problems
|
||||
|
||||
* http://rubyforge.org/forum/forum.php?forum_id=16450
|
||||
|
||||
Patches and contributions are very welcome. Please note that contributors are required to assign copyright for their additions to Cloudburst, LLC.
|
||||
|
||||
== Further resources
|
||||
|
||||
* http://blog.evanweaver.com/articles/2007/08/15/polymorphs-tutorial
|
||||
* http://blog.evanweaver.com/articles/2007/02/22/polymorphs-25-total-insanity-branch
|
||||
* http://blog.evanweaver.com/articles/2007/02/09/how-to-find-the-most-popular-tags
|
||||
* http://blog.evanweaver.com/articles/2007/01/13/growing-up-your-acts_as_taggable
|
||||
* http://blog.evanweaver.com/articles/2006/12/02/polymorphs-19
|
||||
* http://blog.evanweaver.com/articles/2006/11/05/directed-double-polymorphic-associations
|
||||
* http://blog.evanweaver.com/articles/2006/11/04/namespaced-model-support-in-has_many_polymorphs
|
||||
* http://blog.evanweaver.com/articles/2006/09/26/sti-support-in-has_many_polymorphs
|
||||
* http://blog.evanweaver.com/articles/2006/09/11/make-polymorphic-children-belong-to-only-one-parent
|
||||
21
vendor/plugins/has_many_polymorphs/Rakefile
vendored
Normal file
21
vendor/plugins/has_many_polymorphs/Rakefile
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
|
||||
require 'rubygems'
|
||||
gem 'echoe', '>=2.2'
|
||||
require 'echoe'
|
||||
require 'lib/has_many_polymorphs/rake_task_redefine_task'
|
||||
|
||||
Echoe.new("has_many_polymorphs") do |p|
|
||||
p.project = "fauna"
|
||||
p.summary = "An ActiveRecord plugin for self-referential and double-sided polymorphic associations."
|
||||
p.url = "http://blog.evanweaver.com/files/doc/fauna/has_many_polymorphs/"
|
||||
p.docs_host = "blog.evanweaver.com:~/www/bax/public/files/doc/"
|
||||
p.dependencies = ["activerecord"]
|
||||
p.rdoc_pattern = /polymorphs\/association|polymorphs\/class_methods|polymorphs\/reflection|polymorphs\/autoload|polymorphs\/configuration|README|CHANGELOG|TODO|LICENSE|templates\/migration\.rb|templates\/tag\.rb|templates\/tagging\.rb|templates\/tagging_extensions\.rb/
|
||||
p.require_signed = true
|
||||
end
|
||||
|
||||
desc 'Run the test suite.'
|
||||
Rake::Task.redefine_task("test") do
|
||||
puts "Warning! Tests must be run with the plugin installed in a functioning Rails\nenvironment."
|
||||
system "ruby -Ibin:lib:test test/unit/polymorph_test.rb #{ENV['METHOD'] ? "--name=#{ENV['METHOD']}" : ""}"
|
||||
end
|
||||
5
vendor/plugins/has_many_polymorphs/TODO
vendored
Normal file
5
vendor/plugins/has_many_polymorphs/TODO
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
|
||||
* Does :namespace key definitely work with doubles?
|
||||
* Migration examples in docs
|
||||
* Controller for tagging generator
|
||||
* Tag cloud method
|
||||
69
vendor/plugins/has_many_polymorphs/examples/hmph.rb
vendored
Normal file
69
vendor/plugins/has_many_polymorphs/examples/hmph.rb
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
require 'camping'
|
||||
require 'has_many_polymorphs'
|
||||
|
||||
Camping.goes :Hmph
|
||||
|
||||
module Hmph::Models
|
||||
class GuestsKennel < Base
|
||||
belongs_to :kennel
|
||||
belongs_to :guest, :polymorphic => true
|
||||
end
|
||||
|
||||
class Dog < Base
|
||||
end
|
||||
|
||||
class Cat < Base
|
||||
end
|
||||
|
||||
class Bird < Base
|
||||
end
|
||||
|
||||
class Kennel < Base
|
||||
has_many_polymorphs :guests,
|
||||
:from => [:dogs, :cats, :birds],
|
||||
:through => :guests_kennels,
|
||||
:namespace => :"hmph/models/"
|
||||
end
|
||||
|
||||
class InitialSchema < V 1.0
|
||||
def self.up
|
||||
create_table :hmph_kennels do |t|
|
||||
t.column :created_at, :datetime
|
||||
t.column :modified_at, :datetime
|
||||
t.column :name, :string, :default => 'Anonymous Kennel'
|
||||
end
|
||||
|
||||
create_table :hmph_guests_kennels do |t|
|
||||
t.column :guest_id, :integer
|
||||
t.column :guest_type, :string
|
||||
t.column :kennel_id, :integer
|
||||
end
|
||||
|
||||
create_table :hmph_dogs do |t|
|
||||
t.column :name, :string, :default => 'Fido'
|
||||
end
|
||||
|
||||
create_table :hmph_cats do |t|
|
||||
t.column :name, :string, :default => 'Morris'
|
||||
end
|
||||
|
||||
create_table :hmph_birds do |t|
|
||||
t.column :name, :string, :default => 'Polly'
|
||||
end
|
||||
end
|
||||
|
||||
def self.down
|
||||
drop_table :hmph_kennels
|
||||
drop_table :hmph_guests_kennels
|
||||
drop_table :hmph_dogs
|
||||
drop_table :hmph_cats
|
||||
drop_table :hmph_birds
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module Hmph::Controllers
|
||||
end
|
||||
|
||||
module Hmph::Views
|
||||
end
|
||||
94
vendor/plugins/has_many_polymorphs/generators/commenting/commenting_generator.rb
vendored
Normal file
94
vendor/plugins/has_many_polymorphs/generators/commenting/commenting_generator.rb
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
|
||||
class CommentingGenerator < Rails::Generator::NamedBase
|
||||
default_options :skip_migration => false
|
||||
default_options :self_referential => false
|
||||
attr_reader :parent_association_name
|
||||
attr_reader :commentable_models
|
||||
|
||||
def initialize(runtime_args, runtime_options = {})
|
||||
@parent_association_name = (runtime_args.include?("--self-referential") ? "commenter" : "comment")
|
||||
@commentable_models = runtime_args.reject{|opt| opt =~ /^--/}.map do |commentable|
|
||||
":" + commentable.underscore.pluralize
|
||||
end
|
||||
@commentable_models += [":comments"] if runtime_args.include?("--self-referential")
|
||||
@commentable_models.uniq!
|
||||
|
||||
verify @commentable_models
|
||||
hacks
|
||||
runtime_args.unshift("placeholder")
|
||||
super
|
||||
end
|
||||
|
||||
def verify models
|
||||
puts "** Warning: only one commentable model specified; tests may not run properly." if models.size < 2
|
||||
models.each do |model|
|
||||
model = model[1..-1].classify
|
||||
next if model == "Comment" # don't load ourselves when --self-referential is used
|
||||
self.class.const_get(model) rescue puts "** Error: model #{model[1..-1].classify} could not be loaded." or exit
|
||||
end
|
||||
end
|
||||
|
||||
def hacks
|
||||
# add the extension require in environment.rb
|
||||
phrase = "require 'commenting_extensions'"
|
||||
filename = "#{RAILS_ROOT}/config/environment.rb"
|
||||
unless (open(filename) do |file|
|
||||
file.grep(/#{Regexp.escape phrase}/).any?
|
||||
end)
|
||||
open(filename, 'a+') do |file|
|
||||
file.puts "\n" + phrase + "\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def manifest
|
||||
record do |m|
|
||||
m.class_collisions class_path, class_name, "#{class_name}Test"
|
||||
|
||||
m.directory File.join('app/models', class_path)
|
||||
m.directory File.join('test/unit', class_path)
|
||||
m.directory File.join('test/fixtures', class_path)
|
||||
m.directory File.join('test/fixtures', class_path)
|
||||
m.directory File.join('lib')
|
||||
|
||||
m.template 'comment.rb', File.join('app/models', class_path, "comment.rb")
|
||||
m.template 'comment_test.rb', File.join('test/unit', class_path, "comment_test.rb")
|
||||
m.template 'comments.yml', File.join('test/fixtures', class_path, "comments.yml")
|
||||
|
||||
m.template 'commenting.rb', File.join('app/models', class_path, "commenting.rb")
|
||||
m.template 'commenting_test.rb', File.join('test/unit', class_path, "commenting_test.rb")
|
||||
m.template 'commentings.yml', File.join('test/fixtures', class_path, "commentings.yml")
|
||||
|
||||
m.template 'commenting_extensions.rb', File.join('lib', 'commenting_extensions.rb')
|
||||
|
||||
unless options[:skip_migration]
|
||||
m.migration_template 'migration.rb', 'db/migrate',
|
||||
:migration_file_name => "create_comments_and_commentings"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def banner
|
||||
"Usage: #{$0} generate commenting [CommentableModelA CommentableModelB ...]"
|
||||
end
|
||||
|
||||
def add_options!(opt)
|
||||
opt.separator ''
|
||||
opt.separator 'Options:'
|
||||
opt.on("--skip-migration",
|
||||
"Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
|
||||
opt.on("--self-referential",
|
||||
"Allow comments to comment themselves.") { |v| options[:self_referential] = v }
|
||||
end
|
||||
|
||||
# Useful for generating tests/fixtures
|
||||
def model_one
|
||||
commentable_models[0][1..-1].classify
|
||||
end
|
||||
|
||||
def model_two
|
||||
commentable_models[1][1..-1].classify rescue model_one
|
||||
end
|
||||
end
|
||||
33
vendor/plugins/has_many_polymorphs/generators/commenting/templates/comment.rb
vendored
Normal file
33
vendor/plugins/has_many_polymorphs/generators/commenting/templates/comment.rb
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
|
||||
# The Comment model. This model is automatically generated and added to your app if you run the commenting generator.
|
||||
|
||||
class Comment < ActiveRecord::Base
|
||||
|
||||
# If database speed becomes an issue, you could remove these validations and rescue the ActiveRecord database constraint errors instead.
|
||||
validates_presence_of :name, :email, :body
|
||||
validates_format_of :email, :with => /^([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
|
||||
|
||||
after_validation :prepend_url
|
||||
|
||||
# Set up the polymorphic relationship.
|
||||
has_many_polymorphs :commentables,
|
||||
:from => [<%= commentable_models.join(", ") %>],
|
||||
:through => :commentings,
|
||||
:dependent => :destroy,
|
||||
<% if options[:self_referential] -%> :as => :<%= parent_association_name -%>,
|
||||
<% end -%>
|
||||
:parent_extend => proc {
|
||||
}
|
||||
|
||||
# Tag::Error class. Raised by ActiveRecord::Base::TaggingExtensions if something goes wrong.
|
||||
class Error < StandardError
|
||||
end
|
||||
|
||||
protected
|
||||
def prepend_url
|
||||
return if self[:url].blank?
|
||||
if self[:url] !~ /^http(s):\/\//i
|
||||
self.url = 'http://' + self[:url]
|
||||
end
|
||||
end
|
||||
end
|
||||
12
vendor/plugins/has_many_polymorphs/generators/commenting/templates/comment_test.rb
vendored
Normal file
12
vendor/plugins/has_many_polymorphs/generators/commenting/templates/comment_test.rb
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class CommentTest < Test::Unit::TestCase
|
||||
fixtures :comments, :commentings, <%= commentable_models[0..1].join(", ") -%>
|
||||
|
||||
def test_to_s
|
||||
assert_equal "no1@nowhere.com", <%= model_two -%>.find(2).comments.first.email
|
||||
assert_equal "http://letrails.cn", <%= model_two -%>.find(2).comments.last.url
|
||||
assert_equal "http://fr.ivolo.us", <%= model_two -%>.find(2).comments.first.url
|
||||
end
|
||||
|
||||
end
|
||||
13
vendor/plugins/has_many_polymorphs/generators/commenting/templates/commenting.rb
vendored
Normal file
13
vendor/plugins/has_many_polymorphs/generators/commenting/templates/commenting.rb
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
# The Commenting join model. This model is automatically generated and added to your app if you run the commenting generator.
|
||||
|
||||
class Commenting < ActiveRecord::Base
|
||||
|
||||
belongs_to :<%= parent_association_name -%><%= ", :foreign_key => \"#{parent_association_name}_id\", :class_name => \"Comment\"" if options[:self_referential] %>
|
||||
belongs_to :commentable, :polymorphic => true
|
||||
|
||||
# This callback makes sure that an orphaned <tt>Comment</tt> is deleted if it no longer tags anything.
|
||||
def before_destroy
|
||||
<%= parent_association_name -%>.destroy_without_callbacks if <%= parent_association_name -%> and <%= parent_association_name -%>.commentings.count == 1
|
||||
end
|
||||
end
|
||||
30
vendor/plugins/has_many_polymorphs/generators/commenting/templates/commenting_extensions.rb
vendored
Normal file
30
vendor/plugins/has_many_polymorphs/generators/commenting/templates/commenting_extensions.rb
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
class ActiveRecord::Base
|
||||
module CommentingExtensions
|
||||
|
||||
def comment_count
|
||||
commentable?
|
||||
self.comments.size
|
||||
end
|
||||
|
||||
def comment_with(attributes)
|
||||
commentable?(true)
|
||||
begin
|
||||
comment = Comment.create(attributes)
|
||||
raise Comment::Error, "Comment could not be saved with" if comment.new_record?
|
||||
comment.commentables << self
|
||||
rescue
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
def commentable?(should_raise = false) #:nodoc:
|
||||
unless flag = respond_to?(:<%= parent_association_name -%>s)
|
||||
raise "#{self.class} is not a commentable model" if should_raise
|
||||
end
|
||||
flag
|
||||
end
|
||||
end
|
||||
|
||||
include CommentingExtensions
|
||||
end
|
||||
|
||||
30
vendor/plugins/has_many_polymorphs/generators/commenting/templates/commenting_test.rb
vendored
Normal file
30
vendor/plugins/has_many_polymorphs/generators/commenting/templates/commenting_test.rb
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class CommentingTest < Test::Unit::TestCase
|
||||
fixtures :commentings, :comments, <%= commentable_models[0..1].join(", ") -%>
|
||||
|
||||
def setup
|
||||
@obj1 = <%= model_two %>.find(1)
|
||||
@obj2 = <%= model_two %>.find(2)
|
||||
<% if commentable_models.size > 1 -%>
|
||||
@obj3 = <%= model_one -%>.find(1)
|
||||
<% end -%>
|
||||
@comment1 = Comment.find(1)
|
||||
@comment2 = Comment.find(2)
|
||||
@commenting1 = Commenting.find(1)
|
||||
end
|
||||
|
||||
def test_commentable
|
||||
assert_raises(RuntimeError) do
|
||||
@commenting1.send(:commentable?, true)
|
||||
end
|
||||
assert !@commenting1.send(:commentable?)
|
||||
<% if commentable_models.size > 1 -%>
|
||||
assert @obj3.send(:commentable?)
|
||||
<% end -%>
|
||||
<% if options[:self_referential] -%>
|
||||
assert @comment1.send(:commentable?)
|
||||
<% end -%>
|
||||
end
|
||||
|
||||
end
|
||||
23
vendor/plugins/has_many_polymorphs/generators/commenting/templates/commentings.yml
vendored
Normal file
23
vendor/plugins/has_many_polymorphs/generators/commenting/templates/commentings.yml
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
<% if commentable_models.size > 1 -%>
|
||||
commentings_003:
|
||||
<%= parent_association_name -%>_id: "2"
|
||||
id: "3"
|
||||
commentable_type: <%= model_one %>
|
||||
commentable_id: "1"
|
||||
<% end -%>
|
||||
commentings_004:
|
||||
<%= parent_association_name -%>_id: "2"
|
||||
id: "4"
|
||||
commentable_type: <%= model_two %>
|
||||
commentable_id: "2"
|
||||
commentings_001:
|
||||
<%= parent_association_name -%>_id: "1"
|
||||
id: "1"
|
||||
commentable_type: <%= model_two %>
|
||||
commentable_id: "1"
|
||||
commentings_002:
|
||||
<%= parent_association_name -%>_id: "1"
|
||||
id: "2"
|
||||
commentable_type: <%= model_two %>
|
||||
commentable_id: "2"
|
||||
13
vendor/plugins/has_many_polymorphs/generators/commenting/templates/comments.yml
vendored
Normal file
13
vendor/plugins/has_many_polymorphs/generators/commenting/templates/comments.yml
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
---
|
||||
comments_001:
|
||||
id: "1"
|
||||
name: frivolous
|
||||
email: no1@nowhere.com
|
||||
url: http://fr.ivolo.us
|
||||
body: this plugin rocks!
|
||||
tags_002:
|
||||
id: "2"
|
||||
name: yuanyiz
|
||||
email: no1@nowhere.com
|
||||
url: http://letrails.cn
|
||||
body: this plugin has saved my life
|
||||
28
vendor/plugins/has_many_polymorphs/generators/commenting/templates/migration.rb
vendored
Normal file
28
vendor/plugins/has_many_polymorphs/generators/commenting/templates/migration.rb
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
# A migration to add tables for Comment and Commenting. This file is automatically generated and added to your app if you run the commenting generator.
|
||||
|
||||
class CreateCommentsAndCommentings < ActiveRecord::Migration
|
||||
|
||||
# Add the new tables.
|
||||
def self.up
|
||||
create_table :comments do |t|
|
||||
t.column :name, :string, :null => false
|
||||
t.column :url, :string
|
||||
t.column :email, :string
|
||||
t.column :body, :text
|
||||
end
|
||||
|
||||
create_table :commentings do |t|
|
||||
t.column :<%= parent_association_name -%>_id, :integer, :null => false
|
||||
t.column :commentable_id, :integer, :null => false
|
||||
t.column :commentable_type, :string, :null => false
|
||||
end
|
||||
end
|
||||
|
||||
# Remove the tables.
|
||||
def self.down
|
||||
drop_table :comments
|
||||
drop_table :commentings
|
||||
end
|
||||
|
||||
end
|
||||
95
vendor/plugins/has_many_polymorphs/generators/tagging/tagging_generator.rb
vendored
Normal file
95
vendor/plugins/has_many_polymorphs/generators/tagging/tagging_generator.rb
vendored
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
require 'ruby-debug' and Debugger.start if ENV['USER'] == 'eweaver'
|
||||
|
||||
class TaggingGenerator < Rails::Generator::NamedBase
|
||||
default_options :skip_migration => false
|
||||
default_options :self_referential => false
|
||||
attr_reader :parent_association_name
|
||||
attr_reader :taggable_models
|
||||
|
||||
def initialize(runtime_args, runtime_options = {})
|
||||
@parent_association_name = (runtime_args.include?("--self-referential") ? "tagger" : "tag")
|
||||
@taggable_models = runtime_args.reject{|opt| opt =~ /^--/}.map do |taggable|
|
||||
":" + taggable.underscore.pluralize
|
||||
end
|
||||
@taggable_models += [":tags"] if runtime_args.include?("--self-referential")
|
||||
@taggable_models.uniq!
|
||||
|
||||
verify @taggable_models
|
||||
hacks
|
||||
runtime_args.unshift("placeholder")
|
||||
super
|
||||
end
|
||||
|
||||
def verify models
|
||||
puts "** Warning: only one taggable model specified; tests may not run properly." if models.size < 2
|
||||
models.each do |model|
|
||||
model = model[1..-1].classify
|
||||
next if model == "Tag" # don't load ourselves when --self-referential is used
|
||||
self.class.const_get(model) rescue puts "** Error: model #{model[1..-1].classify} could not be loaded." or exit
|
||||
end
|
||||
end
|
||||
|
||||
def hacks
|
||||
# add the extension require in environment.rb
|
||||
phrase = "require 'tagging_extensions'"
|
||||
filename = "#{RAILS_ROOT}/config/environment.rb"
|
||||
unless (open(filename) do |file|
|
||||
file.grep(/#{Regexp.escape phrase}/).any?
|
||||
end)
|
||||
open(filename, 'a+') do |file|
|
||||
file.puts "\n" + phrase + "\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def manifest
|
||||
record do |m|
|
||||
m.class_collisions class_path, class_name, "#{class_name}Test"
|
||||
|
||||
m.directory File.join('app/models', class_path)
|
||||
m.directory File.join('test/unit', class_path)
|
||||
m.directory File.join('test/fixtures', class_path)
|
||||
m.directory File.join('test/fixtures', class_path)
|
||||
m.directory File.join('lib')
|
||||
|
||||
m.template 'tag.rb', File.join('app/models', class_path, "tag.rb")
|
||||
m.template 'tag_test.rb', File.join('test/unit', class_path, "tag_test.rb")
|
||||
m.template 'tags.yml', File.join('test/fixtures', class_path, "tags.yml")
|
||||
|
||||
m.template 'tagging.rb', File.join('app/models', class_path, "tagging.rb")
|
||||
m.template 'tagging_test.rb', File.join('test/unit', class_path, "tagging_test.rb")
|
||||
m.template 'taggings.yml', File.join('test/fixtures', class_path, "taggings.yml")
|
||||
|
||||
m.template 'tagging_extensions.rb', File.join('lib', 'tagging_extensions.rb')
|
||||
|
||||
unless options[:skip_migration]
|
||||
m.migration_template 'migration.rb', 'db/migrate',
|
||||
:migration_file_name => "create_tags_and_taggings"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
def banner
|
||||
"Usage: #{$0} generate tagging [TaggableModelA TaggableModelB ...]"
|
||||
end
|
||||
|
||||
def add_options!(opt)
|
||||
opt.separator ''
|
||||
opt.separator 'Options:'
|
||||
opt.on("--skip-migration",
|
||||
"Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
|
||||
opt.on("--self-referential",
|
||||
"Allow tags to tag themselves.") { |v| options[:self_referential] = v }
|
||||
end
|
||||
|
||||
# Useful for generating tests/fixtures
|
||||
def model_one
|
||||
taggable_models[0][1..-1].classify
|
||||
end
|
||||
|
||||
def model_two
|
||||
taggable_models[1][1..-1].classify rescue model_one
|
||||
end
|
||||
end
|
||||
28
vendor/plugins/has_many_polymorphs/generators/tagging/templates/migration.rb
vendored
Normal file
28
vendor/plugins/has_many_polymorphs/generators/tagging/templates/migration.rb
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
|
||||
# A migration to add tables for Tag and Tagging. This file is automatically generated and added to your app if you run the tagging generator included with has_many_polymorphs.
|
||||
|
||||
class CreateTagsAndTaggings < ActiveRecord::Migration
|
||||
|
||||
# Add the new tables.
|
||||
def self.up
|
||||
create_table :tags do |t|
|
||||
t.column :name, :string, :null => false
|
||||
end
|
||||
add_index :tags, :name, :unique => true
|
||||
|
||||
create_table :taggings do |t|
|
||||
t.column :<%= parent_association_name -%>_id, :integer, :null => false
|
||||
t.column :taggable_id, :integer, :null => false
|
||||
t.column :taggable_type, :string, :null => false
|
||||
# t.column :position, :integer # Uncomment this if you need to use <tt>acts_as_list</tt>.
|
||||
end
|
||||
add_index :taggings, [:<%= parent_association_name -%>_id, :taggable_id, :taggable_type], :unique => true
|
||||
end
|
||||
|
||||
# Remove the tables.
|
||||
def self.down
|
||||
drop_table :tags
|
||||
drop_table :taggings
|
||||
end
|
||||
|
||||
end
|
||||
39
vendor/plugins/has_many_polymorphs/generators/tagging/templates/tag.rb
vendored
Normal file
39
vendor/plugins/has_many_polymorphs/generators/tagging/templates/tag.rb
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
|
||||
# The Tag model. This model is automatically generated and added to your app if you run the tagging generator included with has_many_polymorphs.
|
||||
|
||||
class Tag < ActiveRecord::Base
|
||||
|
||||
DELIMITER = " " # Controls how to split and join tagnames from strings. You may need to change the <tt>validates_format_of parameters</tt> if you change this.
|
||||
|
||||
# If database speed becomes an issue, you could remove these validations and rescue the ActiveRecord database constraint errors instead.
|
||||
validates_presence_of :name
|
||||
validates_uniqueness_of :name, :case_sensitive => false
|
||||
|
||||
# Change this validation if you need more complex tag names.
|
||||
validates_format_of :name, :with => /^[a-zA-Z0-9\_\-]+$/, :message => "can not contain special characters"
|
||||
|
||||
# Set up the polymorphic relationship.
|
||||
has_many_polymorphs :taggables,
|
||||
:from => [<%= taggable_models.join(", ") %>],
|
||||
:through => :taggings,
|
||||
:dependent => :destroy,
|
||||
<% if options[:self_referential] -%> :as => :<%= parent_association_name -%>,
|
||||
<% end -%>
|
||||
:skip_duplicates => false,
|
||||
:parent_extend => proc {
|
||||
# Defined on the taggable models, not on Tag itself. Return the tagnames associated with this record as a string.
|
||||
def to_s
|
||||
self.map(&:name).sort.join(Tag::DELIMITER)
|
||||
end
|
||||
}
|
||||
|
||||
# Callback to strip extra spaces from the tagname before saving it. If you allow tags to be renamed later, you might want to use the <tt>before_save</tt> callback instead.
|
||||
def before_create
|
||||
self.name = name.downcase.strip.squeeze(" ")
|
||||
end
|
||||
|
||||
# Tag::Error class. Raised by ActiveRecord::Base::TaggingExtensions if something goes wrong.
|
||||
class Error < StandardError
|
||||
end
|
||||
|
||||
end
|
||||
10
vendor/plugins/has_many_polymorphs/generators/tagging/templates/tag_test.rb
vendored
Normal file
10
vendor/plugins/has_many_polymorphs/generators/tagging/templates/tag_test.rb
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
require File.dirname(__FILE__) + '/../test_helper'
|
||||
|
||||
class TagTest < Test::Unit::TestCase
|
||||
fixtures :tags, :taggings, <%= taggable_models[0..1].join(", ") -%>
|
||||
|
||||
def test_to_s
|
||||
assert_equal "delicious sexy", <%= model_two -%>.find(2).tags.to_s
|
||||
end
|
||||
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue