initial commit

This commit is contained in:
Espen Antonsen 2009-05-22 14:13:46 +02:00
commit d48313b7d3
96 changed files with 15121 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
log/*.log
tmp/**/*
.DS_Store
doc/api
doc/app

243
README Normal file
View file

@ -0,0 +1,243 @@
== Welcome to Rails
Rails is a web-application framework that includes everything needed to create
database-backed web applications according to the Model-View-Control pattern.
This pattern splits the view (also called the presentation) into "dumb" templates
that are primarily responsible for inserting pre-built data in between HTML tags.
The model contains the "smart" domain objects (such as Account, Product, Person,
Post) that holds all the business logic and knows how to persist themselves to
a database. The controller handles the incoming requests (such as Save New Account,
Update Product, Show Post) by manipulating the model and directing data to the view.
In Rails, the model is handled by what's called an object-relational mapping
layer entitled Active Record. This layer allows you to present the data from
database rows as objects and embellish these data objects with business logic
methods. You can read more about Active Record in
link:files/vendor/rails/activerecord/README.html.
The controller and view are handled by the Action Pack, which handles both
layers by its two parts: Action View and Action Controller. These two layers
are bundled in a single package due to their heavy interdependence. This is
unlike the relationship between the Active Record and Action Pack that is much
more separate. Each of these packages can be used independently outside of
Rails. You can read more about Action Pack in
link:files/vendor/rails/actionpack/README.html.
== Getting Started
1. At the command prompt, start a new Rails application using the <tt>rails</tt> command
and your application name. Ex: rails myapp
2. Change directory into myapp and start the web server: <tt>script/server</tt> (run with --help for options)
3. Go to http://localhost:3000/ and get "Welcome aboard: You're riding the Rails!"
4. Follow the guidelines to start developing your application
== Web Servers
By default, Rails will try to use Mongrel if it's are installed when started with script/server, otherwise Rails will use WEBrick, the webserver that ships with Ruby. But you can also use Rails
with a variety of other web servers.
Mongrel is a Ruby-based webserver with a C component (which requires compilation) that is
suitable for development and deployment of Rails applications. If you have Ruby Gems installed,
getting up and running with mongrel is as easy as: <tt>gem install mongrel</tt>.
More info at: http://mongrel.rubyforge.org
Say other Ruby web servers like Thin and Ebb or regular web servers like Apache or LiteSpeed or
Lighttpd or IIS. The Ruby web servers are run through Rack and the latter can either be setup to use
FCGI or proxy to a pack of Mongrels/Thin/Ebb servers.
== Apache .htaccess example for FCGI/CGI
# General Apache options
AddHandler fastcgi-script .fcgi
AddHandler cgi-script .cgi
Options +FollowSymLinks +ExecCGI
# If you don't want Rails to look in certain directories,
# use the following rewrite rules so that Apache won't rewrite certain requests
#
# Example:
# RewriteCond %{REQUEST_URI} ^/notrails.*
# RewriteRule .* - [L]
# Redirect all requests not available on the filesystem to Rails
# By default the cgi dispatcher is used which is very slow
#
# For better performance replace the dispatcher with the fastcgi one
#
# Example:
# RewriteRule ^(.*)$ dispatch.fcgi [QSA,L]
RewriteEngine On
# If your Rails application is accessed via an Alias directive,
# then you MUST also set the RewriteBase in this htaccess file.
#
# Example:
# Alias /myrailsapp /path/to/myrailsapp/public
# RewriteBase /myrailsapp
RewriteRule ^$ index.html [QSA]
RewriteRule ^([^.]+)$ $1.html [QSA]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ dispatch.cgi [QSA,L]
# In case Rails experiences terminal errors
# Instead of displaying this message you can supply a file here which will be rendered instead
#
# Example:
# ErrorDocument 500 /500.html
ErrorDocument 500 "<h2>Application error</h2>Rails application failed to start properly"
== Debugging Rails
Sometimes your application goes wrong. Fortunately there are a lot of tools that
will help you debug it and get it back on the rails.
First area to check is the application log files. Have "tail -f" commands running
on the server.log and development.log. Rails will automatically display debugging
and runtime information to these files. Debugging info will also be shown in the
browser on requests from 127.0.0.1.
You can also log your own messages directly into the log file from your code using
the Ruby logger class from inside your controllers. Example:
class WeblogController < ActionController::Base
def destroy
@weblog = Weblog.find(params[:id])
@weblog.destroy
logger.info("#{Time.now} Destroyed Weblog ID ##{@weblog.id}!")
end
end
The result will be a message in your log file along the lines of:
Mon Oct 08 14:22:29 +1000 2007 Destroyed Weblog ID #1
More information on how to use the logger is at http://www.ruby-doc.org/core/
Also, Ruby documentation can be found at http://www.ruby-lang.org/ including:
* The Learning Ruby (Pickaxe) Book: http://www.ruby-doc.org/docs/ProgrammingRuby/
* Learn to Program: http://pine.fm/LearnToProgram/ (a beginners guide)
These two online (and free) books will bring you up to speed on the Ruby language
and also on programming in general.
== Debugger
Debugger support is available through the debugger command when you start your Mongrel or
Webrick server with --debugger. This means that you can break out of execution at any point
in the code, investigate and change the model, AND then resume execution!
You need to install ruby-debug to run the server in debugging mode. With gems, use 'gem install ruby-debug'
Example:
class WeblogController < ActionController::Base
def index
@posts = Post.find(:all)
debugger
end
end
So the controller will accept the action, run the first line, then present you
with a IRB prompt in the server window. Here you can do things like:
>> @posts.inspect
=> "[#<Post:0x14a6be8 @attributes={\"title\"=>nil, \"body\"=>nil, \"id\"=>\"1\"}>,
#<Post:0x14a6620 @attributes={\"title\"=>\"Rails you know!\", \"body\"=>\"Only ten..\", \"id\"=>\"2\"}>]"
>> @posts.first.title = "hello from a debugger"
=> "hello from a debugger"
...and even better is that you can examine how your runtime objects actually work:
>> f = @posts.first
=> #<Post:0x13630c4 @attributes={"title"=>nil, "body"=>nil, "id"=>"1"}>
>> f.
Display all 152 possibilities? (y or n)
Finally, when you're ready to resume execution, you enter "cont"
== Console
You can interact with the domain model by starting the console through <tt>script/console</tt>.
Here you'll have all parts of the application configured, just like it is when the
application is running. You can inspect domain models, change values, and save to the
database. Starting the script without arguments will launch it in the development environment.
Passing an argument will specify a different environment, like <tt>script/console production</tt>.
To reload your controllers and models after launching the console run <tt>reload!</tt>
== dbconsole
You can go to the command line of your database directly through <tt>script/dbconsole</tt>.
You would be connected to the database with the credentials defined in database.yml.
Starting the script without arguments will connect you to the development database. Passing an
argument will connect you to a different database, like <tt>script/dbconsole production</tt>.
Currently works for mysql, postgresql and sqlite.
== Description of Contents
app
Holds all the code that's specific to this particular application.
app/controllers
Holds controllers that should be named like weblogs_controller.rb for
automated URL mapping. All controllers should descend from ApplicationController
which itself descends from ActionController::Base.
app/models
Holds models that should be named like post.rb.
Most models will descend from ActiveRecord::Base.
app/views
Holds the template files for the view that should be named like
weblogs/index.html.erb for the WeblogsController#index action. All views use eRuby
syntax.
app/views/layouts
Holds the template files for layouts to be used with views. This models the common
header/footer method of wrapping views. In your views, define a layout using the
<tt>layout :default</tt> and create a file named default.html.erb. Inside default.html.erb,
call <% yield %> to render the view using this layout.
app/helpers
Holds view helpers that should be named like weblogs_helper.rb. These are generated
for you automatically when using script/generate for controllers. Helpers can be used to
wrap functionality for your views into methods.
config
Configuration files for the Rails environment, the routing map, the database, and other dependencies.
db
Contains the database schema in schema.rb. db/migrate contains all
the sequence of Migrations for your schema.
doc
This directory is where your application documentation will be stored when generated
using <tt>rake doc:app</tt>
lib
Application specific libraries. Basically, any kind of custom code that doesn't
belong under controllers, models, or helpers. This directory is in the load path.
public
The directory available for the web server. Contains subdirectories for images, stylesheets,
and javascripts. Also contains the dispatchers and the default HTML files. This should be
set as the DOCUMENT_ROOT of your web server.
script
Helper scripts for automation and generation.
test
Unit and functional tests along with fixtures. When using the script/generate scripts, template
test files will be generated for you and placed in this directory.
vendor
External libraries that the application depends on. Also includes the plugins subdirectory.
If the app has frozen rails, those gems also go here, under vendor/rails/.
This directory is in the load path.

10
Rakefile Normal file
View file

@ -0,0 +1,10 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
require(File.join(File.dirname(__FILE__), 'config', 'boot'))
require 'rake'
require 'rake/testtask'
require 'rake/rdoctask'
require 'tasks/rails'

View file

@ -0,0 +1,8 @@
class Admin::ApplicationController < ApplicationController
protected
before_filter :login_required
end

View file

@ -0,0 +1,21 @@
class AlbumsController < ApplicationController
def index
@albums = Album.find(:all)
respond_to do |format|
format.html
format.json { render :json => @albums }
format.xml { render :xml => @albums }
end
end
def show
@album = Album.find( params[:id])
respond_to do |format|
format.html
format.json { render :json => @album }
format.xml { render :xml => @album }
end
end
end

View file

@ -0,0 +1,20 @@
# Filters added to this controller apply to all controllers in the application.
# Likewise, all the methods added will be available for all controllers.
class ApplicationController < ActionController::Base
helper :all # include all helpers, all the time
protect_from_forgery # See ActionController::RequestForgeryProtection for details
# Scrub sensitive parameters from your log
# filter_parameter_logging :password
protected
def set_current_person
@current_user = session[:user]
end
def login_required
redirect_to(login_path) unless @current_user
end
end

View file

@ -0,0 +1,2 @@
class PhotosController < ApplicationController
end

View file

@ -0,0 +1,3 @@
# Methods added to this helper will be available to all templates in the application.
module ApplicationHelper
end

View file

@ -0,0 +1,2 @@
module EventsHelper
end

View file

@ -0,0 +1,2 @@
module PhotosHelper
end

3
app/models/album.rb Normal file
View file

@ -0,0 +1,3 @@
class Album < ActiveRecord::Base
has_many :photos
end

3
app/models/photo.rb Normal file
View file

@ -0,0 +1,3 @@
class Photo < ActiveRecord::Base
belongs_to :album
end

View file

@ -0,0 +1,5 @@
<p><%= link_to album.title, album %>
<div id="photos">
<%= render :partial => album.photos.find(:all, :limit => 2) %>
</div>
</p>

View file

@ -0,0 +1,2 @@
<h1>Albums</h1>
<%= render :partial => @albums %>

View file

@ -0,0 +1,42 @@
<h1><%= @album.title %></h1>
<script src="/javascripts/jquery-1.3.2.js" type="text/javascript" charset="utf-8"></script>
<!--
<link rel="stylesheet" href="/javascripts/jquery-galleryview-2.0/galleryview.css" type="text/css" media="screen" title="no title" charset="utf-8">
<link rel="stylesheet" href="/javascripts/jquery-galleryview-2.0/galleryview.css" type="text/css" media="screen" title="no title" charset="utf-8">
<script src="/javascripts/jquery-galleryview-2.0/jquery.timers-1.1.2.js" type="text/javascript" charset="utf-8"></script>
<script src="/javascripts/jquery-galleryview-2.0/jquery.galleryview-2.0.js" type="text/javascript" charset="utf-8"></script>
<div id="photos" class="gallery">
<div class="panel">
<div class="panel-overlay">
This is the text in the background
</div>
<div class="overlay-background"></div>
</div>
<ul class="filmstrip">
<%= render :partial => @album.photos.find(:all, :limit => 10) %>
</ul>
</div>
<script type="text/javascript">
$('#photos').galleryView({
panel_width: 800,
panel_height: 400,
frame_width: 100,
frame_height: 100
});
</script>
-->
<link href="/javascripts/galleria/galleria.css" rel="stylesheet" type="text/css" media="screen">
<script type="text/javascript" src="/javascripts/galleria/jquery.galleria.js"></script>
<script type="text/javascript">
jQuery(function($) { $('ul.gallery').galleria(); });
</script>
<ul class="gallery">
<%= render :partial => @album.photos.find(:all, :limit => 10) %>
</ul>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Gallery admin</title>
</head>
<body>
<%= yield%>
</body>
</html>

View file

@ -0,0 +1,16 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>Gallery</title>
</head>
<body>
<%= yield %>
</body>
</html>

View file

@ -0,0 +1,4 @@
<li>
<a href="<%= APP_CONFIG[:thumbs_path_public] + photo.album.path + "/" + photo.id.to_s + "_large.jpg" %>" title="<%= photo.title %>">
<%= image_tag APP_CONFIG[:thumbs_path_public] + photo.album.path + "/" + photo.id.to_s + "_small.jpg" %></a>
</li>

View file

@ -0,0 +1 @@
<%= image_tag APP_CONFIG[:thumbs_path_public] + photo.id.to_s + ".jpg" %>

110
config/boot.rb Normal file
View file

@ -0,0 +1,110 @@
# Don't change this file!
# Configure your app in config/environment.rb and config/environments/*.rb
RAILS_ROOT = "#{File.dirname(__FILE__)}/.." unless defined?(RAILS_ROOT)
module Rails
class << self
def boot!
unless booted?
preinitialize
pick_boot.run
end
end
def booted?
defined? Rails::Initializer
end
def pick_boot
(vendor_rails? ? VendorBoot : GemBoot).new
end
def vendor_rails?
File.exist?("#{RAILS_ROOT}/vendor/rails")
end
def preinitialize
load(preinitializer_path) if File.exist?(preinitializer_path)
end
def preinitializer_path
"#{RAILS_ROOT}/config/preinitializer.rb"
end
end
class Boot
def run
load_initializer
Rails::Initializer.run(:set_load_path)
end
end
class VendorBoot < Boot
def load_initializer
require "#{RAILS_ROOT}/vendor/rails/railties/lib/initializer"
Rails::Initializer.run(:install_gem_spec_stubs)
Rails::GemDependency.add_frozen_gem_path
end
end
class GemBoot < Boot
def load_initializer
self.class.load_rubygems
load_rails_gem
require 'initializer'
end
def load_rails_gem
if version = self.class.gem_version
gem 'rails', version
else
gem 'rails'
end
rescue Gem::LoadError => load_error
$stderr.puts %(Missing the Rails #{version} gem. Please `gem install -v=#{version} rails`, update your RAILS_GEM_VERSION setting in config/environment.rb for the Rails version you do have installed, or comment out RAILS_GEM_VERSION to use the latest version installed.)
exit 1
end
class << self
def rubygems_version
Gem::RubyGemsVersion rescue nil
end
def gem_version
if defined? RAILS_GEM_VERSION
RAILS_GEM_VERSION
elsif ENV.include?('RAILS_GEM_VERSION')
ENV['RAILS_GEM_VERSION']
else
parse_gem_version(read_environment_rb)
end
end
def load_rubygems
require 'rubygems'
min_version = '1.3.1'
unless rubygems_version >= min_version
$stderr.puts %Q(Rails requires RubyGems >= #{min_version} (you have #{rubygems_version}). Please `gem update --system` and try again.)
exit 1
end
rescue LoadError
$stderr.puts %Q(Rails requires RubyGems >= #{min_version}. Please install RubyGems and try again: http://rubygems.rubyforge.org)
exit 1
end
def parse_gem_version(text)
$1 if text =~ /^[^#]*RAILS_GEM_VERSION\s*=\s*["']([!~<>=]*\s*[\d.]+)["']/
end
private
def read_environment_rb
File.read("#{RAILS_ROOT}/config/environment.rb")
end
end
end
end
# All that for this:
Rails.boot!

15
config/database.yml Normal file
View file

@ -0,0 +1,15 @@
development:
adapter: postgresql
database: gallery_development
username: gallery
password: changeme
production:
adapter: postgresql
database: gallery
username: gallery
password: changeme
test:
adapter: postgresql
database: gallery_test
username: gallery
password: changeme

30
config/environment.rb Normal file
View file

@ -0,0 +1,30 @@
# Be sure to restart your server when you modify this file
# Specifies gem version of Rails to use when vendor/rails is not present
RAILS_GEM_VERSION = '2.3.2' unless defined? RAILS_GEM_VERSION
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
Rails::Initializer.run do |config|
config.time_zone = 'Copenhagen'
config.i18n.default_locale = 'no-NB'
config.action_controller.session = {
:session_key => '_app_session',
:secret => '060feafeop90cuepaiam324eoimxeaioa2b4220c445486dace48f53fc0a0d4ec4e8de033e1db323628d66b6cx990loibjustintime99'
}
config.action_mailer.smtp_settings = {
:address => "smtp.gmail.com",
:port => 587,
:domain => "espen@inspired.no",
:authentication => :plain,
:user_name => "espen@inspired.no",
:password => "tkg5megmeg"
}
#config.gem "image_science"
end

View file

@ -0,0 +1,17 @@
# Settings specified here will take precedence over those in config/environment.rb
# In the development environment your application's code is reloaded on
# every request. This slows down response time but is perfect for development
# since you don't have to restart the webserver when you make code changes.
config.cache_classes = false
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_view.debug_rjs = true
config.action_controller.perform_caching = false
# Don't care if the mailer can't send
config.action_mailer.raise_delivery_errors = false

View file

@ -0,0 +1,28 @@
# Settings specified here will take precedence over those in config/environment.rb
# The production environment is meant for finished, "live" apps.
# Code is not reloaded between requests
config.cache_classes = true
# Full error reports are disabled and caching is turned on
config.action_controller.consider_all_requests_local = false
config.action_controller.perform_caching = true
config.action_view.cache_template_loading = true
# See everything in the log (default is :info)
# config.log_level = :debug
# Use a different logger for distributed setups
# config.logger = SyslogLogger.new
# Use a different cache store in production
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and javascripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
# Enable threaded mode
# config.threadsafe!

View file

@ -0,0 +1,28 @@
# Settings specified here will take precedence over those in config/environment.rb
# The test environment is used exclusively to run your application's
# test suite. You never need to work with it otherwise. Remember that
# your test database is "scratch space" for the test suite and is wiped
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true
# Log error messages when you accidentally call methods on nil.
config.whiny_nils = true
# Show full error reports and disable caching
config.action_controller.consider_all_requests_local = true
config.action_controller.perform_caching = false
config.action_view.cache_template_loading = true
# Disable request forgery protection in test environment
config.action_controller.allow_forgery_protection = false
# Tell Action Mailer not to deliver emails to the real world.
# The :test delivery method accumulates sent emails in the
# ActionMailer::Base.deliveries array.
config.action_mailer.delivery_method = :test
# Use SQL instead of Active Record's schema dumper when creating the test database.
# This is necessary if your schema can't be completely dumped by the schema dumper,
# like if you have constraints or database-specific column types
# config.active_record.schema_format = :sql

View file

@ -0,0 +1,7 @@
# Be sure to restart your server when you modify this file.
# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
# You can also remove all the silencers if you're trying do debug a problem that might steem from framework code.
# Rails.backtrace_cleaner.remove_silencers!

View file

@ -0,0 +1,10 @@
# Be sure to restart your server when you modify this file.
# Add new inflection rules using the following format
# (all these examples are active by default):
# ActiveSupport::Inflector.inflections do |inflect|
# inflect.plural /^(ox)$/i, '\1en'
# inflect.singular /^(ox)en/i, '\1'
# inflect.irregular 'person', 'people'
# inflect.uncountable %w( fish sheep )
# end

View file

@ -0,0 +1 @@
APP_CONFIG = YAML.load_file("#{RAILS_ROOT}/config/settings.yml")[RAILS_ENV].symbolize_keys

View file

@ -0,0 +1,5 @@
# Be sure to restart your server when you modify this file.
# Add new mime types for use in respond_to blocks:
# Mime::Type.register "text/richtext", :rtf
# Mime::Type.register_alias "text/html", :iphone

View file

@ -0,0 +1,19 @@
# Be sure to restart your server when you modify this file.
# These settings change the behavior of Rails 2 apps and will be defaults
# for Rails 3. You can remove this initializer when Rails 3 is released.
if defined?(ActiveRecord)
# Include Active Record class name as root for JSON serialized output.
ActiveRecord::Base.include_root_in_json = true
# Store the full class name (including module namespace) in STI type column.
ActiveRecord::Base.store_full_sti_class = true
end
# Use ISO 8601 format for JSON serialized times and dates.
ActiveSupport.use_standard_json_time_format = true
# Don't escape HTML entities in JSON, leave that for the #json_escape helper.
# if you're including raw json in an HTML page.
ActiveSupport.escape_html_entities_in_json = false

View file

@ -0,0 +1,15 @@
# Be sure to restart your server when you modify this file.
# Your secret key for verifying cookie session data integrity.
# If you change this key, all old sessions will become invalid!
# Make sure the secret is at least 30 characters and all random,
# no regular words or you'll be exposed to dictionary attacks.
ActionController::Base.session = {
:key => '_gallery_session',
:secret => 'eb2b1c59315d0f788fd425feab3e10d78a6830dd2bf843a56ac4af333eb290781b06e6605c15d87a1539181b3a928852b76f7459e963b32fdb77e5995af0b1d6'
}
# Use the database for sessions instead of the cookie-based default,
# which shouldn't be used to store highly confidential information
# (create the session table with "rake db:sessions:create")
# ActionController::Base.session_store = :active_record_store

5
config/locales/en.yml Normal file
View file

@ -0,0 +1,5 @@
# Sample localization file for English. Add more files in this directory for other locales.
# See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
en:
hello: "Hello world"

98
config/locales/no-NB.yml Normal file
View file

@ -0,0 +1,98 @@
# Norwegian, norsk bokmål, by irb.no
"no-NB":
support:
array:
sentence_connector: "og"
date:
formats:
default: "%d.%m.%Y"
short: "%e. %b"
long: "%e. %B %Y"
day_names: [søndag, mandag, tirsdag, onsdag, torsdag, fredag, lørdag]
abbr_day_names: [søn, man, tir, ons, tor, fre, lør]
month_names: [~, januar, februar, mars, april, mai, juni, juli, august, september, oktober, november, desember]
abbr_month_names: [~, jan, feb, mar, apr, mai, jun, jul, aug, sep, okt, nov, des]
order: [:day, :month, :year]
time:
formats:
default: "%A, %e. %B %Y, %H:%M"
time: "%H:%M"
short: "%e. %B, %H:%M"
long: "%A, %e. %B %Y, %H:%M"
date: "%d.%m.%Y"
full_date: "%A, %d. %B %Y"
am: ""
pm: ""
datetime:
distance_in_words:
half_a_minute: "et halvt minutt"
less_than_x_seconds:
one: "mindre enn 1 sekund"
other: "mindre enn {{count}} sekunder"
x_seconds:
one: "1 sekund"
other: "{{count}} sekunder"
less_than_x_minutes:
one: "mindre enn 1 minutt"
other: "mindre enn {{count}} minutter"
x_minutes:
one: "1 minutt"
other: "{{count}} minutter"
about_x_hours:
one: "rundt 1 time"
other: "rundt {{count}} timer"
x_days:
one: "1 dag"
other: "{{count}} dager"
about_x_months:
one: "rundt 1 måned"
other: "rundt {{count}} måneder"
x_months:
one: "1 måned"
other: "{{count}} måneder"
about_x_years:
one: "rundt 1 år"
other: "rundt {{count}} år"
over_x_years:
one: "over 1 år"
other: "over {{count}} år"
number:
format:
precision: 2
separator: "."
delimiter: ","
currency:
format:
unit: "kr"
format: "%n %u"
precision:
format:
delimiter: ""
precision: 4
activerecord:
errors:
template:
header: "kunne ikke lagre {{model}} på grunn av {{count}} feil."
body: "det oppstod problemer i følgende felt:"
messages:
inclusion: "er ikke inkludert i listen"
exclusion: "er reservert"
invalid: "er ugyldig"
confirmation: "passer ikke bekreftelsen"
accepted: "må være akseptert"
empty: "kan ikke være tom"
blank: "kan ikke være blank"
too_long: "er for lang (maksimum {{count}} tegn)"
too_short: "er for kort (minimum {{count}} tegn)"
wrong_length: "er av feil lengde (maksimum {{count}} tegn)"
taken: "er allerede i bruk"
not_a_number: "er ikke et tall"
greater_than: "må være større enn {{count}}"
greater_than_or_equal_to: "må være større enn eller lik {{count}}"
equal_to: "må være lik {{count}}"
less_than: "må være mindre enn {{count}}"
less_than_or_equal_to: "må være mindre enn eller lik {{count}}"
odd: "må være oddetall"
even: "må være partall"
# models:
# attributes:

6
config/routes.rb Normal file
View file

@ -0,0 +1,6 @@
ActionController::Routing::Routes.draw do |map|
map.resources :photos
map.resources :albums
#map.connect ':controller/:action/:id'
#map.connect ':controller/:action/:id.:format'
end

16
config/settings.yml Normal file
View file

@ -0,0 +1,16 @@
development:
site_name: Gallery
admin_email: espen@inspired.no
site_url: gallery.dev:3000
photos_path: '/users/Espen/gallery/'
thumbs_path: '/users/Espen/gallery_thumbs/'
photos_path_public: '/files/'
thumbs_path_public: '/thumbs/'
thumb_width: 200
thumb_height: 200
production:
site_name: Gallery
admin_email: espen@inspired.no
site_url: photos.inspired.no
photos_path: '/users/Espen/gallery/'
photos_path_public: '/photos/'

View file

@ -0,0 +1,13 @@
class CreateAlbum < ActiveRecord::Migration
def self.up
create_table :albums do |t|
t.string :title, :length => 250, :null => false
t.text :description
t.timestamps
end
end
def self.down
drop_table :albums
end
end

View file

@ -0,0 +1,14 @@
class CreatePhotos < ActiveRecord::Migration
def self.up
create_table :photos do |t|
t.string :title, :length => 250, :null => false
t.text :description
t.references :album
t.timestamps
end
end
def self.down
drop_table :photos
end
end

View file

@ -0,0 +1,11 @@
class AddPathToAlbumAndPhoto < ActiveRecord::Migration
def self.up
add_column :albums, :path, :text
add_column :photos, :path, :text
end
def self.down
remove_column :albums, :path
remove_column :photos, :path
end
end

31
db/schema.rb Normal file
View file

@ -0,0 +1,31 @@
# This file is auto-generated from the current state of the database. Instead of editing this file,
# please use the migrations feature of Active Record to incrementally modify your database, and
# then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your database schema. If you need
# to create the application database on another system, you should be using db:schema:load, not running
# all the migrations from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20090520230047) do
create_table "albums", :force => true do |t|
t.string "title", :null => false
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.text "path"
end
create_table "photos", :force => true do |t|
t.string "title", :null => false
t.text "description"
t.integer "album_id"
t.datetime "created_at"
t.datetime "updated_at"
t.text "path"
end
end

2
doc/README_FOR_APP Normal file
View file

@ -0,0 +1,2 @@
Use this README file to introduce your application and point to useful places in the API for learning more.
Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.

80
lib/scan.rb Normal file
View file

@ -0,0 +1,80 @@
module ScanFiles
# protected
require "find"
#require 'RMagick'
require "Image_Science"
supported_files = ["jpeg", "jpg", "gif", "png"]
def self.FullScan
Find.find( APP_CONFIG[:photos_path] ) { |path|
if File.file?(path) && [".jpeg", ".jpg", ".gif", ".png"].include?( File.extname(path) )
relpath = File.dirname( path ).sub(APP_CONFIG[:photos_path], '')
relfile = path.sub(APP_CONFIG[:photos_path], '')
puts relpath
album = Album.find_by_path( relpath )
if album.nil?
puts "New album : " + File.basename( relpath )
album = Album.create( :path => relpath, :title => File.basename( File.dirname(path) ) )
Dir.mkdir( APP_CONFIG[:thumbs_path] + album.path )
end
if Photo.find_by_path( relpath ).nil?
puts "New photo added"
photo = Photo.create( :album => album, :title => File.basename(path).sub( File.extname(path), '' ) , :path => relfile )
#image = Magick::Image.read(APP_CONFIG[:photos_path] + photo.path)
ImageScience.with_image(APP_CONFIG[:photos_path] + relfile) do |img|
puts "thumbing.."
img.thumbnail(75) do |thumb|
thumb.save APP_CONFIG[:thumbs_path] + photo.album.path + "/" + photo.id.to_s + "_small" + File.extname( APP_CONFIG[:photos_path] + photo.path )
end
img.thumbnail(600) do |thumb|
thumb.save APP_CONFIG[:thumbs_path] + photo.album.path + "/" + photo.id.to_s + "_large" + File.extname( APP_CONFIG[:photos_path] + photo.path )
end
end
#self.CreateThumbnail( photo, image, "small", 150, 150 )
#self.CreateThumbnail( photo, image, "large", 600, 500 )
end
end
}
end
def self.CreateThumbnail(photo,image,thumbname,width,height)
puts "Create thumb of " + photo.path
thumb = image.first.resize_to_fill( width, height)
thumb2 = image.first.change_geometry!("#{width}x#{ height }") { |cols, rows, img|
if cols < width.to_i || rows < height.to_i
puts "first if"
img.resize!(cols, rows)
bg = Magick::Image.new( width, height){self.background_color = "white"}
bg.composite(img, Magick::CenterGravity, Magick::OverCompositeOp)
else
puts "second if"
img.resize!(cols, rows)
end
}
puts "hello"
puts "write... " + APP_CONFIG[:thumbs_path] + photo.album.path + "/" + photo.id.to_s + "_" + thumbname + File.extname( APP_CONFIG[:photos_path] + photo.path )
thumb.write(APP_CONFIG[:thumbs_path] + photo.album.path + "/" + photo.id.to_s + "_" + thumbname + File.extname( APP_CONFIG[:photos_path] + photo.path ) ) { self.quality = 100 }
#image.change_geometry!(MAINSITE_SIZE) { |cols, rows, img|
# img.resize!(cols, rows)
#}
#image.write(mainsite_file)
end
def self.FullScanOld
self.ScanDirectory(APP_CONFIG[:photo_directory])
end
def self.ScanDirectory(dir)
puts "now scanning: " + dir
Dir.entries( dir ).select { |f| (f != "." && f != "..") }.each { |f|
if ( File.directory?( dir + f))
puts "found directory scan more.. " + dir + f + "/"
self.ScanDirectory( dir + f )
elsif ( supported_files.include?( File.extname(dir + f) ) )
puts "insert file in database: " + f
end
}
end
end

30
public/404.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>The page you were looking for doesn't exist (404)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/404.html -->
<div class="dialog">
<h1>The page you were looking for doesn't exist.</h1>
<p>You may have mistyped the address or the page may have moved.</p>
</div>
</body>
</html>

30
public/422.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>The change you wanted was rejected (422)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/422.html -->
<div class="dialog">
<h1>The change you wanted was rejected.</h1>
<p>Maybe you tried to change something you didn't have access to.</p>
</div>
</body>
</html>

30
public/500.html Normal file
View file

@ -0,0 +1,30 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8" />
<title>We're sorry, but something went wrong (500)</title>
<style type="text/css">
body { background-color: #fff; color: #666; text-align: center; font-family: arial, sans-serif; }
div.dialog {
width: 25em;
padding: 0 4em;
margin: 4em auto 0 auto;
border: 1px solid #ccc;
border-right-color: #999;
border-bottom-color: #999;
}
h1 { font-size: 100%; color: #f00; line-height: 1.5em; }
</style>
</head>
<body>
<!-- This file lives in public/500.html -->
<div class="dialog">
<h1>We're sorry, but something went wrong.</h1>
<p>We've been notified about this issue and we'll take a look at it shortly.</p>
</div>
</body>
</html>

0
public/favicon.ico Normal file
View file

1
public/files Symbolic link
View file

@ -0,0 +1 @@
/users/espen/gallery

BIN
public/images/rails.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

275
public/index.html Normal file
View file

@ -0,0 +1,275 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<title>Ruby on Rails: Welcome aboard</title>
<style type="text/css" media="screen">
body {
margin: 0;
margin-bottom: 25px;
padding: 0;
background-color: #f0f0f0;
font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
font-size: 13px;
color: #333;
}
h1 {
font-size: 28px;
color: #000;
}
a {color: #03c}
a:hover {
background-color: #03c;
color: white;
text-decoration: none;
}
#page {
background-color: #f0f0f0;
width: 750px;
margin: 0;
margin-left: auto;
margin-right: auto;
}
#content {
float: left;
background-color: white;
border: 3px solid #aaa;
border-top: none;
padding: 25px;
width: 500px;
}
#sidebar {
float: right;
width: 175px;
}
#footer {
clear: both;
}
#header, #about, #getting-started {
padding-left: 75px;
padding-right: 30px;
}
#header {
background-image: url("images/rails.png");
background-repeat: no-repeat;
background-position: top left;
height: 64px;
}
#header h1, #header h2 {margin: 0}
#header h2 {
color: #888;
font-weight: normal;
font-size: 16px;
}
#about h3 {
margin: 0;
margin-bottom: 10px;
font-size: 14px;
}
#about-content {
background-color: #ffd;
border: 1px solid #fc0;
margin-left: -11px;
}
#about-content table {
margin-top: 10px;
margin-bottom: 10px;
font-size: 11px;
border-collapse: collapse;
}
#about-content td {
padding: 10px;
padding-top: 3px;
padding-bottom: 3px;
}
#about-content td.name {color: #555}
#about-content td.value {color: #000}
#about-content.failure {
background-color: #fcc;
border: 1px solid #f00;
}
#about-content.failure p {
margin: 0;
padding: 10px;
}
#getting-started {
border-top: 1px solid #ccc;
margin-top: 25px;
padding-top: 15px;
}
#getting-started h1 {
margin: 0;
font-size: 20px;
}
#getting-started h2 {
margin: 0;
font-size: 14px;
font-weight: normal;
color: #333;
margin-bottom: 25px;
}
#getting-started ol {
margin-left: 0;
padding-left: 0;
}
#getting-started li {
font-size: 18px;
color: #888;
margin-bottom: 25px;
}
#getting-started li h2 {
margin: 0;
font-weight: normal;
font-size: 18px;
color: #333;
}
#getting-started li p {
color: #555;
font-size: 13px;
}
#search {
margin: 0;
padding-top: 10px;
padding-bottom: 10px;
font-size: 11px;
}
#search input {
font-size: 11px;
margin: 2px;
}
#search-text {width: 170px}
#sidebar ul {
margin-left: 0;
padding-left: 0;
}
#sidebar ul h3 {
margin-top: 25px;
font-size: 16px;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
}
#sidebar li {
list-style-type: none;
}
#sidebar ul.links li {
margin-bottom: 5px;
}
</style>
<script type="text/javascript" src="javascripts/prototype.js"></script>
<script type="text/javascript" src="javascripts/effects.js"></script>
<script type="text/javascript">
function about() {
if (Element.empty('about-content')) {
new Ajax.Updater('about-content', 'rails/info/properties', {
method: 'get',
onFailure: function() {Element.classNames('about-content').add('failure')},
onComplete: function() {new Effect.BlindDown('about-content', {duration: 0.25})}
});
} else {
new Effect[Element.visible('about-content') ?
'BlindUp' : 'BlindDown']('about-content', {duration: 0.25});
}
}
window.onload = function() {
$('search-text').value = '';
$('search').onsubmit = function() {
$('search-text').value = 'site:rubyonrails.org ' + $F('search-text');
}
}
</script>
</head>
<body>
<div id="page">
<div id="sidebar">
<ul id="sidebar-items">
<li>
<form id="search" action="http://www.google.com/search" method="get">
<input type="hidden" name="hl" value="en" />
<input type="text" id="search-text" name="q" value="site:rubyonrails.org " />
<input type="submit" value="Search" /> the Rails site
</form>
</li>
<li>
<h3>Join the community</h3>
<ul class="links">
<li><a href="http://www.rubyonrails.org/">Ruby on Rails</a></li>
<li><a href="http://weblog.rubyonrails.org/">Official weblog</a></li>
<li><a href="http://wiki.rubyonrails.org/">Wiki</a></li>
</ul>
</li>
<li>
<h3>Browse the documentation</h3>
<ul class="links">
<li><a href="http://api.rubyonrails.org/">Rails API</a></li>
<li><a href="http://stdlib.rubyonrails.org/">Ruby standard library</a></li>
<li><a href="http://corelib.rubyonrails.org/">Ruby core</a></li>
<li><a href="http://guides.rubyonrails.org/">Rails Guides</a></li>
</ul>
</li>
</ul>
</div>
<div id="content">
<div id="header">
<h1>Welcome aboard</h1>
<h2>You&rsquo;re riding Ruby on Rails!</h2>
</div>
<div id="about">
<h3><a href="rails/info/properties" onclick="about(); return false">About your application&rsquo;s environment</a></h3>
<div id="about-content" style="display: none"></div>
</div>
<div id="getting-started">
<h1>Getting started</h1>
<h2>Here&rsquo;s how to get rolling:</h2>
<ol>
<li>
<h2>Use <tt>script/generate</tt> to create your models and controllers</h2>
<p>To see all available options, run it without parameters.</p>
</li>
<li>
<h2>Set up a default route and remove or rename this file</h2>
<p>Routes are set up in config/routes.rb.</p>
</li>
<li>
<h2>Create your database</h2>
<p>Run <tt>rake db:migrate</tt> to create your database. If you're not using SQLite (the default), edit <tt>config/database.yml</tt> with your username and password.</p>
</li>
</ol>
</div>
</div>
<div id="footer">&nbsp;</div>
</div>
</body>
</html>

View file

@ -0,0 +1,2 @@
// Place your application-specific JavaScript functions and classes here
// This file is automatically included by javascript_include_tag :defaults

963
public/javascripts/controls.js vendored Normal file
View file

@ -0,0 +1,963 @@
// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com)
// Contributors:
// Richard Livsey
// Rahul Bhargava
// Rob Wills
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
if(typeof Effect == 'undefined')
throw("controls.js requires including script.aculo.us' effects.js library");
var Autocompleter = { };
Autocompleter.Base = Class.create({
baseInitialize: function(element, update, options) {
element = $(element);
this.element = element;
this.update = $(update);
this.hasFocus = false;
this.changed = false;
this.active = false;
this.index = 0;
this.entryCount = 0;
this.oldElementValue = this.element.value;
if(this.setOptions)
this.setOptions(options);
else
this.options = options || { };
this.options.paramName = this.options.paramName || this.element.name;
this.options.tokens = this.options.tokens || [];
this.options.frequency = this.options.frequency || 0.4;
this.options.minChars = this.options.minChars || 1;
this.options.onShow = this.options.onShow ||
function(element, update){
if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute';
Position.clone(element, update, {
setHeight: false,
offsetTop: element.offsetHeight
});
}
Effect.Appear(update,{duration:0.15});
};
this.options.onHide = this.options.onHide ||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
if(typeof(this.options.tokens) == 'string')
this.options.tokens = new Array(this.options.tokens);
// Force carriage returns as token delimiters anyway
if (!this.options.tokens.include('\n'))
this.options.tokens.push('\n');
this.observer = null;
this.element.setAttribute('autocomplete','off');
Element.hide(this.update);
Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
},
show: function() {
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
if(!this.iefix &&
(Prototype.Browser.IE) &&
(Element.getStyle(this.update, 'position')=='absolute')) {
new Insertion.After(this.update,
'<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, {setTop:(!this.update.style.height)});
this.iefix.style.zIndex = 1;
this.update.style.zIndex = 2;
Element.show(this.iefix);
},
hide: function() {
this.stopIndicator();
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
if(this.iefix) Element.hide(this.iefix);
},
startIndicator: function() {
if(this.options.indicator) Element.show(this.options.indicator);
},
stopIndicator: function() {
if(this.options.indicator) Element.hide(this.options.indicator);
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
activate: function() {
this.changed = false;
this.hasFocus = true;
this.getUpdatedChoices();
},
onHover: function(event) {
var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex)
{
this.index = element.autocompleteIndex;
this.render();
}
Event.stop(event);
},
onClick: function(event) {
var element = Event.findElement(event, 'LI');
this.index = element.autocompleteIndex;
this.selectEntry();
this.hide();
},
onBlur: function(event) {
// needed to make click events working
setTimeout(this.hide.bind(this), 250);
this.hasFocus = false;
this.active = false;
},
render: function() {
if(this.entryCount > 0) {
for (var i = 0; i < this.entryCount; i++)
this.index==i ?
Element.addClassName(this.getEntry(i),"selected") :
Element.removeClassName(this.getEntry(i),"selected");
if(this.hasFocus) {
this.show();
this.active = true;
}
} else {
this.active = false;
this.hide();
}
},
markPrevious: function() {
if(this.index > 0) this.index--;
else this.index = this.entryCount-1;
this.getEntry(this.index).scrollIntoView(true);
},
markNext: function() {
if(this.index < this.entryCount-1) this.index++;
else this.index = 0;
this.getEntry(this.index).scrollIntoView(false);
},
getEntry: function(index) {
return this.update.firstChild.childNodes[index];
},
getCurrentEntry: function() {
return this.getEntry(this.index);
},
selectEntry: function() {
this.active = false;
this.updateElement(this.getCurrentEntry());
},
updateElement: function(selectedElement) {
if (this.options.updateElement) {
this.options.updateElement(selectedElement);
return;
}
var value = '';
if (this.options.select) {
var nodes = $(selectedElement).select('.' + this.options.select) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var bounds = this.getTokenBounds();
if (bounds[0] != -1) {
var newValue = this.element.value.substr(0, bounds[0]);
var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.value = newValue + value + this.element.value.substr(bounds[1]);
} else {
this.element.value = value;
}
this.oldElementValue = this.element.value;
this.element.focus();
if (this.options.afterUpdateElement)
this.options.afterUpdateElement(this.element, selectedElement);
},
updateChoices: function(choices) {
if(!this.changed && this.hasFocus) {
this.update.innerHTML = choices;
Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.down());
if(this.update.firstChild && this.update.down().childNodes) {
this.entryCount =
this.update.down().childNodes.length;
for (var i = 0; i < this.entryCount; i++) {
var entry = this.getEntry(i);
entry.autocompleteIndex = i;
this.addObservers(entry);
}
} else {
this.entryCount = 0;
}
this.stopIndicator();
this.index = 0;
if(this.entryCount==1 && this.options.autoSelect) {
this.selectEntry();
this.hide();
} else {
this.render();
}
}
},
addObservers: function(element) {
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
},
onObserverEvent: function() {
this.changed = false;
this.tokenBounds = null;
if(this.getToken().length>=this.options.minChars) {
this.getUpdatedChoices();
} else {
this.active = false;
this.hide();
}
this.oldElementValue = this.element.value;
},
getToken: function() {
var bounds = this.getTokenBounds();
return this.element.value.substring(bounds[0], bounds[1]).strip();
},
getTokenBounds: function() {
if (null != this.tokenBounds) return this.tokenBounds;
var value = this.element.value;
if (value.strip().empty()) return [-1, 0];
var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
var offset = (diff == this.oldElementValue.length ? 1 : 0);
var prevTokenPos = -1, nextTokenPos = value.length;
var tp;
for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
if (tp > prevTokenPos) prevTokenPos = tp;
tp = value.indexOf(this.options.tokens[index], diff + offset);
if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
}
return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
}
});
Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
var boundary = Math.min(newS.length, oldS.length);
for (var index = 0; index < boundary; ++index)
if (newS[index] != oldS[index])
return index;
return boundary;
};
Ajax.Autocompleter = Class.create(Autocompleter.Base, {
initialize: function(element, update, url, options) {
this.baseInitialize(element, update, options);
this.options.asynchronous = true;
this.options.onComplete = this.onComplete.bind(this);
this.options.defaultParams = this.options.parameters || null;
this.url = url;
},
getUpdatedChoices: function() {
this.startIndicator();
var entry = encodeURIComponent(this.options.paramName) + '=' +
encodeURIComponent(this.getToken());
this.options.parameters = this.options.callback ?
this.options.callback(this.element, entry) : entry;
if(this.options.defaultParams)
this.options.parameters += '&' + this.options.defaultParams;
new Ajax.Request(this.url, this.options);
},
onComplete: function(request) {
this.updateChoices(request.responseText);
}
});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
// text only at the beginning of strings in the
// autocomplete array. Defaults to true, which will
// match text at the beginning of any *word* in the
// strings in the autocomplete array. If you want to
// search anywhere in the string, additionally set
// the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
// a partial match (unlike minChars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
// Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
Autocompleter.Local = Class.create(Autocompleter.Base, {
initialize: function(element, update, array, options) {
this.baseInitialize(element, update, options);
this.options.array = array;
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instance) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getToken();
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<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 and collection editor
// Full rewrite by Christophe Porteneuve <tdd@tddsworld.com> (April 2007).
// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
setTimeout(function() {
Field.activate(field);
}, 1);
};
Ajax.InPlaceEditor = Class.create({
initialize: function(element, url, options) {
this.url = url;
this.element = element = $(element);
this.prepareOptions();
this._controls = { };
arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
Object.extend(this.options, options || { });
if (!this.options.formId && this.element.id) {
this.options.formId = this.element.id + '-inplaceeditor';
if ($(this.options.formId))
this.options.formId = '';
}
if (this.options.externalControl)
this.options.externalControl = $(this.options.externalControl);
if (!this.options.externalControl)
this.options.externalControlOnly = false;
this._originalBackground = this.element.getStyle('background-color') || 'transparent';
this.element.title = this.options.clickToEditText;
this._boundCancelHandler = this.handleFormCancellation.bind(this);
this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
this._boundFailureHandler = this.handleAJAXFailure.bind(this);
this._boundSubmitHandler = this.handleFormSubmission.bind(this);
this._boundWrapperHandler = this.wrapUp.bind(this);
this.registerListeners();
},
checkForEscapeOrReturn: function(e) {
if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
if (Event.KEY_ESC == e.keyCode)
this.handleFormCancellation(e);
else if (Event.KEY_RETURN == e.keyCode)
this.handleFormSubmission(e);
},
createControl: function(mode, handler, extraClasses) {
var control = this.options[mode + 'Control'];
var text = this.options[mode + 'Text'];
if ('button' == control) {
var btn = document.createElement('input');
btn.type = 'submit';
btn.value = text;
btn.className = 'editor_' + mode + '_button';
if ('cancel' == mode)
btn.onclick = this._boundCancelHandler;
this._form.appendChild(btn);
this._controls[mode] = btn;
} else if ('link' == control) {
var link = document.createElement('a');
link.href = '#';
link.appendChild(document.createTextNode(text));
link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
link.className = 'editor_' + mode + '_link';
if (extraClasses)
link.className += ' ' + extraClasses;
this._form.appendChild(link);
this._controls[mode] = link;
}
},
createEditField: function() {
var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
var fld;
if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
fld = document.createElement('input');
fld.type = 'text';
var size = this.options.size || this.options.cols || 0;
if (0 < size) fld.size = size;
} else {
fld = document.createElement('textarea');
fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
fld.cols = this.options.cols || 40;
}
fld.name = this.options.paramName;
fld.value = text; // No HTML breaks conversion anymore
fld.className = 'editor_field';
if (this.options.submitOnBlur)
fld.onblur = this._boundSubmitHandler;
this._controls.editor = fld;
if (this.options.loadTextURL)
this.loadExternalText();
this._form.appendChild(this._controls.editor);
},
createForm: function() {
var ipe = this;
function addText(mode, condition) {
var text = ipe.options['text' + mode + 'Controls'];
if (!text || condition === false) return;
ipe._form.appendChild(document.createTextNode(text));
};
this._form = $(document.createElement('form'));
this._form.id = this.options.formId;
this._form.addClassName(this.options.formClassName);
this._form.onsubmit = this._boundSubmitHandler;
this.createEditField();
if ('textarea' == this._controls.editor.tagName.toLowerCase())
this._form.appendChild(document.createElement('br'));
if (this.options.onFormCustomization)
this.options.onFormCustomization(this, this._form);
addText('Before', this.options.okControl || this.options.cancelControl);
this.createControl('ok', this._boundSubmitHandler);
addText('Between', this.options.okControl && this.options.cancelControl);
this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
addText('After', this.options.okControl || this.options.cancelControl);
},
destroy: function() {
if (this._oldInnerHTML)
this.element.innerHTML = this._oldInnerHTML;
this.leaveEditMode();
this.unregisterListeners();
},
enterEditMode: function(e) {
if (this._saving || this._editing) return;
this._editing = true;
this.triggerCallback('onEnterEditMode');
if (this.options.externalControl)
this.options.externalControl.hide();
this.element.hide();
this.createForm();
this.element.parentNode.insertBefore(this._form, this.element);
if (!this.options.loadTextURL)
this.postProcessEditField();
if (e) Event.stop(e);
},
enterHover: function(e) {
if (this.options.hoverClassName)
this.element.addClassName(this.options.hoverClassName);
if (this._saving) return;
this.triggerCallback('onEnterHover');
},
getText: function() {
return this.element.innerHTML.unescapeHTML();
},
handleAJAXFailure: function(transport) {
this.triggerCallback('onFailure', transport);
if (this._oldInnerHTML) {
this.element.innerHTML = this._oldInnerHTML;
this._oldInnerHTML = null;
}
},
handleFormCancellation: function(e) {
this.wrapUp();
if (e) Event.stop(e);
},
handleFormSubmission: function(e) {
var form = this._form;
var value = $F(this._controls.editor);
this.prepareSubmission();
var params = this.options.callback(form, value) || '';
if (Object.isString(params))
params = params.toQueryParams();
params.editorId = this.element.id;
if (this.options.htmlResponse) {
var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Updater({ success: this.element }, this.url, options);
} else {
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: params,
onComplete: this._boundWrapperHandler,
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.url, options);
}
if (e) Event.stop(e);
},
leaveEditMode: function() {
this.element.removeClassName(this.options.savingClassName);
this.removeForm();
this.leaveHover();
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
if (this.options.externalControl)
this.options.externalControl.show();
this._saving = false;
this._editing = false;
this._oldInnerHTML = null;
this.triggerCallback('onLeaveEditMode');
},
leaveHover: function(e) {
if (this.options.hoverClassName)
this.element.removeClassName(this.options.hoverClassName);
if (this._saving) return;
this.triggerCallback('onLeaveHover');
},
loadExternalText: function() {
this._form.addClassName(this.options.loadingClassName);
this._controls.editor.disabled = true;
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._form.removeClassName(this.options.loadingClassName);
var text = transport.responseText;
if (this.options.stripLoadedTextTags)
text = text.stripTags();
this._controls.editor.value = text;
this._controls.editor.disabled = false;
this.postProcessEditField();
}.bind(this),
onFailure: this._boundFailureHandler
});
new Ajax.Request(this.options.loadTextURL, options);
},
postProcessEditField: function() {
var fpc = this.options.fieldPostCreation;
if (fpc)
$(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
},
prepareOptions: function() {
this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
[this._extraDefaultOptions].flatten().compact().each(function(defs) {
Object.extend(this.options, defs);
}.bind(this));
},
prepareSubmission: function() {
this._saving = true;
this.removeForm();
this.leaveHover();
this.showSaving();
},
registerListeners: function() {
this._listeners = { };
var listener;
$H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
listener = this[pair.value].bind(this);
this._listeners[pair.key] = listener;
if (!this.options.externalControlOnly)
this.element.observe(pair.key, listener);
if (this.options.externalControl)
this.options.externalControl.observe(pair.key, listener);
}.bind(this));
},
removeForm: function() {
if (!this._form) return;
this._form.remove();
this._form = null;
this._controls = { };
},
showSaving: function() {
this._oldInnerHTML = this.element.innerHTML;
this.element.innerHTML = this.options.savingText;
this.element.addClassName(this.options.savingClassName);
this.element.style.backgroundColor = this._originalBackground;
this.element.show();
},
triggerCallback: function(cbName, arg) {
if ('function' == typeof this.options[cbName]) {
this.options[cbName](this, arg);
}
},
unregisterListeners: function() {
$H(this._listeners).each(function(pair) {
if (!this.options.externalControlOnly)
this.element.stopObserving(pair.key, pair.value);
if (this.options.externalControl)
this.options.externalControl.stopObserving(pair.key, pair.value);
}.bind(this));
},
wrapUp: function(transport) {
this.leaveEditMode();
// Can't use triggerCallback due to backward compatibility: requires
// binding + direct element
this._boundComplete(transport, this.element);
}
});
Object.extend(Ajax.InPlaceEditor.prototype, {
dispose: Ajax.InPlaceEditor.prototype.destroy
});
Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
initialize: function($super, element, url, options) {
this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
$super(element, url, options);
},
createEditField: function() {
var list = document.createElement('select');
list.name = this.options.paramName;
list.size = 1;
this._controls.editor = list;
this._collection = this.options.collection || [];
if (this.options.loadCollectionURL)
this.loadCollection();
else
this.checkForExternalText();
this._form.appendChild(this._controls.editor);
},
loadCollection: function() {
this._form.addClassName(this.options.loadingClassName);
this.showLoadingText(this.options.loadingCollectionText);
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
var js = transport.responseText.strip();
if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
throw('Server returned an invalid collection representation.');
this._collection = eval(js);
this.checkForExternalText();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(this.options.loadCollectionURL, options);
},
showLoadingText: function(text) {
this._controls.editor.disabled = true;
var tempOption = this._controls.editor.firstChild;
if (!tempOption) {
tempOption = document.createElement('option');
tempOption.value = '';
this._controls.editor.appendChild(tempOption);
tempOption.selected = true;
}
tempOption.update((text || '').stripScripts().stripTags());
},
checkForExternalText: function() {
this._text = this.getText();
if (this.options.loadTextURL)
this.loadExternalText();
else
this.buildOptionList();
},
loadExternalText: function() {
this.showLoadingText(this.options.loadingText);
var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
Object.extend(options, {
parameters: 'editorId=' + encodeURIComponent(this.element.id),
onComplete: Prototype.emptyFunction,
onSuccess: function(transport) {
this._text = transport.responseText.strip();
this.buildOptionList();
}.bind(this),
onFailure: this.onFailure
});
new Ajax.Request(this.options.loadTextURL, options);
},
buildOptionList: function() {
this._form.removeClassName(this.options.loadingClassName);
this._collection = this._collection.map(function(entry) {
return 2 === entry.length ? entry : [entry, entry].flatten();
});
var marker = ('value' in this.options) ? this.options.value : this._text;
var textFound = this._collection.any(function(entry) {
return entry[0] == marker;
}.bind(this));
this._controls.editor.update('');
var option;
this._collection.each(function(entry, index) {
option = document.createElement('option');
option.value = entry[0];
option.selected = textFound ? entry[0] == marker : 0 == index;
option.appendChild(document.createTextNode(entry[1]));
this._controls.editor.appendChild(option);
}.bind(this));
this._controls.editor.disabled = false;
Field.scrollFreeActivate(this._controls.editor);
}
});
//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
//**** This only exists for a while, in order to let ****
//**** users adapt to the new API. Read up on the new ****
//**** API and convert your code to it ASAP! ****
Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
if (!options) return;
function fallback(name, expr) {
if (name in options || expr === undefined) return;
options[name] = expr;
};
fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
options.cancelLink == options.cancelButton == false ? false : undefined)));
fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
options.okLink == options.okButton == false ? false : undefined)));
fallback('highlightColor', options.highlightcolor);
fallback('highlightEndColor', options.highlightendcolor);
};
Object.extend(Ajax.InPlaceEditor, {
DefaultOptions: {
ajaxOptions: { },
autoRows: 3, // Use when multi-line w/ rows == 1
cancelControl: 'link', // 'link'|'button'|false
cancelText: 'cancel',
clickToEditText: 'Click to edit',
externalControl: null, // id|elt
externalControlOnly: false,
fieldPostCreation: 'activate', // 'activate'|'focus'|false
formClassName: 'inplaceeditor-form',
formId: null, // id|elt
highlightColor: '#ffff99',
highlightEndColor: '#ffffff',
hoverClassName: '',
htmlResponse: true,
loadingClassName: 'inplaceeditor-loading',
loadingText: 'Loading...',
okControl: 'button', // 'link'|'button'|false
okText: 'ok',
paramName: 'value',
rows: 1, // If 1 and multi-line, uses autoRows
savingClassName: 'inplaceeditor-saving',
savingText: 'Saving...',
size: 0,
stripLoadedTextTags: false,
submitOnBlur: false,
textAfterControls: '',
textBeforeControls: '',
textBetweenControls: ''
},
DefaultCallbacks: {
callback: function(form) {
return Form.serialize(form);
},
onComplete: function(transport, element) {
// For backward compatibility, this one is bound to the IPE, and passes
// the element directly. It was too often customized, so we don't break it.
new Effect.Highlight(element, {
startcolor: this.options.highlightColor, keepBackgroundImage: true });
},
onEnterEditMode: null,
onEnterHover: function(ipe) {
ipe.element.style.backgroundColor = ipe.options.highlightColor;
if (ipe._effect)
ipe._effect.cancel();
},
onFailure: function(transport, ipe) {
alert('Error communication with the server: ' + transport.responseText.stripTags());
},
onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
onLeaveEditMode: null,
onLeaveHover: function(ipe) {
ipe._effect = new Effect.Highlight(ipe.element, {
startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
restorecolor: ipe._originalBackground, keepBackgroundImage: true
});
}
},
Listeners: {
click: 'enterEditMode',
keydown: 'checkForEscapeOrReturn',
mouseover: 'enterHover',
mouseout: 'leaveHover'
}
});
Ajax.InPlaceCollectionEditor.DefaultOptions = {
loadingCollectionText: 'Loading options...'
};
// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields
Form.Element.DelayedObserver = Class.create({
initialize: function(element, delay, callback) {
this.delay = delay || 0.5;
this.element = $(element);
this.callback = callback;
this.timer = null;
this.lastValue = $F(this.element);
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
},
delayedListener: function(event) {
if(this.lastValue == $F(this.element)) return;
if(this.timer) clearTimeout(this.timer);
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
this.lastValue = $F(this.element);
},
onTimerEvent: function() {
this.timer = null;
this.callback(this.element, $F(this.element));
}
});

973
public/javascripts/dragdrop.js vendored Normal file
View file

@ -0,0 +1,973 @@
// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// script.aculo.us is freely distributable under the terms of an MIT-style license.
// For details, see the script.aculo.us web site: http://script.aculo.us/
if(Object.isUndefined(Effect))
throw("dragdrop.js requires including script.aculo.us' effects.js library");
var Droppables = {
drops: [],
remove: function(element) {
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
},
add: function(element) {
element = $(element);
var options = Object.extend({
greedy: true,
hoverclass: null,
tree: false
}, arguments[1] || { });
// cache containers
if(options.containment) {
options._containers = [];
var containment = options.containment;
if(Object.isArray(containment)) {
containment.each( function(c) { options._containers.push($(c)) });
} else {
options._containers.push($(containment));
}
}
if(options.accept) options.accept = [options.accept].flatten();
Element.makePositioned(element); // fix IE
options.element = element;
this.drops.push(options);
},
findDeepestChild: function(drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i)
if (Element.isParent(drops[i].element, deepest.element))
deepest = drops[i];
return deepest;
},
isContained: function(element, drop) {
var containmentNode;
if(drop.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return drop._containers.detect(function(c) { return containmentNode == c });
},
isAffected: function(point, element, drop) {
return (
(drop.element!=element) &&
((!drop._containers) ||
this.isContained(element, drop)) &&
((!drop.accept) ||
(Element.classNames(element).detect(
function(v) { return drop.accept.include(v) } ) )) &&
Position.within(drop.element, point[0], point[1]) );
},
deactivate: function(drop) {
if(drop.hoverclass)
Element.removeClassName(drop.element, drop.hoverclass);
this.last_active = null;
},
activate: function(drop) {
if(drop.hoverclass)
Element.addClassName(drop.element, drop.hoverclass);
this.last_active = drop;
},
show: function(point, element) {
if(!this.drops.length) return;
var drop, affected = [];
this.drops.each( function(drop) {
if(Droppables.isAffected(point, element, drop))
affected.push(drop);
});
if(affected.length>0)
drop = Droppables.findDeepestChild(affected);
if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
if (drop) {
Position.within(drop.element, point[0], point[1]);
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
if (drop != this.last_active) Droppables.activate(drop);
}
},
fire: function(event, element) {
if(!this.last_active) return;
Position.prepare();
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
if (this.last_active.onDrop) {
this.last_active.onDrop(element, this.last_active.element, event);
return true;
}
},
reset: function() {
if(this.last_active)
this.deactivate(this.last_active);
}
};
var Draggables = {
drags: [],
observers: [],
register: function(draggable) {
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
unregister: function(draggable) {
this.drags = this.drags.reject(function(d) { return d==draggable });
if(this.drags.length == 0) {
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
Event.stopObserving(document, "keypress", this.eventKeypress);
}
},
activate: function(draggable) {
if(draggable.options.delay) {
this._timeout = setTimeout(function() {
Draggables._timeout = null;
window.focus();
Draggables.activeDraggable = draggable;
}.bind(this), draggable.options.delay);
} else {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
this.activeDraggable = draggable;
}
},
deactivate: function() {
this.activeDraggable = null;
},
updateDrag: function(event) {
if(!this.activeDraggable) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
// Mozilla-based browsers fire successive mousemove events with
// the same coordinates, prevent needless redrawing (moz bug?)
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer);
},
endDrag: function(event) {
if(this._timeout) {
clearTimeout(this._timeout);
this._timeout = null;
}
if(!this.activeDraggable) return;
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
},
keyPress: function(event) {
if(this.activeDraggable)
this.activeDraggable.keyPress(event);
},
addObserver: function(observer) {
this.observers.push(observer);
this._cacheObserverCallbacks();
},
removeObserver: function(element) { // element instead of observer fixes mem leaks
this.observers = this.observers.reject( function(o) { return o.element==element });
this._cacheObserverCallbacks();
},
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
if(this[eventName+'Count'] > 0)
this.observers.each( function(o) {
if(o[eventName]) o[eventName](eventName, draggable, event);
});
if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
},
_cacheObserverCallbacks: function() {
['onStart','onEnd','onDrag'].each( function(eventName) {
Draggables[eventName+'Count'] = Draggables.observers.select(
function(o) { return o[eventName]; }
).length;
});
}
};
/*--------------------------------------------------------------------------*/
var Draggable = Class.create({
initialize: function(element) {
var defaults = {
handle: false,
reverteffect: function(element, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
queue: {scope:'_draggable', position:'end'}
});
},
endeffect: function(element) {
var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
queue: {scope:'_draggable', position:'end'},
afterFinish: function(){
Draggable._dragging[element] = false
}
});
},
zindex: 1000,
revert: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] }
delay: 0
};
if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
Object.extend(defaults, {
starteffect: function(element) {
element._opacity = Element.getOpacity(element);
Draggable._dragging[element] = true;
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
}
});
var options = Object.extend(defaults, arguments[1] || { });
this.element = $(element);
if(options.handle && Object.isString(options.handle))
this.handle = this.element.down('.'+options.handle, 0);
if(!this.handle) this.handle = $(options.handle);
if(!this.handle) this.handle = this.element;
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
options.scroll = $(options.scroll);
this._isScrollChild = Element.childOf(this.element, options.scroll);
}
Element.makePositioned(this.element); // fix IE
this.options = options;
this.dragging = false;
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
Event.observe(this.handle, "mousedown", this.eventMouseDown);
Draggables.register(this);
},
destroy: function() {
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
Draggables.unregister(this);
},
currentDelta: function() {
return([
parseInt(Element.getStyle(this.element,'left') || '0'),
parseInt(Element.getStyle(this.element,'top') || '0')]);
},
initDrag: function(event) {
if(!Object.isUndefined(Draggable._dragging[this.element]) &&
Draggable._dragging[this.element]) return;
if(Event.isLeftClick(event)) {
// abort on form elements, fixes a Firefox issue
var src = Event.element(event);
if((tag_name = src.tagName.toUpperCase()) && (
tag_name=='INPUT' ||
tag_name=='SELECT' ||
tag_name=='OPTION' ||
tag_name=='BUTTON' ||
tag_name=='TEXTAREA')) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var pos = Position.cumulativeOffset(this.element);
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
Draggables.activate(this);
Event.stop(event);
}
},
startDrag: function(event) {
this.dragging = true;
if(!this.delta)
this.delta = this.currentDelta();
if(this.options.zindex) {
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
this.element.style.zIndex = this.options.zindex;
}
if(this.options.ghosting) {
this._clone = this.element.cloneNode(true);
this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
if (!this._originallyAbsolute)
Position.absolutize(this.element);
this.element.parentNode.insertBefore(this._clone, this.element);
}
if(this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
Draggables.notify('onStart', this, event);
if(this.options.starteffect) this.options.starteffect(this.element);
},
updateDrag: function(event, pointer) {
if(!this.dragging) this.startDrag(event);
if(!this.options.quiet){
Position.prepare();
Droppables.show(pointer, this.element);
}
Draggables.notify('onDrag', this, event);
this.draw(pointer);
if(this.options.change) this.options.change(this);
if(this.options.scroll) {
this.stopScrolling();
var p;
if (this.options.scroll == window) {
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
} else {
p = Position.page(this.options.scroll);
p[0] += this.options.scroll.scrollLeft + Position.deltaX;
p[1] += this.options.scroll.scrollTop + Position.deltaY;
p.push(p[0]+this.options.scroll.offsetWidth);
p.push(p[1]+this.options.scroll.offsetHeight);
}
var speed = [0,0];
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
this.startScrolling(speed);
}
// fix AppleWebKit rendering
if(Prototype.Browser.WebKit) window.scrollBy(0,0);
Event.stop(event);
},
finishDrag: function(event, success) {
this.dragging = false;
if(this.options.quiet){
Position.prepare();
var pointer = [Event.pointerX(event), Event.pointerY(event)];
Droppables.show(pointer, this.element);
}
if(this.options.ghosting) {
if (!this._originallyAbsolute)
Position.relativize(this.element);
delete this._originallyAbsolute;
Element.remove(this._clone);
this._clone = null;
}
var dropped = false;
if(success) {
dropped = Droppables.fire(event, this.element);
if (!dropped) dropped = false;
}
if(dropped && this.options.onDropped) this.options.onDropped(this.element);
Draggables.notify('onEnd', this, event);
var revert = this.options.revert;
if(revert && Object.isFunction(revert)) revert = revert(this.element);
var d = this.currentDelta();
if(revert && this.options.reverteffect) {
if (dropped == 0 || revert != 'failure')
this.options.reverteffect(this.element,
d[1]-this.delta[1], d[0]-this.delta[0]);
} else {
this.delta = d;
}
if(this.options.zindex)
this.element.style.zIndex = this.originalZ;
if(this.options.endeffect)
this.options.endeffect(this.element);
Draggables.deactivate(this);
Droppables.reset();
},
keyPress: function(event) {
if(event.keyCode!=Event.KEY_ESC) return;
this.finishDrag(event, false);
Event.stop(event);
},
endDrag: function(event) {
if(!this.dragging) return;
this.stopScrolling();
this.finishDrag(event, true);
Event.stop(event);
},
draw: function(point) {
var pos = Position.cumulativeOffset(this.element);
if(this.options.ghosting) {
var r = Position.realOffset(this.element);
pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
}
var d = this.currentDelta();
pos[0] -= d[0]; pos[1] -= d[1];
if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
}
var p = [0,1].map(function(i){
return (point[i]-pos[i]-this.offset[i])
}.bind(this));
if(this.options.snap) {
if(Object.isFunction(this.options.snap)) {
p = this.options.snap(p[0],p[1],this);
} else {
if(Object.isArray(this.options.snap)) {
p = p.map( function(v, i) {
return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
} else {
p = p.map( function(v) {
return (v/this.options.snap).round()*this.options.snap }.bind(this));
}
}}
var style = this.element.style;
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
style.left = p[0] + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
style.top = p[1] + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
stopScrolling: function() {
if(this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
Draggables._lastScrollPointer = null;
}
},
startScrolling: function(speed) {
if(!(speed[0] || speed[1])) return;
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
},
scroll: function() {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if(this.options.scroll == window) {
with (this._getWindowScroll(this.options.scroll)) {
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var d = delta / 1000;
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
}
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
Position.prepare();
Droppables.show(Draggables._lastPointer, this.element);
Draggables.notify('onDrag', this);
if (this._isScrollChild) {
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
if (Draggables._lastScrollPointer[0] < 0)
Draggables._lastScrollPointer[0] = 0;
if (Draggables._lastScrollPointer[1] < 0)
Draggables._lastScrollPointer[1] = 0;
this.draw(Draggables._lastScrollPointer);
}
if(this.options.change) this.options.change(this);
},
_getWindowScroll: function(w) {
var T, L, W, H;
with (w.document) {
if (w.document.documentElement && documentElement.scrollTop) {
T = documentElement.scrollTop;
L = documentElement.scrollLeft;
} else if (w.document.body) {
T = body.scrollTop;
L = body.scrollLeft;
}
if (w.innerWidth) {
W = w.innerWidth;
H = w.innerHeight;
} else if (w.document.documentElement && documentElement.clientWidth) {
W = documentElement.clientWidth;
H = documentElement.clientHeight;
} else {
W = body.offsetWidth;
H = body.offsetHeight;
}
}
return { top: T, left: L, width: W, height: H };
}
});
Draggable._dragging = { };
/*--------------------------------------------------------------------------*/
var SortableObserver = Class.create({
initialize: function(element, observer) {
this.element = $(element);
this.observer = observer;
this.lastValue = Sortable.serialize(this.element);
},
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
onEnd: function() {
Sortable.unmark();
if(this.lastValue != Sortable.serialize(this.element))
this.observer(this.element)
}
});
var Sortable = {
SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
sortables: { },
_findRootElement: function(element) {
while (element.tagName.toUpperCase() != "BODY") {
if(element.id && Sortable.sortables[element.id]) return element;
element = element.parentNode;
}
},
options: function(element) {
element = Sortable._findRootElement($(element));
if(!element) return;
return Sortable.sortables[element.id];
},
destroy: function(element){
element = $(element);
var s = Sortable.sortables[element.id];
if(s) {
Draggables.removeObserver(s.element);
s.droppables.each(function(d){ Droppables.remove(d) });
s.draggables.invoke('destroy');
delete Sortable.sortables[s.element.id];
}
},
create: function(element) {
element = $(element);
var options = Object.extend({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
dropOnEmpty: false,
tree: false,
treeTag: 'ul',
overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false
containment: element, // also takes array of elements (or id's); or false
handle: false, // or a CSS class
only: false,
delay: 0,
hoverclass: null,
ghosting: false,
quiet: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
format: this.SERIALIZE_RULE,
// these take arrays of elements or ids and can be
// used for better initialization performance
elements: false,
handles: false,
onChange: Prototype.emptyFunction,
onUpdate: Prototype.emptyFunction
}, arguments[1] || { });
// clear any old sortable with same element
this.destroy(element);
// build options for the draggables
var options_for_draggable = {
revert: true,
quiet: options.quiet,
scroll: options.scroll,
scrollSpeed: options.scrollSpeed,
scrollSensitivity: options.scrollSensitivity,
delay: options.delay,
ghosting: options.ghosting,
constraint: options.constraint,
handle: options.handle };
if(options.starteffect)
options_for_draggable.starteffect = options.starteffect;
if(options.reverteffect)
options_for_draggable.reverteffect = options.reverteffect;
else
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
element.style.top = 0;
element.style.left = 0;
};
if(options.endeffect)
options_for_draggable.endeffect = options.endeffect;
if(options.zindex)
options_for_draggable.zindex = options.zindex;
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
tree: options.tree,
hoverclass: options.hoverclass,
onHover: Sortable.onHover
};
var options_for_tree = {
onHover: Sortable.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass
};
// fix for gecko engine
Element.cleanWhitespace(element);
options.draggables = [];
options.droppables = [];
// drop on empty handling
if(options.dropOnEmpty || options.tree) {
Droppables.add(element, options_for_tree);
options.droppables.push(element);
}
(options.elements || this.findElements(element, options) || []).each( function(e,i) {
var handle = options.handles ? $(options.handles[i]) :
(options.handle ? $(e).select('.' + options.handle)[0] : e);
options.draggables.push(
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
Droppables.add(e, options_for_droppable);
if(options.tree) e.treeNode = element;
options.droppables.push(e);
});
if(options.tree) {
(Sortable.findTreeElements(element, options) || []).each( function(e) {
Droppables.add(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
});
}
// keep reference
this.sortables[element.id] = options;
// for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
},
// return all suitable-for-sortable elements in a guaranteed order
findElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.tag);
},
findTreeElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
},
onHover: function(element, dropon, overlap) {
if(Element.isParent(dropon, element)) return;
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
return;
} else if(overlap>0.5) {
Sortable.mark(dropon, 'before');
if(dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
} else {
Sortable.mark(dropon, 'after');
var nextElement = dropon.nextSibling || null;
if(nextElement != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
}
},
onEmptyHover: function(element, dropon, overlap) {
var oldParentNode = element.parentNode;
var droponOptions = Sortable.options(dropon);
if(!Element.isParent(dropon, element)) {
var index;
var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
var child = null;
if(children) {
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
offset -= Element.offsetSize (children[index], droponOptions.overlap);
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
Sortable.options(oldParentNode).onChange(element);
droponOptions.onChange(element);
}
},
unmark: function() {
if(Sortable._marker) Sortable._marker.hide();
},
mark: function(dropon, position) {
// mark on ghosting only
var sortable = Sortable.options(dropon.parentNode);
if(sortable && !sortable.ghosting) return;
if(!Sortable._marker) {
Sortable._marker =
($('dropmarker') || Element.extend(document.createElement('DIV'))).
hide().addClassName('dropmarker').setStyle({position:'absolute'});
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
}
var offsets = Position.cumulativeOffset(dropon);
Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
if(position=='after')
if(sortable.overlap == 'horizontal')
Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
else
Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
Sortable._marker.show();
},
_tree: function(element, options, parent) {
var children = Sortable.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) continue;
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: [],
position: parent.children.length,
container: $(children[i]).down(options.treeTag)
};
/* Get the element containing the children and recurse over it */
if (child.container)
this._tree(child.container, options, child);
parent.children.push (child);
}
return parent;
},
tree: function(element) {
element = $(element);
var sortableOptions = this.options(element);
var options = Object.extend({
tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only,
name: element.id,
format: sortableOptions.format
}, arguments[1] || { });
var root = {
id: null,
parent: null,
children: [],
container: element,
position: 0
};
return Sortable._tree(element, options, root);
},
/* Construct a [i] index for a particular node */
_constructIndex: function(node) {
var index = '';
do {
if (node.id) index = '[' + node.position + ']' + index;
} while ((node = node.parent) != null);
return index;
},
sequence: function(element) {
element = $(element);
var options = Object.extend(this.options(element), arguments[1] || { });
return $(this.findElements(element, options) || []).map( function(item) {
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
});
},
setSequence: function(element, new_sequence) {
element = $(element);
var options = Object.extend(this.options(element), arguments[2] || { });
var nodeMap = { };
this.findElements(element, options).each( function(n) {
if (n.id.match(options.format))
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
n.parentNode.removeChild(n);
});
new_sequence.each(function(ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
});
},
serialize: function(element) {
element = $(element);
var options = Object.extend(Sortable.options(element), arguments[1] || { });
var name = encodeURIComponent(
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
if (options.tree) {
return Sortable.tree(element, arguments[1]).children.map( function (item) {
return [name + Sortable._constructIndex(item) + "[id]=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}).flatten().join('&');
} else {
return Sortable.sequence(element, arguments[1]).map( function(item) {
return name + "[]=" + encodeURIComponent(item);
}).join('&');
}
}
};
// Returns true if child is contained within element
Element.isParent = function(child, element) {
if (!child.parentNode || child == element) return false;
if (child.parentNode == element) return true;
return Element.isParent(child.parentNode, element);
};
Element.findChildren = function(element, only, recursive, tagName) {
if(!element.hasChildNodes()) return null;
tagName = tagName.toUpperCase();
if(only) only = [only].flatten();
var elements = [];
$A(element.childNodes).each( function(e) {
if(e.tagName && e.tagName.toUpperCase()==tagName &&
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
elements.push(e);
if(recursive) {
var grandchildren = Element.findChildren(e, only, recursive, tagName);
if(grandchildren) elements.push(grandchildren);
}
});
return (elements.length>0 ? elements.flatten() : []);
};
Element.offsetSize = function (element, type) {
return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
};

1128
public/javascripts/effects.js vendored Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,9 @@
.galleria{list-style:none;width:200px}
.galleria li{display:block;width:80px;height:80px;overflow:hidden;float:left;margin:0 10px 10px 0}
.galleria li a{display:none}
.galleria li div{position:absolute;display:none;top:0;left:180px}
.galleria li div img{cursor:pointer}
.galleria li.active div img,.galleria li.active div{display:block}
.galleria li img.thumb{cursor:pointer;top:auto;left:auto;display:block;width:auto;height:auto}
.galleria li .caption{display:block;padding-top:.5em}
* html .galleria li div span{width:400px} /* MSIE bug */

View file

@ -0,0 +1,532 @@
/**
* Galleria (http://monc.se/kitchen)
*
* Galleria is a javascript image gallery written in jQuery.
* It loads the images one by one from an unordered list and displays thumbnails when each image is loaded.
* It will create thumbnails for you if you choose so, scaled or unscaled,
* centered and cropped inside a fixed thumbnail box defined by CSS.
*
* The core of Galleria lies in it's smart preloading behaviour, snappiness and the fresh absence
* of obtrusive design elements. Use it as a foundation for your custom styled image gallery.
*
* MAJOR CHANGES v.FROM 0.9
* Galleria now features a useful history extension, enabling back button and bookmarking for each image.
* The main image is no longer stored inside each list item, instead it is placed inside a container
* onImage and onThumb functions lets you customize the behaviours of the images on the site
*
* Tested in Safari 3, Firefox 2, MSIE 6, MSIE 7, Opera 9
*
* Version 1.0
* Februari 21, 2008
*
* Copyright (c) 2008 David Hellsing (http://monc.se)
* Licensed under the GPL licenses.
* http://www.gnu.org/licenses/gpl.txt
**/
(function($){
var $$;
/**
*
* @desc Convert images from a simple html <ul> into a thumbnail gallery
* @author David Hellsing
* @version 1.0
*
* @name Galleria
* @type jQuery
*
* @cat plugins/Media
*
* @example $('ul.gallery').galleria({options});
* @desc Create a a gallery from an unordered list of images with thumbnails
* @options
* insert: (selector string) by default, Galleria will create a container div before your ul that holds the image.
* You can, however, specify a selector where the image will be placed instead (f.ex '#main_img')
* history: Boolean for setting the history object in action with enabled back button, bookmarking etc.
* onImage: (function) a function that gets fired when the image is displayed and brings the jQuery image object.
* You can use it to add click functionality and effects.
* f.ex onImage(image) { image.css('display','none').fadeIn(); } will fadeIn each image that is displayed
* onThumb: (function) a function that gets fired when the thumbnail is displayed and brings the jQuery thumb object.
* Works the same as onImage except it targets the thumbnail after it's loaded.
*
**/
$$ = $.fn.galleria = function($options) {
// check for basic CSS support
if (!$$.hasCSS()) { return false; }
// init the modified history object
$.historyInit($$.onPageLoad);
// set default options
var $defaults = {
insert : '.galleria_container',
history : true,
clickNext : true,
onImage : function(image,caption,thumb) {},
onThumb : function(thumb) {}
};
// extend the options
var $opts = $.extend($defaults, $options);
// bring the options to the galleria object
for (var i in $opts) {
if (i) {
$.galleria[i] = $opts[i];
}
}
// if no insert selector, create a new division and insert it before the ul
var _insert = ( $($opts.insert).is($opts.insert) ) ?
$($opts.insert) :
jQuery(document.createElement('div')).insertBefore(this);
// create a wrapping div for the image
var _div = $(document.createElement('div')).addClass('galleria_wrapper');
// create a caption span
var _span = $(document.createElement('span')).addClass('caption');
// inject the wrapper in in the insert selector
_insert.addClass('galleria_container').append(_div).append(_span);
//-------------
return this.each(function(){
// add the Galleria class
$(this).addClass('galleria');
// loop through list
$(this).children('li').each(function(i) {
// bring the scope
var _container = $(this);
// build element specific options
var _o = $.meta ? $.extend({}, $opts, _container.data()) : $opts;
// remove the clickNext if image is only child
_o.clickNext = $(this).is(':only-child') ? false : _o.clickNext;
// try to fetch an anchor
var _a = $(this).find('a').is('a') ? $(this).find('a') : false;
// reference the original image as a variable and hide it
var _img = $(this).children('img').css('display','none');
// extract the original source
var _src = _a ? _a.attr('href') : _img.attr('src');
// find a title
var _title = _a ? _a.attr('title') : _img.attr('title');
// create loader image
var _loader = new Image();
// check url and activate container if match
if (_o.history && (window.location.hash && window.location.hash.replace(/\#/,'') == _src)) {
_container.siblings('.active').removeClass('active');
_container.addClass('active');
}
// begin loader
$(_loader).load(function () {
// try to bring the alt
$(this).attr('alt',_img.attr('alt'));
//-----------------------------------------------------------------
// the image is loaded, let's create the thumbnail
var _thumb = _a ?
_a.find('img').addClass('thumb noscale').css('display','none') :
_img.clone(true).addClass('thumb').css('display','none');
if (_a) { _a.replaceWith(_thumb); }
if (!_thumb.hasClass('noscale')) { // scaled tumbnails!
var w = Math.ceil( _img.width() / _img.height() * _container.height() );
var h = Math.ceil( _img.height() / _img.width() * _container.width() );
if (w < h) {
_thumb.css({ height: 'auto', width: _container.width(), marginTop: -(h-_container.height())/2 });
} else {
_thumb.css({ width: 'auto', height: _container.height(), marginLeft: -(w-_container.width())/2 });
}
} else { // Center thumbnails.
// a tiny timer fixed the width/height
window.setTimeout(function() {
_thumb.css({
marginLeft: -( _thumb.width() - _container.width() )/2,
marginTop: -( _thumb.height() - _container.height() )/2
});
}, 1);
}
// add the rel attribute
_thumb.attr('rel',_src);
// add the title attribute
_thumb.attr('title',_title);
// add the click functionality to the _thumb
_thumb.click(function() {
$.galleria.activate(_src);
});
// hover classes for IE6
_thumb.hover(
function() { $(this).addClass('hover'); },
function() { $(this).removeClass('hover'); }
);
_container.hover(
function() { _container.addClass('hover'); },
function() { _container.removeClass('hover'); }
);
// prepend the thumbnail in the container
_container.prepend(_thumb);
// show the thumbnail
_thumb.css('display','block');
// call the onThumb function
_o.onThumb(jQuery(_thumb));
// check active class and activate image if match
if (_container.hasClass('active')) {
$.galleria.activate(_src);
//_span.text(_title);
}
//-----------------------------------------------------------------
// finally delete the original image
_img.remove();
}).error(function () {
// Error handling
_container.html('<span class="error" style="color:red">Error loading image: '+_src+'</span>');
}).attr('src', _src);
});
});
};
/**
*
* @name NextSelector
*
* @desc Returns the sibling sibling, or the first one
*
**/
$$.nextSelector = function(selector) {
return $(selector).is(':last-child') ?
$(selector).siblings(':first-child') :
$(selector).next();
};
/**
*
* @name previousSelector
*
* @desc Returns the previous sibling, or the last one
*
**/
$$.previousSelector = function(selector) {
return $(selector).is(':first-child') ?
$(selector).siblings(':last-child') :
$(selector).prev();
};
/**
*
* @name hasCSS
*
* @desc Checks for CSS support and returns a boolean value
*
**/
$$.hasCSS = function() {
$('body').append(
$(document.createElement('div')).attr('id','css_test').css({ width:'1px', height:'1px', display:'none' })
);
var _v = ($('#css_test').width() != 1) ? false : true;
$('#css_test').remove();
return _v;
};
/**
*
* @name onPageLoad
*
* @desc The function that displays the image and alters the active classes
*
* Note: This function gets called when:
* 1. after calling $.historyInit();
* 2. after calling $.historyLoad();
* 3. after pushing "Go Back" button of a browser
*
**/
$$.onPageLoad = function(_src) {
// get the wrapper
var _wrapper = $('.galleria_wrapper');
// get the thumb
var _thumb = $('.galleria img[rel="'+_src+'"]');
if (_src) {
// new hash location
if ($.galleria.history) {
window.location = window.location.href.replace(/\#.*/,'') + '#' + _src;
}
// alter the active classes
_thumb.parents('li').siblings('.active').removeClass('active');
_thumb.parents('li').addClass('active');
// define a new image
var _img = $(new Image()).attr('src',_src).addClass('replaced');
// empty the wrapper and insert the new image
_wrapper.empty().append(_img);
// insert the caption
_wrapper.siblings('.caption').text(_thumb.attr('title'));
// fire the onImage function to customize the loaded image's features
$.galleria.onImage(_img,_wrapper.siblings('.caption'),_thumb);
// add clickable image helper
if($.galleria.clickNext) {
_img.css('cursor','pointer').click(function() { $.galleria.next(); });
}
} else {
// clean up the container if none are active
_wrapper.siblings().andSelf().empty();
// remove active classes
$('.galleria li.active').removeClass('active');
}
// place the source in the galleria.current variable
$.galleria.current = _src;
};
/**
*
* @name jQuery.galleria
*
* @desc The global galleria object holds four constant variables and four public methods:
* $.galleria.history = a boolean for setting the history object in action with named URLs
* $.galleria.current = is the current source that's being viewed.
* $.galleria.clickNext = boolean helper for adding a clickable image that leads to the next one in line
* $.galleria.next() = displays the next image in line, returns to first image after the last.
* $.galleria.prev() = displays the previous image in line, returns to last image after the first.
* $.galleria.activate(_src) = displays an image from _src in the galleria container.
* $.galleria.onImage(image,caption) = gets fired when the image is displayed.
*
**/
$.extend({galleria : {
current : '',
onImage : function(){},
activate : function(_src) {
if ($.galleria.history) {
$.historyLoad(_src);
} else {
$$.onPageLoad(_src);
}
},
next : function() {
var _next = $($$.nextSelector($('.galleria img[rel="'+$.galleria.current+'"]').parents('li'))).find('img').attr('rel');
$.galleria.activate(_next);
},
prev : function() {
var _prev = $($$.previousSelector($('.galleria img[rel="'+$.galleria.current+'"]').parents('li'))).find('img').attr('rel');
$.galleria.activate(_prev);
}
}
});
})(jQuery);
/**
*
* History extension for jQuery
* Credits to http://www.mikage.to/
*
**/
/*
* jQuery history plugin
*
* Copyright (c) 2006 Taku Sano (Mikage Sawatari)
* Licensed under the MIT License:
* http://www.opensource.org/licenses/mit-license.php
*
* Modified by Lincoln Cooper to add Safari support and only call the callback once during initialization
* for msie when no initial hash supplied.
*/
jQuery.extend({
historyCurrentHash: undefined,
historyCallback: undefined,
historyInit: function(callback){
jQuery.historyCallback = callback;
var current_hash = location.hash;
jQuery.historyCurrentHash = current_hash;
if(jQuery.browser.msie) {
// To stop the callback firing twice during initilization if no hash present
if (jQuery.historyCurrentHash === '') {
jQuery.historyCurrentHash = '#';
}
// add hidden iframe for IE
$("body").prepend('<iframe id="jQuery_history" style="display: none;"></iframe>');
var ihistory = $("#jQuery_history")[0];
var iframe = ihistory.contentWindow.document;
iframe.open();
iframe.close();
iframe.location.hash = current_hash;
}
else if ($.browser.safari) {
// etablish back/forward stacks
jQuery.historyBackStack = [];
jQuery.historyBackStack.length = history.length;
jQuery.historyForwardStack = [];
jQuery.isFirst = true;
}
jQuery.historyCallback(current_hash.replace(/^#/, ''));
setInterval(jQuery.historyCheck, 100);
},
historyAddHistory: function(hash) {
// This makes the looping function do something
jQuery.historyBackStack.push(hash);
jQuery.historyForwardStack.length = 0; // clear forwardStack (true click occured)
this.isFirst = true;
},
historyCheck: function(){
if(jQuery.browser.msie) {
// On IE, check for location.hash of iframe
var ihistory = $("#jQuery_history")[0];
var iframe = ihistory.contentDocument || ihistory.contentWindow.document;
var current_hash = iframe.location.hash;
if(current_hash != jQuery.historyCurrentHash) {
location.hash = current_hash;
jQuery.historyCurrentHash = current_hash;
jQuery.historyCallback(current_hash.replace(/^#/, ''));
}
} else if ($.browser.safari) {
if (!jQuery.dontCheck) {
var historyDelta = history.length - jQuery.historyBackStack.length;
if (historyDelta) { // back or forward button has been pushed
jQuery.isFirst = false;
var i;
if (historyDelta < 0) { // back button has been pushed
// move items to forward stack
for (i = 0; i < Math.abs(historyDelta); i++) {
jQuery.historyForwardStack.unshift(jQuery.historyBackStack.pop());
}
} else { // forward button has been pushed
// move items to back stack
for (i = 0; i < historyDelta; i++) {
jQuery.historyBackStack.push(jQuery.historyForwardStack.shift());
}
}
var cachedHash = jQuery.historyBackStack[jQuery.historyBackStack.length - 1];
if (cachedHash !== undefined) {
jQuery.historyCurrentHash = location.hash;
jQuery.historyCallback(cachedHash);
}
} else if (jQuery.historyBackStack[jQuery.historyBackStack.length - 1] === undefined && !jQuery.isFirst) {
// back button has been pushed to beginning and URL already pointed to hash (e.g. a bookmark)
// document.URL doesn't change in Safari
if (document.URL.indexOf('#') >= 0) {
jQuery.historyCallback(document.URL.split('#')[1]);
} else {
current_hash = location.hash;
jQuery.historyCallback('');
}
jQuery.isFirst = true;
}
}
} else {
// otherwise, check for location.hash
current_hash = location.hash;
if(current_hash != jQuery.historyCurrentHash) {
jQuery.historyCurrentHash = current_hash;
jQuery.historyCallback(current_hash.replace(/^#/, ''));
}
}
},
historyLoad: function(hash){
var newhash;
if (jQuery.browser.safari) {
newhash = hash;
}
else {
newhash = '#' + hash;
location.hash = newhash;
}
jQuery.historyCurrentHash = newhash;
if(jQuery.browser.msie) {
var ihistory = $("#jQuery_history")[0];
var iframe = ihistory.contentWindow.document;
iframe.open();
iframe.close();
iframe.location.hash = newhash;
jQuery.historyCallback(hash);
}
else if (jQuery.browser.safari) {
jQuery.dontCheck = true;
// Manually keep track of the history values for Safari
this.historyAddHistory(hash);
// Wait a while before allowing checking so that Safari has time to update the "history" object
// correctly (otherwise the check loop would detect a false change in hash).
var fn = function() {jQuery.dontCheck = false;};
window.setTimeout(fn, 200);
jQuery.historyCallback(hash);
// N.B. "location.hash=" must be the last line of code for Safari as execution stops afterwards.
// By explicitly using the "location.hash" command (instead of using a variable set to "location.hash") the
// URL in the browser and the "history" object are both updated correctly.
location.hash = newhash;
}
else {
jQuery.historyCallback(hash);
}
}
});

4376
public/javascripts/jquery-1.3.2.js vendored Normal file

File diff suppressed because it is too large Load diff

19
public/javascripts/jquery-1.3.2.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,73 @@
GalleryView - jQuery Content Gallery Plugin
Author: Jack Anderson
Documentation: http://www.spaceforaname.com/galleryview/
Version 2.0 - 2009-05-05
----------------------------------------
Revised required HTML markup for GalleryView
Filmstrip frames are generated automatically (no need for two sets of images)
Moved majority of aesthetic styling to external CSS
Default aesthetics for gallery updated
Set GalleryView to wait until images finish loading before building itself
Added 'current' class to current filmstrip frame for user customization
Allowed for more user-defined styling in panels and filmstrip frames
Filmstrip can now be placed on left and right side of gallery, in addition to top and bottom
Panel images can now be scaled to fit within panel or zoomed & cropped to fill panel
Filmstrip images can now be scaled to fit within frame or zoomed & cropped to fill frame
Panel overlay backgrounds are now only added to those panels with overlays
'pause_on_hover' option now pauses when mouse hovers over filmstrip as well as panel
Added option to start gallery on any frame
Current frame pointer is now built with CSS and its size can be customized easily
Increased size of hitbox on panel-navigation buttons
Updated options:
- filmstrip_position
- new allowable values : 'top', 'right', 'bottom', 'left'
Removed options:
- overlay_height
- overlay_font_size
- overlay_color
- background_color
- overlay_text_color
- caption_text_color
- border
New options:
- show_panels (boolean)
- show_filmstrip (boolean)
- start_frame (integer)
- pointer_size (integer)
- panel_scale
- allowable values : 'crop', 'nocrop'
- frame_scale
- allowable values : 'crop', 'nocrop'
- frame_gap (integer)
Version 1.1 - 2009-04-05
----------------------------------------
Added feature allowing filmstrip w/o panels
Added feature allowing panels w/o filmstrip
Added feature allowing filmstrip placement above or below panels
Added new graphics for panel navigation
Anchored navigation buttons to edges of filmstrip, rather than gallery boundaries
New options:
- filmstrip_position
- allowable values : 'top', 'bottom'
- overlay_position
- allowable values : 'top', 'bottom'
Version 1.0.1 - 2009-03-30
----------------------------------------
Fixed bug allowing blank frames to display in filmstrip
Changed pointer graphic from GIF to PNG to allow for greater customization
Added 'overflow: hidden' to panels to prevent oversized content from breaking gallery
Disabled frame clicking during filmstrip animation
Changed pointer border from 4px to 2px
Version 1.0
----------------------------------------
2009-03-29 : Initial Release

View file

@ -0,0 +1,19 @@
Copyright (c) 2009 John Anderson III
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.

View file

@ -0,0 +1,88 @@
/*
GalleryView Stylesheet
Use the CSS rules below to modify the look of your gallery.
To create additional rules, use the markup below as a guide to GalleryView's architecture.
<div class="gallery">
<div class="panel">
<img src="path/to/image.jpg" />
<div class="panel-overlay">
...overlay content...
</div>
<div class="overlay-background"></div>
</div>
<ul class="filmstrip">
<li class="frame current">
<img src="path/to/thumbnail.jpg" />
<div class="caption">caption text</div>
</li>
<li class="frame">
<img src="path/to/thumbnail.jpg" />
<div class="caption">caption text</div>
</li>
</ul>
</div>
*/
/* GALLERY LIST */
/* IMPORTANT - Change '#photos' to the ID of your gallery list to prevent a flash of unstyled content */
#photos { visibility: hidden; }
/* GALLERY CONTAINER */
.gallery { background: #ddd; border: 1px solid #aaa; padding: 5px;}
/* LOADING BOX */
.loader { background: url(loader.gif) center center no-repeat #ddd; }
/* GALLERY PANELS */
.panel {}
/* DEFINE HEIGHT OF PANEL OVERLAY */
/* NOTE - It is best to define padding here as well so overlay and background retain identical dimensions */
.panel .panel-overlay,
.panel .overlay-background { height: 60px; padding: 0 1em; }
/* PANEL OVERLAY BACKGROUND */
.panel .overlay-background { background: #222; }
/* PANEL OVERLAY CONTENT */
.panel .panel-overlay { color: white; font-size: 0.7em; }
.panel .panel-overlay a { color: white; text-decoration: underline; font-weight: bold; }
/* FILMSTRIP */
/* 'margin' will define top/bottom margin in completed gallery */
.filmstrip { margin: 5px; }
/* FILMSTRIP FRAMES (contains both images and captions) */
.frame {}
/* WRAPPER FOR FILMSTRIP IMAGES */
.frame .img_wrap { border: 1px solid #aaa; }
/* WRAPPER FOR CURRENT FILMSTRIP IMAGE */
.frame.current .img_wrap { border-color: #000; }
/* FRAME IMAGES */
.frame img { border: none; }
/* FRAME CAPTION */
.frame .caption { font-size: 11px; text-align: center; color: #888; }
/* CURRENT FRAME CAPTION */
.frame.current .caption { color: #000; }
/* POINTER FOR CURRENT FRAME */
.pointer {
border-color: #000;
}
/* TRANSPARENT BORDER FIX FOR IE6 */
/* NOTE - DO NOT CHANGE THIS RULE */
*html .pointer {
filter: chroma(color=pink);
}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,968 @@
/*
GalleryView - jQuery Content Gallery Plugin
Author: Jack Anderson
Version: 2.0 (May 5, 2009)
Documentation: http://www.spaceforaname.com/galleryview/
Please use this development script if you intend to make changes to the
plugin code. For production sites, please use jquery.galleryview-2.0-pack.js.
See CHANGELOG.txt for a review of changes and LICENSE.txt for the applicable
licensing information.
*/
//Global variable to check if window is already loaded
//Used for calling GalleryView after page has loaded
var window_loaded = false;
(function($){
$.fn.galleryView = function(options) {
var opts = $.extend($.fn.galleryView.defaults,options);
var id;
var iterator = 0;
var item_count = 0;
var slide_method;
var theme_path;
var paused = false;
//Element dimensions
var gallery_width;
var gallery_height;
var pointer_height;
var pointer_width;
var strip_width;
var strip_height;
var wrapper_width;
var f_frame_width;
var f_frame_height;
var frame_caption_size = 20;
var gallery_padding;
var filmstrip_margin;
var filmstrip_orientation;
//Arrays used to scale frames and panels
var frame_img_scale = new Object();
var panel_img_scale = new Object();
var img_h = new Object();
var img_w = new Object();
//Flag indicating whether to scale panel images
var scale_panel_images = true;
var panel_nav_displayed = false;
//Define jQuery objects for reuse
var j_gallery;
var j_filmstrip;
var j_frames;
var j_frame_img_wrappers;
var j_panels;
var j_pointer;
/************************************************/
/* Plugin Methods */
/************************************************/
//Transition from current item to item 'i'
function showItem(i) {
//Disable next/prev buttons until transition is complete
$('.nav-next-overlay',j_gallery).unbind('click');
$('.nav-prev-overlay',j_gallery).unbind('click');
$('.nav-next',j_gallery).unbind('click');
$('.nav-prev',j_gallery).unbind('click');
j_frames.unbind('click');
//Fade out all frames while fading in target frame
if(opts.show_filmstrip) {
j_frames.removeClass('current').find('img').stop().animate({
'opacity':opts.frame_opacity
},opts.transition_speed);
j_frames.eq(i).addClass('current').find('img').stop().animate({
'opacity':1.0
},opts.transition_speed);
}
//If the gallery has panels and the panels should fade, fade out all panels while fading in target panel
if(opts.show_panels && opts.fade_panels) {
j_panels.fadeOut(opts.transition_speed).eq(i%item_count).fadeIn(opts.transition_speed,function(){
if(!opts.show_filmstrip) {
$('.nav-prev-overlay',j_gallery).click(showPrevItem);
$('.nav-next-overlay',j_gallery).click(showNextItem);
$('.nav-prev',j_gallery).click(showPrevItem);
$('.nav-next',j_gallery).click(showNextItem);
}
});
}
//If gallery has a filmstrip, handle animation of frames
if(opts.show_filmstrip) {
//Slide either pointer or filmstrip, depending on transition method
if(slide_method=='strip') {
//Stop filmstrip if it's currently in motion
j_filmstrip.stop();
if(filmstrip_orientation=='horizontal') {
//Determine distance between pointer (eventual destination) and target frame
var distance = getPos(j_frames[i]).left - (getPos(j_pointer[0]).left+(pointer_width/2)-(f_frame_width/2));
var diststr = (distance>=0?'-=':'+=')+Math.abs(distance)+'px';
//Animate filmstrip and slide target frame under pointer
j_filmstrip.animate({
'left':diststr
},opts.transition_speed,opts.easing,function(){
//Always ensure that there are a sufficient number of hidden frames on either
//side of the filmstrip to avoid empty frames
var old_i = i;
if(i>item_count) {
i = i%item_count;
iterator = i;
j_filmstrip.css('left','-'+((f_frame_width+opts.frame_gap)*i)+'px');
} else if (i<=(item_count-strip_size)) {
i = (i%item_count)+item_count;
iterator = i;
j_filmstrip.css('left','-'+((f_frame_width+opts.frame_gap)*i)+'px');
}
//If the target frame has changed due to filmstrip shifting,
//Make sure new target frame has 'current' class and correct size/opacity settings
if(old_i != i) {
j_frames.eq(old_i).removeClass('current').find('img').css({
'opacity':opts.frame_opacity
});
j_frames.eq(i).addClass('current').find('img').css({
'opacity':1.0
});
}
if(!opts.fade_panels) {
j_panels.hide().eq(i%item_count).show();
}
//Enable navigation now that animation is complete
$('.nav-prev-overlay',j_gallery).click(showPrevItem);
$('.nav-next-overlay',j_gallery).click(showNextItem);
$('.nav-prev',j_gallery).click(showPrevItem);
$('.nav-next',j_gallery).click(showNextItem);
enableFrameClicking();
});
} else {
//Determine distance between pointer (eventual destination) and target frame
var distance = getPos(j_frames[i]).top - (getPos(j_pointer[0]).top+(pointer_height)-(f_frame_height/2));
var diststr = (distance>=0?'-=':'+=')+Math.abs(distance)+'px';
//Animate filmstrip and slide target frame under pointer
j_filmstrip.animate({
'top':diststr
},opts.transition_speed,opts.easing,function(){
//Always ensure that there are a sufficient number of hidden frames on either
//side of the filmstrip to avoid empty frames
var old_i = i;
if(i>item_count) {
i = i%item_count;
iterator = i;
j_filmstrip.css('top','-'+((f_frame_height+opts.frame_gap)*i)+'px');
} else if (i<=(item_count-strip_size)) {
i = (i%item_count)+item_count;
iterator = i;
j_filmstrip.css('top','-'+((f_frame_height+opts.frame_gap)*i)+'px');
}
//If the target frame has changed due to filmstrip shifting,
//Make sure new target frame has 'current' class and correct size/opacity settings
if(old_i != i) {
j_frames.eq(old_i).removeClass('current').find('img').css({
'opacity':opts.frame_opacity
});
j_frames.eq(i).addClass('current').find('img').css({
'opacity':1.0
});
}
if(!opts.fade_panels) {
j_panels.hide().eq(i%item_count).show();
}
//Enable navigation now that animation is complete
$('.nav-prev-overlay',j_gallery).click(showPrevItem);
$('.nav-next-overlay',j_gallery).click(showNextItem);
$('.nav-prev',j_gallery).click(showPrevItem);
$('.nav-next',j_gallery).click(showNextItem);
enableFrameClicking();
});
}
} else if(slide_method=='pointer') {
//Stop pointer if it's currently in motion
j_pointer.stop();
//Get position of target frame
var pos = getPos(j_frames[i]);
if(filmstrip_orientation=='horizontal') {
//Slide the pointer over the target frame
j_pointer.animate({
'left':(pos.left+(f_frame_width/2)-(pointer_width/2)+'px')
},opts.transition_speed,opts.easing,function(){
if(!opts.fade_panels) {
j_panels.hide().eq(i%item_count).show();
}
$('.nav-prev-overlay',j_gallery).click(showPrevItem);
$('.nav-next-overlay',j_gallery).click(showNextItem);
$('.nav-prev',j_gallery).click(showPrevItem);
$('.nav-next',j_gallery).click(showNextItem);
enableFrameClicking();
});
} else {//Slide the pointer over the target frame
j_pointer.animate({
'top':(pos.top+(f_frame_height/2)-(pointer_height)+'px')
},opts.transition_speed,opts.easing,function(){
if(!opts.fade_panels) {
j_panels.hide().eq(i%item_count).show();
}
$('.nav-prev-overlay',j_gallery).click(showPrevItem);
$('.nav-next-overlay',j_gallery).click(showNextItem);
$('.nav-prev',j_gallery).click(showPrevItem);
$('.nav-next',j_gallery).click(showNextItem);
enableFrameClicking();
});
}
}
}
};
//Find padding and border widths applied to element
//If border is non-numerical ('thin','medium', etc) set to zero
function extraWidth(el) {
if(!el) return 0;
if(el.length==0) return 0;
el = el.eq(0);
var ew = 0;
ew += getInt(el.css('paddingLeft'));
ew += getInt(el.css('paddingRight'));
ew += getInt(el.css('borderLeftWidth'));
ew += getInt(el.css('borderRightWidth'));
return ew;
}
//Find padding and border heights applied to element
//If border is non-numerical ('thin','medium', etc) set to zero
function extraHeight(el) {
if(!el) return 0;
if(el.length==0) return 0;
el = el.eq(0);
var eh = 0;
eh += getInt(el.css('paddingTop'));
eh += getInt(el.css('paddingBottom'));
eh += getInt(el.css('borderTopWidth'));
eh += getInt(el.css('borderBottomWidth'));
return eh;
}
//Halt transition timer, move to next item, restart timer
function showNextItem() {
$(document).stopTime("transition");
if(++iterator==j_frames.length) {iterator=0;}
showItem(iterator);
if(!paused) {
$(document).everyTime(opts.transition_interval,"transition",function(){
showNextItem();
});
}
};
//Halt transition timer, move to previous item, restart timer
function showPrevItem() {
$(document).stopTime("transition");
if(--iterator<0) {iterator = item_count-1;}
showItem(iterator);
if(!paused) {
$(document).everyTime(opts.transition_interval,"transition",function(){
showNextItem();
});
}
};
//Get absolute position of element in relation to top-left corner of gallery
//If el=gallery, return position of gallery within browser viewport
function getPos(el) {
var left = 0, top = 0;
var el_id = el.id;
if(el.offsetParent) {
do {
left += el.offsetLeft;
top += el.offsetTop;
} while(el = el.offsetParent);
}
//If we want the position of the gallery itself, return it
if(el_id == id) {return {'left':left,'top':top};}
//Otherwise, get position of element relative to gallery
else {
var gPos = getPos(j_gallery[0]);
var gLeft = gPos.left;
var gTop = gPos.top;
return {'left':left-gLeft,'top':top-gTop};
}
};
//Add onclick event to each frame
function enableFrameClicking() {
j_frames.each(function(i){
//If there isn't a link in this frame, set up frame to slide on click
//Frames with links will handle themselves
if($('a',this).length==0) {
$(this).click(function(){
if(iterator!=i) {
$(document).stopTime("transition");
showItem(i);
iterator = i;
if(!paused) {
$(document).everyTime(opts.transition_interval,"transition",function(){
showNextItem();
});
}
}
});
}
});
};
//Construct gallery panels from '.panel' <div>s
function buildPanels() {
//If there are panel captions, add overlay divs
j_panels.each(function(i){
if($('.panel-overlay',this).length>0) {
$(this).append('<div class="overlay-background"></div>');
}
});
if(!opts.show_filmstrip) {
//Add navigation buttons
$('<img />').addClass('nav-next').attr('src',theme_path+opts.nav_theme+'/next.gif').appendTo(j_gallery).css({
'position':'absolute',
'zIndex':'1100',
'cursor':'pointer',
'top':((opts.panel_height-22)/2)+gallery_padding+'px',
'right':'10px',
'display':'none'
}).click(showNextItem);
$('<img />').addClass('nav-prev').attr('src',theme_path+opts.nav_theme+'/prev.gif').appendTo(j_gallery).css({
'position':'absolute',
'zIndex':'1100',
'cursor':'pointer',
'top':((opts.panel_height-22)/2)+gallery_padding+'px',
'left':'10px',
'display':'none'
}).click(showPrevItem);
$('<img />').addClass('nav-next-overlay').attr('src',theme_path+opts.nav_theme+'/panel-nav-next.gif').appendTo(j_gallery).css({
'position':'absolute',
'zIndex':'1099',
'top':((opts.panel_height-22)/2)+gallery_padding-10+'px',
'right':'0',
'display':'none',
'cursor':'pointer',
'opacity':0.75
}).click(showNextItem);
$('<img />').addClass('nav-prev-overlay').attr('src',theme_path+opts.nav_theme+'/panel-nav-prev.gif').appendTo(j_gallery).css({
'position':'absolute',
'zIndex':'1099',
'top':((opts.panel_height-22)/2)+gallery_padding-10+'px',
'left':'0',
'display':'none',
'cursor':'pointer',
'opacity':0.75
}).click(showPrevItem);
}
j_panels.each(function(i){
$(this).css({
'width':(opts.panel_width-extraWidth(j_panels))+'px',
'height':(opts.panel_height-extraHeight(j_panels))+'px',
'position':'absolute',
'overflow':'hidden',
'display':'none'
});
switch(opts.filmstrip_position) {
case 'top': $(this).css({
'top':strip_height+Math.max(gallery_padding,filmstrip_margin)+'px',
'left':gallery_padding+'px'
}); break;
case 'left': $(this).css({
'top':gallery_padding+'px',
'left':strip_width+Math.max(gallery_padding,filmstrip_margin)+'px'
}); break;
default: $(this).css({'top':gallery_padding+'px','left':gallery_padding+'px'}); break;
}
});
$('.panel-overlay',j_panels).css({
'position':'absolute',
'zIndex':'999',
'width':(opts.panel_width-extraWidth($('.panel-overlay',j_panels)))+'px',
'left':'0'
});
$('.overlay-background',j_panels).css({
'position':'absolute',
'zIndex':'998',
'width':opts.panel_width+'px',
'left':'0',
'opacity':opts.overlay_opacity
});
if(opts.overlay_position=='top') {
$('.panel-overlay',j_panels).css('top',0);
$('.overlay-background',j_panels).css('top',0);
} else {
$('.panel-overlay',j_panels).css('bottom',0);
$('.overlay-background',j_panels).css('bottom',0);
}
$('.panel iframe',j_panels).css({
'width':opts.panel_width+'px',
'height':opts.panel_height+'px',
'border':'0'
});
if(scale_panel_images) {
$('img',j_panels).each(function(i){
$(this).css({
'height':panel_img_scale[i%item_count]*img_h[i%item_count],
'width':panel_img_scale[i%item_count]*img_w[i%item_count],
'position':'relative',
'top':(opts.panel_height-(panel_img_scale[i%item_count]*img_h[i%item_count]))/2+'px',
'left':(opts.panel_width-(panel_img_scale[i%item_count]*img_w[i%item_count]))/2+'px'
});
});
}
};
//Construct filmstrip from '.filmstrip' <ul>
function buildFilmstrip() {
//Add wrapper to filmstrip to hide extra frames
j_filmstrip.wrap('<div class="strip_wrapper"></div>');
if(slide_method=='strip') {
j_frames.clone().appendTo(j_filmstrip);
j_frames.clone().appendTo(j_filmstrip);
j_frames = $('li',j_filmstrip);
}
//If captions are enabled, add caption divs and fill with the image titles
if(opts.show_captions) {
j_frames.append('<div class="caption"></div>').each(function(i){
$(this).find('.caption').html($(this).find('img').attr('title'));
//$(this).find('.caption').html(i);
});
}
j_filmstrip.css({
'listStyle':'none',
'margin':'0',
'padding':'0',
'width':strip_width+'px',
'position':'absolute',
'zIndex':'900',
'top':(filmstrip_orientation=='vertical' && slide_method=='strip'?-((f_frame_height+opts.frame_gap)*iterator):0)+'px',
'left':(filmstrip_orientation=='horizontal' && slide_method=='strip'?-((f_frame_width+opts.frame_gap)*iterator):0)+'px',
'height':strip_height+'px'
});
j_frames.css({
'float':'left',
'position':'relative',
'height':f_frame_height+(opts.show_captions?frame_caption_size:0)+'px',
'width':f_frame_width+'px',
'zIndex':'901',
'padding':'0',
'cursor':'pointer'
});
switch(opts.filmstrip_position) {
case 'top': j_frames.css({
'marginBottom':filmstrip_margin+'px',
'marginRight':opts.frame_gap+'px'
}); break;
case 'bottom': j_frames.css({
'marginTop':filmstrip_margin+'px',
'marginRight':opts.frame_gap+'px'
}); break;
case 'left': j_frames.css({
'marginRight':filmstrip_margin+'px',
'marginBottom':opts.frame_gap+'px'
}); break;
case 'right': j_frames.css({
'marginLeft':filmstrip_margin+'px',
'marginBottom':opts.frame_gap+'px'
}); break;
}
$('.img_wrap',j_frames).each(function(i){
$(this).css({
'height':Math.min(opts.frame_height,img_h[i%item_count]*frame_img_scale[i%item_count])+'px',
'width':Math.min(opts.frame_width,img_w[i%item_count]*frame_img_scale[i%item_count])+'px',
'position':'relative',
'top':(opts.show_captions && opts.filmstrip_position=='top'?frame_caption_size:0)+Math.max(0,(opts.frame_height-(frame_img_scale[i%item_count]*img_h[i%item_count]))/2)+'px',
'left':Math.max(0,(opts.frame_width-(frame_img_scale[i%item_count]*img_w[i%item_count]))/2)+'px',
'overflow':'hidden'
});
});
$('img',j_frames).each(function(i){
$(this).css({
'opacity':opts.frame_opacity,
'height':img_h[i%item_count]*frame_img_scale[i%item_count]+'px',
'width':img_w[i%item_count]*frame_img_scale[i%item_count]+'px',
'position':'relative',
'top':Math.min(0,(opts.frame_height-(frame_img_scale[i%item_count]*img_h[i%item_count]))/2)+'px',
'left':Math.min(0,(opts.frame_width-(frame_img_scale[i%item_count]*img_w[i%item_count]))/2)+'px'
}).mouseover(function(){
$(this).stop().animate({'opacity':1.0},300);
}).mouseout(function(){
//Don't fade out current frame on mouseout
if(!$(this).parent().parent().hasClass('current')) $(this).stop().animate({'opacity':opts.frame_opacity},300);
});
});
$('.strip_wrapper',j_gallery).css({
'position':'absolute',
'overflow':'hidden'
});
if(filmstrip_orientation=='horizontal') {
$('.strip_wrapper',j_gallery).css({
'top':(opts.filmstrip_position=='top'?Math.max(gallery_padding,filmstrip_margin)+'px':opts.panel_height+gallery_padding+'px'),
'left':((gallery_width-wrapper_width)/2)+gallery_padding+'px',
'width':wrapper_width+'px',
'height':strip_height+'px'
});
} else {
$('.strip_wrapper',j_gallery).css({
'left':(opts.filmstrip_position=='left'?Math.max(gallery_padding,filmstrip_margin)+'px':opts.panel_width+gallery_padding+'px'),
'top':Math.max(gallery_padding,opts.frame_gap)+'px',
'width':strip_width+'px',
'height':wrapper_height+'px'
});
}
$('.caption',j_gallery).css({
'position':'absolute',
'top':(opts.filmstrip_position=='bottom'?f_frame_height:0)+'px',
'left':'0',
'margin':'0',
'width':f_frame_width+'px',
'padding':'0',
'height':frame_caption_size+'px',
'overflow':'hidden',
'lineHeight':frame_caption_size+'px'
});
var pointer = $('<div></div>');
pointer.addClass('pointer').appendTo(j_gallery).css({
'position':'absolute',
'zIndex':'1000',
'width':'0px',
'fontSize':'0px',
'lineHeight':'0%',
'borderTopWidth':pointer_height+'px',
'borderRightWidth':(pointer_width/2)+'px',
'borderBottomWidth':pointer_height+'px',
'borderLeftWidth':(pointer_width/2)+'px',
'borderStyle':'solid'
});
//For IE6, use predefined color string in place of transparent (see stylesheet)
var transColor = $.browser.msie && $.browser.version.substr(0,1)=='6' ? 'pink' : 'transparent'
if(!opts.show_panels) { pointer.css('borderColor',transColor); }
switch(opts.filmstrip_position) {
case 'top': pointer.css({
'bottom':(opts.panel_height-(pointer_height*2)+gallery_padding+filmstrip_margin)+'px',
'left':((gallery_width-wrapper_width)/2)+(slide_method=='strip'?0:((f_frame_width+opts.frame_gap)*iterator))+((f_frame_width/2)-(pointer_width/2))+gallery_padding+'px',
'borderBottomColor':transColor,
'borderRightColor':transColor,
'borderLeftColor':transColor
}); break;
case 'bottom': pointer.css({
'top':(opts.panel_height-(pointer_height*2)+gallery_padding+filmstrip_margin)+'px',
'left':((gallery_width-wrapper_width)/2)+(slide_method=='strip'?0:((f_frame_width+opts.frame_gap)*iterator))+((f_frame_width/2)-(pointer_width/2))+gallery_padding+'px',
'borderTopColor':transColor,
'borderRightColor':transColor,
'borderLeftColor':transColor
}); break;
case 'left': pointer.css({
'right':(opts.panel_width-pointer_width+gallery_padding+filmstrip_margin)+'px',
'top':(f_frame_height/2)-(pointer_height)+(slide_method=='strip'?0:((f_frame_height+opts.frame_gap)*iterator))+gallery_padding+'px',
'borderBottomColor':transColor,
'borderRightColor':transColor,
'borderTopColor':transColor
}); break;
case 'right': pointer.css({
'left':(opts.panel_width-pointer_width+gallery_padding+filmstrip_margin)+'px',
'top':(f_frame_height/2)-(pointer_height)+(slide_method=='strip'?0:((f_frame_height+opts.frame_gap)*iterator))+gallery_padding+'px',
'borderBottomColor':transColor,
'borderLeftColor':transColor,
'borderTopColor':transColor
}); break;
}
j_pointer = $('.pointer',j_gallery);
//Add navigation buttons
var navNext = $('<img />');
navNext.addClass('nav-next').attr('src',theme_path+opts.nav_theme+'/next.gif').appendTo(j_gallery).css({
'position':'absolute',
'cursor':'pointer'
}).click(showNextItem);
var navPrev = $('<img />');
navPrev.addClass('nav-prev').attr('src',theme_path+opts.nav_theme+'/prev.gif').appendTo(j_gallery).css({
'position':'absolute',
'cursor':'pointer'
}).click(showPrevItem);
if(filmstrip_orientation=='horizontal') {
navNext.css({
'top':(opts.filmstrip_position=='top'?Math.max(gallery_padding,filmstrip_margin):opts.panel_height+filmstrip_margin+gallery_padding)+((f_frame_height-22)/2)+'px',
'right':((gallery_width+(gallery_padding*2))/2)-(wrapper_width/2)-opts.frame_gap-22+'px'
});
navPrev.css({
'top':(opts.filmstrip_position=='top'?Math.max(gallery_padding,filmstrip_margin):opts.panel_height+filmstrip_margin+gallery_padding)+((f_frame_height-22)/2)+'px',
'left':((gallery_width+(gallery_padding*2))/2)-(wrapper_width/2)-opts.frame_gap-22+'px'
});
} else {
navNext.css({
'left':(opts.filmstrip_position=='left'?Math.max(gallery_padding,filmstrip_margin):opts.panel_width+filmstrip_margin+gallery_padding)+((f_frame_width-22)/2)+13+'px',
'top':wrapper_height+(Math.max(gallery_padding,opts.frame_gap)*2)+'px'
});
navPrev.css({
'left':(opts.filmstrip_position=='left'?Math.max(gallery_padding,filmstrip_margin):opts.panel_width+filmstrip_margin+gallery_padding)+((f_frame_width-22)/2)-13+'px',
'top':wrapper_height+(Math.max(gallery_padding,opts.frame_gap)*2)+'px'
});
}
};
//Check mouse to see if it is within the borders of the panel
//More reliable than 'mouseover' event when elements overlay the panel
function mouseIsOverGallery(x,y) {
var pos = getPos(j_gallery[0]);
var top = pos.top;
var left = pos.left;
return x > left && x < left+gallery_width+(filmstrip_orientation=='horizontal'?(gallery_padding*2):gallery_padding+Math.max(gallery_padding,filmstrip_margin)) && y > top && y < top+gallery_height+(filmstrip_orientation=='vertical'?(gallery_padding*2):gallery_padding+Math.max(gallery_padding,filmstrip_margin));
};
function getInt(i) {
i = parseInt(i,10);
if(isNaN(i)) { i = 0; }
return i;
}
function buildGallery() {
var gallery_images = opts.show_filmstrip?$('img',j_frames):$('img',j_panels);
gallery_images.each(function(i){
img_h[i] = this.height;
img_w[i] = this.width;
if(opts.frame_scale=='nocrop') {
frame_img_scale[i] = Math.min(opts.frame_height/img_h[i],opts.frame_width/img_w[i]);
} else {
frame_img_scale[i] = Math.max(opts.frame_height/img_h[i],opts.frame_width/img_w[i]);
}
if(opts.panel_scale=='nocrop') {
panel_img_scale[i] = Math.min(opts.panel_height/img_h[i],opts.panel_width/img_w[i]);
} else {
panel_img_scale[i] = Math.max(opts.panel_height/img_h[i],opts.panel_width/img_w[i]);
}
});
/************************************************/
/* Apply CSS Styles */
/************************************************/
j_gallery.css({
'position':'relative',
'width':gallery_width+(filmstrip_orientation=='horizontal'?(gallery_padding*2):gallery_padding+Math.max(gallery_padding,filmstrip_margin))+'px',
'height':gallery_height+(filmstrip_orientation=='vertical'?(gallery_padding*2):gallery_padding+Math.max(gallery_padding,filmstrip_margin))+'px'
});
/************************************************/
/* Build filmstrip and/or panels */
/************************************************/
if(opts.show_filmstrip) {
buildFilmstrip();
enableFrameClicking();
}
if(opts.show_panels) {
buildPanels();
}
/************************************************/
/* Add events to various elements */
/************************************************/
if(opts.pause_on_hover || (opts.show_panels && !opts.show_filmstrip)) {
$().mousemove(function(e){
if(mouseIsOverGallery(e.pageX,e.pageY)) {
if(opts.pause_on_hover) {
if(!paused) {
$(document).oneTime(500,"animation_pause",function(){
$(document).stopTime("transition");
paused=true;
});
}
}
if(opts.show_panels && !opts.show_filmstrip && !panel_nav_displayed) {
$('.nav-next-overlay').fadeIn('fast');
$('.nav-prev-overlay').fadeIn('fast');
$('.nav-next',j_gallery).fadeIn('fast');
$('.nav-prev',j_gallery).fadeIn('fast');
panel_nav_displayed = true;
}
} else {
if(opts.pause_on_hover) {
$(document).stopTime("animation_pause");
if(paused) {
$(document).everyTime(opts.transition_interval,"transition",function(){
showNextItem();
});
paused = false;
}
}
if(opts.show_panels && !opts.show_filmstrip && panel_nav_displayed) {
$('.nav-next-overlay').fadeOut('fast');
$('.nav-prev-overlay').fadeOut('fast');
$('.nav-next',j_gallery).fadeOut('fast');
$('.nav-prev',j_gallery).fadeOut('fast');
panel_nav_displayed = false;
}
}
});
}
/****************************************************************/
/* Initiate Automated Animation */
/****************************************************************/
//Hide loading box
j_filmstrip.css('visibility','visible');
j_gallery.css('visibility','visible');
$('.loader',j_gallery).fadeOut('1000',function(){
//Show the 'first' panel
showItem(iterator);
//If we have more than one item, begin automated transitions
if(item_count > 1) {
$(document).everyTime(opts.transition_interval,"transition",function(){
showNextItem();
});
}
});
}
/************************************************/
/* Main Plugin Code */
/************************************************/
return this.each(function() {
//Hide <ul>
$(this).css('visibility','hidden');
//Wrap <ul> in <div> and transfer ID to container <div>
//Assign filmstrip class to <ul>
$(this).wrap("<div></div>");
j_gallery = $(this).parent();
j_gallery.css('visibility','hidden').attr('id',$(this).attr('id')).addClass('gallery');
$(this).removeAttr('id').addClass('filmstrip');
$(document).stopTime("transition");
$(document).stopTime("animation_pause");
id = j_gallery.attr('id');
//If there is no defined panel content, we will scale panel images
scale_panel_images = $('.panel-content',j_gallery).length==0;
//Define dimensions of pointer <div>
pointer_height = opts.pointer_size;
pointer_width = opts.pointer_size*2;
//Determine filmstrip orientation (vertical or horizontal)
//Do not show captions on vertical filmstrips
filmstrip_orientation = (opts.filmstrip_position=='top'||opts.filmstrip_position=='bottom'?'horizontal':'vertical');
if(filmstrip_orientation=='vertical') opts.show_captions = false;
//Determine path between current page and plugin images
//Scan script tags and look for path to GalleryView plugin
$('script').each(function(i){
var s = $(this);
if(s.attr('src') && s.attr('src').match(/jquery\.galleryview/)){
loader_path = s.attr('src').split('jquery.galleryview')[0];
theme_path = s.attr('src').split('jquery.galleryview')[0]+'themes/';
}
});
j_filmstrip = $('.filmstrip',j_gallery);
j_frames = $('li',j_filmstrip);
j_frames.addClass('frame');
//If the user wants panels, generate them using the filmstrip images
if(opts.show_panels) {
for(i=j_frames.length-1;i>=0;i--) {
if(j_frames.eq(i).find('.panel-content').length>0) {
j_frames.eq(i).find('.panel-content').remove().prependTo(j_gallery).addClass('panel');
} else {
p = $('<div>');
p.addClass('panel');
im = $('<img />');
im.attr('src',j_frames.eq(i).find('img').eq(0).attr('src')).appendTo(p);
p.prependTo(j_gallery);
j_frames.eq(i).find('.panel-overlay').remove().appendTo(p);
}
}
} else {
$('.panel-overlay',j_frames).remove();
$('.panel-content',j_frames).remove();
}
//If the user doesn't want a filmstrip, delete it
if(!opts.show_filmstrip) { j_filmstrip.remove(); }
else {
//Wrap the frame images (and links, if applicable) in container divs
//These divs will handle cropping and zooming of the images
j_frames.each(function(i){
if($(this).find('a').length>0) {
$(this).find('a').wrap('<div class="img_wrap"></div>');
} else {
$(this).find('img').wrap('<div class="img_wrap"></div>');
}
});
j_frame_img_wrappers = $('.img_wrap',j_frames);
}
j_panels = $('.panel',j_gallery);
if(!opts.show_panels) {
opts.panel_height = 0;
opts.panel_width = 0;
}
//Determine final frame dimensions, accounting for user-added padding and border
f_frame_width = opts.frame_width+extraWidth(j_frame_img_wrappers);
f_frame_height = opts.frame_height+extraHeight(j_frame_img_wrappers);
//Number of frames in filmstrip
item_count = opts.show_panels?j_panels.length:j_frames.length;
//Number of frames that can display within the gallery block
//64 = width of block for navigation button * 2 + 20
if(filmstrip_orientation=='horizontal') {
strip_size = opts.show_panels?Math.floor((opts.panel_width-((opts.frame_gap+22)*2))/(f_frame_width+opts.frame_gap)):Math.min(item_count,opts.filmstrip_size);
} else {
strip_size = opts.show_panels?Math.floor((opts.panel_height-(opts.frame_gap+22))/(f_frame_height+opts.frame_gap)):Math.min(item_count,opts.filmstrip_size);
}
/************************************************/
/* Determine transition method for filmstrip */
/************************************************/
//If more items than strip size, slide filmstrip
//Otherwise, slide pointer
if(strip_size >= item_count) {
slide_method = 'pointer';
strip_size = item_count;
}
else {slide_method = 'strip';}
iterator = (strip_size<item_count?item_count:0)+opts.start_frame-1;
/************************************************/
/* Determine dimensions of various elements */
/************************************************/
filmstrip_margin = (opts.show_panels?getInt(j_filmstrip.css('marginTop')):0);
j_filmstrip.css('margin','0px');
if(filmstrip_orientation=='horizontal') {
//Width of gallery block
gallery_width = opts.show_panels?opts.panel_width:(strip_size*(f_frame_width+opts.frame_gap))+44+opts.frame_gap;
//Height of gallery block = screen + filmstrip + captions (optional)
gallery_height = (opts.show_panels?opts.panel_height:0)+(opts.show_filmstrip?f_frame_height+filmstrip_margin+(opts.show_captions?frame_caption_size:0):0);
} else {
//Width of gallery block
gallery_height = opts.show_panels?opts.panel_height:(strip_size*(f_frame_height+opts.frame_gap))+22;
//Height of gallery block = screen + filmstrip + captions (optional)
gallery_width = (opts.show_panels?opts.panel_width:0)+(opts.show_filmstrip?f_frame_width+filmstrip_margin:0);
}
//Width of filmstrip
if(filmstrip_orientation=='horizontal') {
if(slide_method == 'pointer') {strip_width = (f_frame_width*item_count)+(opts.frame_gap*(item_count));}
else {strip_width = (f_frame_width*item_count*3)+(opts.frame_gap*(item_count*3));}
} else {
strip_width = (f_frame_width+filmstrip_margin);
}
if(filmstrip_orientation=='horizontal') {
strip_height = (f_frame_height+filmstrip_margin+(opts.show_captions?frame_caption_size:0));
} else {
if(slide_method == 'pointer') {strip_height = (f_frame_height*item_count+opts.frame_gap*(item_count));}
else {strip_height = (f_frame_height*item_count*3)+(opts.frame_gap*(item_count*3));}
}
//Width of filmstrip wrapper (to hide overflow)
wrapper_width = ((strip_size*f_frame_width)+((strip_size-1)*opts.frame_gap));
wrapper_height = ((strip_size*f_frame_height)+((strip_size-1)*opts.frame_gap));
gallery_padding = getInt(j_gallery.css('paddingTop'));
j_gallery.css('padding','0px');
/********************************************************/
/* PLACE LOADING BOX OVER GALLERY UNTIL IMAGES LOAD */
/********************************************************/
galleryPos = getPos(j_gallery[0]);
$('<div>').addClass('loader').css({
'position':'absolute',
'zIndex':'32666',
'opacity':1,
'top':'0px',
'left':'0px',
'width':gallery_width+(filmstrip_orientation=='horizontal'?(gallery_padding*2):gallery_padding+Math.max(gallery_padding,filmstrip_margin))+'px',
'height':gallery_height+(filmstrip_orientation=='vertical'?(gallery_padding*2):gallery_padding+Math.max(gallery_padding,filmstrip_margin))+'px'
}).appendTo(j_gallery);
if(!window_loaded) {
$(window).load(function(){
window_loaded = true;
buildGallery();
});
} else {
buildGallery();
}
});
};
$.fn.galleryView.defaults = {
show_panels: true,
show_filmstrip: true,
panel_width: 600,
panel_height: 400,
frame_width: 60,
frame_height: 40,
start_frame: 1,
filmstrip_size: 3,
transition_speed: 800,
transition_interval: 4000,
overlay_opacity: 0.7,
frame_opacity: 0.3,
pointer_size: 8,
nav_theme: 'dark',
easing: 'swing',
filmstrip_position: 'bottom',
overlay_position: 'bottom',
panel_scale: 'nocrop',
frame_scale: 'crop',
frame_gap: 5,
show_captions: false,
fade_panels: true,
pause_on_hover: false
};
})(jQuery);

View file

@ -0,0 +1,147 @@
/**
* jQuery.timers - Timer abstractions for jQuery
* Written by Blair Mitchelmore (blair DOT mitchelmore AT gmail DOT com)
* Licensed under the WTFPL (http://sam.zoy.org/wtfpl/).
* Date: 2009/02/08
*
* @author Blair Mitchelmore
* @version 1.1.2
*
**/
jQuery.fn.extend({
everyTime: function(interval, label, fn, times, belay) {
return this.each(function() {
jQuery.timer.add(this, interval, label, fn, times, belay);
});
},
oneTime: function(interval, label, fn) {
return this.each(function() {
jQuery.timer.add(this, interval, label, fn, 1);
});
},
stopTime: function(label, fn) {
return this.each(function() {
jQuery.timer.remove(this, label, fn);
});
}
});
jQuery.event.special
jQuery.extend({
timer: {
global: [],
guid: 1,
dataKey: "jQuery.timer",
regex: /^([0-9]+(?:\.[0-9]*)?)\s*(.*s)?$/,
powers: {
// Yeah this is major overkill...
'ms': 1,
'cs': 10,
'ds': 100,
's': 1000,
'das': 10000,
'hs': 100000,
'ks': 1000000
},
timeParse: function(value) {
if (value == undefined || value == null)
return null;
var result = this.regex.exec(jQuery.trim(value.toString()));
if (result[2]) {
var num = parseFloat(result[1]);
var mult = this.powers[result[2]] || 1;
return num * mult;
} else {
return value;
}
},
add: function(element, interval, label, fn, times, belay) {
var counter = 0;
if (jQuery.isFunction(label)) {
if (!times)
times = fn;
fn = label;
label = interval;
}
interval = jQuery.timer.timeParse(interval);
if (typeof interval != 'number' || isNaN(interval) || interval <= 0)
return;
if (times && times.constructor != Number) {
belay = !!times;
times = 0;
}
times = times || 0;
belay = belay || false;
var timers = jQuery.data(element, this.dataKey) || jQuery.data(element, this.dataKey, {});
if (!timers[label])
timers[label] = {};
fn.timerID = fn.timerID || this.guid++;
var handler = function() {
if (belay && this.inProgress)
return;
this.inProgress = true;
if ((++counter > times && times !== 0) || fn.call(element, counter) === false)
jQuery.timer.remove(element, label, fn);
this.inProgress = false;
};
handler.timerID = fn.timerID;
if (!timers[label][fn.timerID])
timers[label][fn.timerID] = window.setInterval(handler,interval);
this.global.push( element );
},
remove: function(element, label, fn) {
var timers = jQuery.data(element, this.dataKey), ret;
if ( timers ) {
if (!label) {
for ( label in timers )
this.remove(element, label, fn);
} else if ( timers[label] ) {
if ( fn ) {
if ( fn.timerID ) {
window.clearInterval(timers[label][fn.timerID]);
delete timers[label][fn.timerID];
}
} else {
for ( var fn in timers[label] ) {
window.clearInterval(timers[label][fn]);
delete timers[label][fn];
}
}
for ( ret in timers[label] ) break;
if ( !ret ) {
ret = null;
delete timers[label];
}
}
for ( ret in timers ) break;
if ( !ret )
jQuery.removeData(element, this.dataKey);
}
}
}
});
jQuery(window).bind("unload", function() {
jQuery.each(jQuery.timer.global, function(index, item) {
jQuery.timer.remove(item);
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 B

4320
public/javascripts/prototype.js vendored Normal file

File diff suppressed because it is too large Load diff

5
public/robots.txt Normal file
View file

@ -0,0 +1,5 @@
# See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
#
# To ban all spiders from the entire site uncomment the next two lines:
# User-Agent: *
# Disallow: /

1
public/thumbs Symbolic link
View file

@ -0,0 +1 @@
/users/espen/gallery_thumbs/

4
script/about Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
$LOAD_PATH.unshift "#{RAILTIES_PATH}/builtin/rails_info"
require 'commands/about'

3
script/console Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/console'

3
script/dbconsole Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/dbconsole'

3
script/destroy Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/destroy'

3
script/generate Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/generate'

3
script/performance/benchmarker Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/performance/benchmarker'

3
script/performance/profiler Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../config/boot'
require 'commands/performance/profiler'

3
script/plugin Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/plugin'

3
script/runner Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/runner'

3
script/server Executable file
View file

@ -0,0 +1,3 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../config/boot'
require 'commands/server'

7
test/fixtures/albums.yml vendored Normal file
View file

@ -0,0 +1,7 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
# one:
# column: value
#
# two:
# column: value

7
test/fixtures/events.yml vendored Normal file
View file

@ -0,0 +1,7 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
# one:
# column: value
#
# two:
# column: value

7
test/fixtures/photos.yml vendored Normal file
View file

@ -0,0 +1,7 @@
# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
# one:
# column: value
#
# two:
# column: value

View file

@ -0,0 +1,8 @@
require 'test_helper'
class EventsControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View file

@ -0,0 +1,8 @@
require 'test_helper'
class PhotosControllerTest < ActionController::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View file

@ -0,0 +1,9 @@
require 'test_helper'
require 'performance_test_help'
# Profiling results for each test method are written to tmp/performance.
class BrowsingTest < ActionController::PerformanceTest
def test_homepage
get '/'
end
end

38
test/test_helper.rb Normal file
View file

@ -0,0 +1,38 @@
ENV["RAILS_ENV"] = "test"
require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
require 'test_help'
class ActiveSupport::TestCase
# Transactional fixtures accelerate your tests by wrapping each test method
# in a transaction that's rolled back on completion. This ensures that the
# test database remains unchanged so your fixtures don't have to be reloaded
# between every test method. Fewer database queries means faster tests.
#
# Read Mike Clark's excellent walkthrough at
# http://clarkware.com/cgi/blosxom/2005/10/24#Rails10FastTesting
#
# Every Active Record database supports transactions except MyISAM tables
# in MySQL. Turn off transactional fixtures in this case; however, if you
# don't care one way or the other, switching from MyISAM to InnoDB tables
# is recommended.
#
# The only drawback to using transactional fixtures is when you actually
# need to test transactions. Since your test is bracketed by a transaction,
# any transactions started in your code will be automatically rolled back.
self.use_transactional_fixtures = true
# Instantiated fixtures are slow, but give you @david where otherwise you
# would need people(:david). If you don't want to migrate your existing
# test cases which use the @david style and don't mind the speed hit (each
# instantiated fixtures translates to a database query per test method),
# then set this back to true.
self.use_instantiated_fixtures = false
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
#
# Note: You'll currently still have to declare fixtures explicitly in integration tests
# -- they do not yet inherit this setting
fixtures :all
# Add more helper methods to be used by all tests here...
end

8
test/unit/album_test.rb Normal file
View file

@ -0,0 +1,8 @@
require 'test_helper'
class AlbumTest < ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

8
test/unit/event_test.rb Normal file
View file

@ -0,0 +1,8 @@
require 'test_helper'
class EventTest < ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end

View file

@ -0,0 +1,4 @@
require 'test_helper'
class EventsHelperTest < ActionView::TestCase
end

View file

@ -0,0 +1,4 @@
require 'test_helper'
class PhotosHelperTest < ActionView::TestCase
end

8
test/unit/photo_test.rb Normal file
View file

@ -0,0 +1,8 @@
require 'test_helper'
class PhotoTest < ActiveSupport::TestCase
# Replace this with your real tests.
test "the truth" do
assert true
end
end