radiant-page_factory-extension 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/EXAMPLES.md +136 -0
- data/README.md +59 -0
- data/Rakefile +136 -0
- data/VERSION +1 -0
- data/app/controllers/admin/page_factories_controller.rb +42 -0
- data/app/helpers/admin/part_description_helper.rb +9 -0
- data/app/views/admin/page_factories/_page_factory.html.haml +4 -0
- data/app/views/admin/page_factories/index.haml +0 -0
- data/app/views/admin/page_parts/_part_description.html.haml +2 -0
- data/app/views/admin/pages/_add_child_column.html.haml +3 -0
- data/app/views/admin/pages/_edit_header.html.haml +1 -0
- data/app/views/admin/pages/_page_factories.html.haml +4 -0
- data/app/views/admin/pages/_page_factory_field.html.haml +1 -0
- data/config/locales/en.yml +3 -0
- data/config/routes.rb +5 -0
- data/db/migrate/20100321222140_add_page_factory.rb +9 -0
- data/lib/page_factory.rb +12 -0
- data/lib/page_factory/base.rb +81 -0
- data/lib/page_factory/manager.rb +120 -0
- data/lib/page_factory/page_extensions.rb +25 -0
- data/lib/page_factory/page_part_extensions.rb +9 -0
- data/lib/page_factory/pages_controller_extensions.rb +32 -0
- data/lib/tasks/page_factory_extension_tasks.rake +95 -0
- data/page_factory_extension.rb +25 -0
- data/public/javascripts/admin/dropdown.js +153 -0
- data/public/javascripts/admin/pagefactory.js +30 -0
- data/public/stylesheets/admin/page_factory.css +14 -0
- data/public/stylesheets/sass/admin/dropdown.sass +32 -0
- data/public/stylesheets/sass/modules/_gradient.sass +47 -0
- data/public/stylesheets/sass/modules/_rounded.sass +41 -0
- data/public/stylesheets/sass/modules/_shadow.sass +9 -0
- data/spec/controllers/admin/page_factories_controller_spec.rb +47 -0
- data/spec/controllers/admin/pages_controller_spec.rb +63 -0
- data/spec/helpers/admin/part_description_helper_spec.rb +42 -0
- data/spec/lib/manager_spec.rb +218 -0
- data/spec/lib/page_extensions_spec.rb +15 -0
- data/spec/models/page_factory_spec.rb +95 -0
- data/spec/models/page_spec.rb +27 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +36 -0
- metadata +128 -0
@@ -0,0 +1,81 @@
|
|
1
|
+
class PageFactory::Base
|
2
|
+
include Annotatable
|
3
|
+
annotate :template_name, :layout, :page_class, :description
|
4
|
+
template_name 'Page'
|
5
|
+
description 'A basic Radiant page.'
|
6
|
+
|
7
|
+
class << self
|
8
|
+
attr_accessor :parts
|
9
|
+
|
10
|
+
def inherited(subclass)
|
11
|
+
subclass.parts = @parts.dup
|
12
|
+
subclass.layout = layout
|
13
|
+
subclass.page_class = page_class
|
14
|
+
subclass.template_name = subclass.name.to_name('Factory')
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Add a part to this PageFactory
|
19
|
+
#
|
20
|
+
# @param [String] name The name of the page part
|
21
|
+
# @param [Hash] attrs A hash of attributes used to construct this part.
|
22
|
+
# @option attrs [String] :description Some additional text that will be
|
23
|
+
# shown in the part's tab on the page editing screen. This is used to
|
24
|
+
# display a part description or helper text to editors.
|
25
|
+
#
|
26
|
+
# @example Add a part with default content and some help text
|
27
|
+
# part 'Sidebar', :content => "Lorem ipsum dolor",
|
28
|
+
# :description => "This appears in the right-hand sidebar."
|
29
|
+
def part(name, attrs={})
|
30
|
+
remove name
|
31
|
+
@parts << PagePart.new(attrs.merge(:name => name))
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Remove a part from this PageFactory
|
36
|
+
#
|
37
|
+
# @param [<String>] names Any number of part names to remove.
|
38
|
+
#
|
39
|
+
# @example
|
40
|
+
# remove 'body'
|
41
|
+
# remove 'body', 'extended'
|
42
|
+
def remove(*names)
|
43
|
+
names = names.map(&:downcase)
|
44
|
+
@parts.delete_if { |p| names.include? p.name.downcase }
|
45
|
+
end
|
46
|
+
|
47
|
+
def descendants
|
48
|
+
load_descendants
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def default_page_parts(config = Radiant::Config)
|
54
|
+
default_parts = config['defaults.page.parts'].to_s.strip.split(/\s*,\s*/)
|
55
|
+
default_parts.map do |name|
|
56
|
+
PagePart.new(:name => name, :filter_id => config['defaults.page.filter'])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def load_descendants
|
61
|
+
unless @_descendants_loaded
|
62
|
+
factory_paths = Radiant::Extension.descendants.inject [Rails.root.to_s + '/lib'] do |paths, ext|
|
63
|
+
paths << ext.root + '/app/models'
|
64
|
+
paths << ext.root + '/lib'
|
65
|
+
end
|
66
|
+
factory_paths.each do |path|
|
67
|
+
Dir["#{path}/*_page_factory.rb"].each do |page_factory|
|
68
|
+
if page_factory =~ %r{/([^/]+)\.rb}
|
69
|
+
require_dependency page_factory
|
70
|
+
ActiveSupport::Dependencies.explicitly_unloadable_constants << $1.camelize
|
71
|
+
end
|
72
|
+
end
|
73
|
+
@_descendants_loaded = true
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
@parts = default_page_parts
|
81
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
module PageFactory
|
2
|
+
##
|
3
|
+
# PageFactory::Manager is used to update your existing content with changes
|
4
|
+
# subsequently made to your PageFactories. All of these methods take a single
|
5
|
+
# optional argument, which should be the name of a PageFactory class.
|
6
|
+
#
|
7
|
+
# If no argument is given, the method is run for all PageFactories.
|
8
|
+
# Plain old pages not created with a specific factory are never affected in
|
9
|
+
# this case. If the name of a PageFactory is given, the method is only run
|
10
|
+
# on pages that were initially created by the specified PageFactory.
|
11
|
+
#
|
12
|
+
# Note that it is possible to pass 'page' as an argument, if you really
|
13
|
+
# need to update pages that were created without a specific factory.
|
14
|
+
class Manager
|
15
|
+
class << self
|
16
|
+
|
17
|
+
##
|
18
|
+
# Remove parts not specified in a PageFactory from all pages initially
|
19
|
+
# created by that PageFactory. This is useful if you decide to remove
|
20
|
+
# a part from a PageFactory and you want your existing content to
|
21
|
+
# reflect that change.
|
22
|
+
#
|
23
|
+
# @param [nil, String, Symbol, #to_s] page_factory The PageFactory to
|
24
|
+
# restrict this operation to, or nil to run it on all PageFactories.
|
25
|
+
def prune_parts!(page_factory=nil)
|
26
|
+
select_factories(page_factory).each do |factory|
|
27
|
+
parts = PagePart.scoped(:include => :page).
|
28
|
+
scoped(:conditions => {'pages.page_factory' => name_for(factory)}).
|
29
|
+
scoped(:conditions => ['page_parts.name NOT IN (?)', factory.parts.map(&:name)])
|
30
|
+
PagePart.destroy parts
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
##
|
35
|
+
# Add any parts defined in a PageFactory to all pages initially created
|
36
|
+
# by that factory, if those pages are missing any parts. This can be
|
37
|
+
# used when you've added a part to a factory and you want your existing
|
38
|
+
# content to reflect that change.
|
39
|
+
#
|
40
|
+
# @param [nil, String, Symbol, #to_s] page_factory The PageFactory to
|
41
|
+
# restrict this operation to, or nil to run it on all PageFactories.
|
42
|
+
def update_parts(page_factory=nil)
|
43
|
+
select_factories(page_factory).each do |factory|
|
44
|
+
Page.find(:all, :include => :parts, :conditions => {:page_factory => name_for(factory)}).each do |page|
|
45
|
+
existing = lambda { |f| page.parts.detect { |p| f.name.downcase == p.name.downcase } }
|
46
|
+
page.parts.create factory.parts.reject(&existing).map(&:attributes)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
##
|
52
|
+
# Replace any parts on a page that share a _name_ but not a _class_ with
|
53
|
+
# the parts defined in its PageFactory. Mismatched parts will be
|
54
|
+
# replaced with wholly new parts of the proper class -- this method
|
55
|
+
# _will_ discard content. Unless you're using an extension that
|
56
|
+
# subclasses PagePart (this is rare) you won't need this method.
|
57
|
+
#
|
58
|
+
# @param [nil, String, Symbol, #to_s] page_factory The PageFactory to
|
59
|
+
# restrict this operation to, or nil to run it on all PageFactories.
|
60
|
+
def sync_parts!(page_factory=nil)
|
61
|
+
select_factories(page_factory).each do |factory|
|
62
|
+
Page.find(:all, :include => :parts, :conditions => {:page_factory => name_for(factory)}).each do |page|
|
63
|
+
unsynced = lambda { |p| factory.parts.detect { |f| f.name.downcase == p.name.downcase and f.class != p.class } }
|
64
|
+
unsynced_parts = page.parts.select(&unsynced)
|
65
|
+
page.parts.destroy unsynced_parts
|
66
|
+
needs_update = lambda { |f| unsynced_parts.map(&:name).include? f.name }
|
67
|
+
page.parts.create factory.parts.select(&needs_update).map &:attributes
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Update the layout of all pages initially created by a PageFactory to
|
74
|
+
# match the layout currently specified on that PageFactory. Used when
|
75
|
+
# you decide to use a new layout in a PageFactory and you want your
|
76
|
+
# existing content to reflect that change.
|
77
|
+
#
|
78
|
+
# @param [nil, String, Symbol, #to_s] page_factory The PageFactory to
|
79
|
+
# restrict this operation to, or nil to run it on all PageFactories.
|
80
|
+
def sync_layouts!(page_factory=nil)
|
81
|
+
select_factories(page_factory).each do |factory|
|
82
|
+
Page.update_all({:layout_id => Layout.find_by_name(factory.layout, :select => :id).try(:id)}, {:page_factory => name_for(factory)})
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
##
|
87
|
+
# Update the Page class of all pages initially created by a PageFactory
|
88
|
+
# to match the class currently specified on that PageFactory. Useful
|
89
|
+
# when you assign a new page class to a PageFactory and you want your
|
90
|
+
# existing content to reflect that change.
|
91
|
+
#
|
92
|
+
# @param [nil, String, Symbol, #to_s] page_factory The PageFactory to
|
93
|
+
# restrict this operation to, or nil to run it on all PageFactories.
|
94
|
+
def sync_classes!(page_factory=nil)
|
95
|
+
select_factories(page_factory).each do |factory|
|
96
|
+
Page.update_all({:class_name => factory.page_class}, {:page_factory => name_for(factory)})
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def select_factories(page_factory)
|
103
|
+
[PageFactory::Base, *PageFactory::Base.descendants].select do |klass|
|
104
|
+
case page_factory
|
105
|
+
when '', nil
|
106
|
+
klass.name != 'PageFactory::Base'
|
107
|
+
when 'PageFactory', :PageFactory
|
108
|
+
klass.name == 'PageFactory::Base'
|
109
|
+
else
|
110
|
+
klass.name == page_factory.to_s.camelcase
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def name_for(factory)
|
116
|
+
factory == PageFactory::Base ? nil : factory.name
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module PageFactory
|
2
|
+
module PageExtensions
|
3
|
+
def self.included(base)
|
4
|
+
base.instance_eval do
|
5
|
+
def default_page_parts(config=Radiant::Config)
|
6
|
+
PageFactory.current_factory.parts
|
7
|
+
end
|
8
|
+
private_class_method :default_page_parts
|
9
|
+
end
|
10
|
+
base.class_eval do
|
11
|
+
##
|
12
|
+
# The PageFactory that was used to create this page. Note that Plain
|
13
|
+
# Old Pages do not have an assigned factory.
|
14
|
+
#
|
15
|
+
# @return [PageFactory, nil] This Page's initial PageFactory
|
16
|
+
def page_factory
|
17
|
+
(factory = read_attribute(:page_factory)).blank? ? nil : factory.constantize
|
18
|
+
rescue NameError => e # @page.page_factory is not a constant. class was removed?
|
19
|
+
logger.warn "Couldn't find page factory: #{e.message}"
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module PageFactory
|
2
|
+
module PagesControllerExtensions
|
3
|
+
def self.included(base)
|
4
|
+
base.class_eval do
|
5
|
+
around_filter :set_page_factory, :only => :new
|
6
|
+
before_filter { |c| c.include_stylesheet 'admin/dropdown' }
|
7
|
+
before_filter { |c| c.include_javascript 'admin/dropdown' }
|
8
|
+
before_filter { |c| c.include_javascript 'admin/pagefactory' }
|
9
|
+
responses do |r|
|
10
|
+
r.singular.default { set_page_defaults if 'new' == action_name }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def set_page_factory
|
16
|
+
begin
|
17
|
+
PageFactory.current_factory = params[:factory]
|
18
|
+
rescue NameError => e # bad factory name passed
|
19
|
+
logger.error "Tried to create page with invalid factory: #{e.message}"
|
20
|
+
ensure
|
21
|
+
yield
|
22
|
+
PageFactory.current_factory = nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def set_page_defaults
|
27
|
+
model.class_name = PageFactory.current_factory.page_class
|
28
|
+
model.layout = Layout.find_by_name(PageFactory.current_factory.layout)
|
29
|
+
model.page_factory = PageFactory.current_factory.name unless PageFactory::Base == PageFactory.current_factory
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
namespace :radiant do
|
2
|
+
namespace :extensions do
|
3
|
+
namespace :page_factory do
|
4
|
+
|
5
|
+
namespace :refresh do
|
6
|
+
def factory_class(factory)
|
7
|
+
factory_class = case factory
|
8
|
+
when '', nil : nil
|
9
|
+
when 'page', 'Page' : 'PageFactory'
|
10
|
+
else factory.capitalize + 'PageFactory'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
task :update_parts, :factory, :needs => :environment do |task, args|
|
14
|
+
updated = PageFactory::Manager.update_parts factory_class(args[:factory])
|
15
|
+
puts "Added missing parts from #{updated.join(', ')}"
|
16
|
+
end
|
17
|
+
task :prune_parts, :factory, :needs => :environment do |task, args|
|
18
|
+
updated = PageFactory::Manager.prune_parts! factory_class(args[:factory])
|
19
|
+
puts "Removed extra parts from #{updated.join(', ')}"
|
20
|
+
end
|
21
|
+
task :sync_parts, :factory, :needs => :environment do |task, args|
|
22
|
+
updated = PageFactory::Manager.sync_parts! factory_class(args[:factory])
|
23
|
+
puts "Synchronized part classes on #{updated.join(', ')}"
|
24
|
+
end
|
25
|
+
task :sync_layouts, :factory, :needs => :environment do |task, args|
|
26
|
+
updated = PageFactory::Manager.sync_layouts! factory_class(args[:factory])
|
27
|
+
puts "Synchronized layouts on #{updated.join(', ')}"
|
28
|
+
end
|
29
|
+
task :sync_classes, :factory, :needs => :environment do |task, args| factory_class(args[:factory])
|
30
|
+
updated = PageFactory::Manager.sync_classes! factory_class(args[:factory])
|
31
|
+
puts "Synchronized page classes on #{updated.join(', ')}"
|
32
|
+
end
|
33
|
+
desc "Add missing page parts, but don't change or remove any data."
|
34
|
+
task :soft, :factory, :needs => :environment do |task, args|
|
35
|
+
Rake::Task['radiant:extensions:page_factory:refresh:update_parts'].invoke args[:factory]
|
36
|
+
end
|
37
|
+
desc "Make pages look exactly like their factory definitions, including layout and page class."
|
38
|
+
task :hard, :factory, :needs => :environment do |task, args|
|
39
|
+
Rake::Task['radiant:extensions:page_factory:refresh:prune_parts'].invoke args[:factory]
|
40
|
+
Rake::Task['radiant:extensions:page_factory:refresh:sync_parts'].invoke args[:factory]
|
41
|
+
Rake::Task['radiant:extensions:page_factory:refresh:update_parts'].invoke args[:factory]
|
42
|
+
Rake::Task['radiant:extensions:page_factory:refresh:sync_layouts'].invoke args[:factory]
|
43
|
+
Rake::Task['radiant:extensions:page_factory:refresh:sync_classes'].invoke args[:factory]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Runs the migration of the Page Factory extension"
|
48
|
+
task :migrate => :environment do
|
49
|
+
require 'radiant/extension_migrator'
|
50
|
+
if ENV["VERSION"]
|
51
|
+
PageFactoryExtension.migrator.migrate(ENV["VERSION"].to_i)
|
52
|
+
else
|
53
|
+
PageFactoryExtension.migrator.migrate
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
desc "Copies public assets of the Page Factory to the instance public/ directory."
|
58
|
+
task :update => :environment do
|
59
|
+
is_svn_or_dir = proc {|path| path =~ /\.svn/ || File.directory?(path) }
|
60
|
+
puts "Copying assets from PageFactoryExtension"
|
61
|
+
Dir[PageFactoryExtension.root + "/public/**/*"].reject(&is_svn_or_dir).each do |file|
|
62
|
+
path = file.sub(PageFactoryExtension.root, '')
|
63
|
+
directory = File.dirname(path)
|
64
|
+
mkdir_p RAILS_ROOT + directory, :verbose => false
|
65
|
+
cp file, RAILS_ROOT + path, :verbose => false
|
66
|
+
end
|
67
|
+
unless PageFactoryExtension.root.starts_with? RAILS_ROOT # don't need to copy vendored tasks
|
68
|
+
puts "Copying rake tasks from PageFactoryExtension"
|
69
|
+
local_tasks_path = File.join(RAILS_ROOT, %w(lib tasks))
|
70
|
+
mkdir_p local_tasks_path, :verbose => false
|
71
|
+
Dir[File.join PageFactoryExtension.root, %w(lib tasks *.rake)].each do |file|
|
72
|
+
cp file, local_tasks_path, :verbose => false
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
desc "Syncs all available translations for this ext to the English ext master"
|
78
|
+
task :sync => :environment do
|
79
|
+
# The main translation root, basically where English is kept
|
80
|
+
language_root = PageFactoryExtension.root + "/config/locales"
|
81
|
+
words = TranslationSupport.get_translation_keys(language_root)
|
82
|
+
|
83
|
+
Dir["#{language_root}/*.yml"].each do |filename|
|
84
|
+
next if filename.match('_available_tags')
|
85
|
+
basename = File.basename(filename, '.yml')
|
86
|
+
puts "Syncing #{basename}"
|
87
|
+
(comments, other) = TranslationSupport.read_file(filename, basename)
|
88
|
+
words.each { |k,v| other[k] ||= words[k] } # Initializing hash variable as empty if it does not exist
|
89
|
+
other.delete_if { |k,v| !words[k] } # Remove if not defined in en.yml
|
90
|
+
TranslationSupport.write_file(filename, basename, comments, other)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require_dependency 'application_controller'
|
2
|
+
|
3
|
+
class PageFactoryExtension < Radiant::Extension
|
4
|
+
version "0.1"
|
5
|
+
description "A small DSL for intelligently defining content types."
|
6
|
+
url "http://github.com/joshfrench/radiant-page_factory-extension"
|
7
|
+
|
8
|
+
define_routes do |map|
|
9
|
+
map.namespace :admin do |admin|
|
10
|
+
admin.factory_link '/pages/factories', :controller => 'page_factories', :action => 'index'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def activate
|
15
|
+
Page.send :include, PageFactory::PageExtensions
|
16
|
+
PagePart.send :include, PageFactory::PagePartExtensions
|
17
|
+
Admin::PagesController.send :include, PageFactory::PagesControllerExtensions
|
18
|
+
Admin::PagesController.helper 'admin/part_description'
|
19
|
+
Admin::PagePartsController.helper 'admin/part_description'
|
20
|
+
admin.pages.new.add :form, 'page_factory_field'
|
21
|
+
admin.pages.edit.add :part_controls, 'admin/page_parts/part_description'
|
22
|
+
admin.pages.index.add :bottom, 'admin/pages/page_factories'
|
23
|
+
ActiveSupport::Dependencies.load_paths << File.join(Rails.root, 'lib')
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
/*
|
2
|
+
* dropdown.js
|
3
|
+
*
|
4
|
+
* dependencies: prototype.js, effects.js, lowpro.js
|
5
|
+
*
|
6
|
+
* --------------------------------------------------------------------------
|
7
|
+
*
|
8
|
+
* Allows you to easily create a dropdown menu item. Simply create a link
|
9
|
+
* with a class of "dropdown" that references the ID of the list that you
|
10
|
+
* would like to use as a dropdown menu.
|
11
|
+
*
|
12
|
+
* A link like this:
|
13
|
+
*
|
14
|
+
* <a class="dropdown" href="#dropdown">Menu</a>
|
15
|
+
*
|
16
|
+
* will dropdown a list of choices in the list with the ID of "dropdown".
|
17
|
+
*
|
18
|
+
* You will need to install the following hook:
|
19
|
+
*
|
20
|
+
* Event.addBehavior({'a.dropdown': Dropdown.TriggerBehavior()});
|
21
|
+
*
|
22
|
+
* --------------------------------------------------------------------------
|
23
|
+
*
|
24
|
+
* Copyright (c) 2010, John W. Long
|
25
|
+
*
|
26
|
+
* Permission is hereby granted, free of charge, to any person obtaining a
|
27
|
+
* copy of this software and associated documentation files (the "Software"),
|
28
|
+
* to deal in the Software without restriction, including without limitation
|
29
|
+
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
30
|
+
* and/or sell copies of the Software, and to permit persons to whom the
|
31
|
+
* Software is furnished to do so, subject to the following conditions:
|
32
|
+
*
|
33
|
+
* The above copyright notice and this permission notice shall be included in
|
34
|
+
* all copies or substantial portions of the Software.
|
35
|
+
*
|
36
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
37
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
38
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
39
|
+
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
40
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
41
|
+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
42
|
+
* DEALINGS IN THE SOFTWARE.
|
43
|
+
*
|
44
|
+
*/
|
45
|
+
|
46
|
+
var Dropdown = {
|
47
|
+
|
48
|
+
DefaultPosition: 'bottom',
|
49
|
+
|
50
|
+
DefaultEffect: 'slide',
|
51
|
+
DefaultEffectDuration: 0.1,
|
52
|
+
|
53
|
+
EffectPairs: {
|
54
|
+
'slide' : ['SlideDown', 'SlideUp'],
|
55
|
+
'blind' : ['BlindDown', 'BlindUp'],
|
56
|
+
'appear': ['Appear', 'Fade']
|
57
|
+
}
|
58
|
+
|
59
|
+
};
|
60
|
+
|
61
|
+
Dropdown.TriggerBehavior = Behavior.create({
|
62
|
+
initialize: function(options) {
|
63
|
+
var options = options || {};
|
64
|
+
options.position = (options.position || Dropdown.DefaultPosition).toLowerCase();
|
65
|
+
options.effect = (options.effect || Dropdown.DefaultEffect).toLowerCase();
|
66
|
+
options.duration = (options.duration || Dropdown.DefaultEffectDuration);
|
67
|
+
this.options = options;
|
68
|
+
|
69
|
+
var matches = this.element.href.match(/\#(.+)$/);
|
70
|
+
if (matches) this.menu = Dropdown.Menu.findOrCreate(matches[1]);
|
71
|
+
},
|
72
|
+
|
73
|
+
onclick: function(event) {
|
74
|
+
if (this.menu) this.menu.toggle(this.element, this.options);
|
75
|
+
event.stop();
|
76
|
+
}
|
77
|
+
});
|
78
|
+
|
79
|
+
Dropdown.Menu = Class.create({
|
80
|
+
|
81
|
+
initialize: function(element) {
|
82
|
+
element.remove();
|
83
|
+
this.element = element;
|
84
|
+
this.wrapper = $div({'class': 'dropdown_wrapper', 'style': 'position: absolute; display: none'}, element);
|
85
|
+
document.body.insert(this.wrapper);
|
86
|
+
},
|
87
|
+
|
88
|
+
open: function(trigger, options) {
|
89
|
+
this.wrapper.hide();
|
90
|
+
trigger.addClassName('selected');
|
91
|
+
this.position(trigger, options);
|
92
|
+
var name = options.effect;
|
93
|
+
var effect = Effect[Dropdown.EffectPairs[name][0]];
|
94
|
+
effect(this.wrapper, {duration: options.duration});
|
95
|
+
},
|
96
|
+
|
97
|
+
close: function(trigger, options) {
|
98
|
+
var name = options.effect;
|
99
|
+
var effect = Effect[Dropdown.EffectPairs[name][1]];
|
100
|
+
effect(this.wrapper, {duration: options.duration});
|
101
|
+
trigger.removeClassName('selected');
|
102
|
+
},
|
103
|
+
|
104
|
+
toggle: function(trigger, options) {
|
105
|
+
if (this.lastTrigger == trigger) {
|
106
|
+
if (this.wrapper.visible()) {
|
107
|
+
this.close(trigger, options);
|
108
|
+
} else {
|
109
|
+
this.open(trigger, options);
|
110
|
+
}
|
111
|
+
} else {
|
112
|
+
if (this.lastTrigger) this.lastTrigger.removeClassName('selected');
|
113
|
+
this.open(trigger, options);
|
114
|
+
}
|
115
|
+
this.lastTrigger = trigger;
|
116
|
+
},
|
117
|
+
|
118
|
+
position: function(trigger, options) {
|
119
|
+
switch(options.position) {
|
120
|
+
case 'top': this.positionTop(trigger); break;
|
121
|
+
case 'bottom': this.positionBottom(trigger); break;
|
122
|
+
default: this.positionBottom(trigger);
|
123
|
+
}
|
124
|
+
},
|
125
|
+
|
126
|
+
positionTop: function(trigger) {
|
127
|
+
var offset = trigger.cumulativeOffset();
|
128
|
+
var height = this.wrapper.getHeight();
|
129
|
+
this.wrapper.setStyle({
|
130
|
+
left: offset.left + 'px',
|
131
|
+
top: (offset.top - height) + 'px'
|
132
|
+
});
|
133
|
+
},
|
134
|
+
|
135
|
+
positionBottom: function(trigger) {
|
136
|
+
var offset = trigger.cumulativeOffset();
|
137
|
+
var height = trigger.getHeight();
|
138
|
+
this.wrapper.setStyle({
|
139
|
+
left: offset.left + 'px',
|
140
|
+
top: (offset.top + height) + 'px'
|
141
|
+
});
|
142
|
+
}
|
143
|
+
|
144
|
+
});
|
145
|
+
|
146
|
+
Dropdown.Menu.findOrCreate = function(element) {
|
147
|
+
var element = $(element);
|
148
|
+
var key = element.identify();
|
149
|
+
var menu = Dropdown.Menu.controls[key];
|
150
|
+
if (menu == null) menu = Dropdown.Menu.controls[key] = new Dropdown.Menu(element);
|
151
|
+
return menu;
|
152
|
+
}
|
153
|
+
Dropdown.Menu.controls = {};
|