├── log
├── production.log
└── server.log
├── public
├── favicon.ico
├── stylesheets
│ ├── .gitkeep
│ ├── jagaimo.css
│ ├── reset.css
│ ├── ruote-fluo-editor.css
│ ├── quaderno.css
│ └── ruote-on-rails.css
├── images
│ ├── rails.png
│ ├── ruote.png
│ └── quaderno_buttons.png
├── javascripts
│ ├── application.js
│ ├── jagaimo.js
│ ├── quaderno.js
│ └── jquery-1.4.3.min.js
├── robots.txt
├── 422.html
├── 404.html
└── 500.html
├── vendor
└── plugins
│ └── .gitkeep
├── .rspec
├── NOTES.txt
├── app
├── helpers
│ └── application_helper.rb
├── views
│ ├── shared
│ │ └── _flash.haml
│ ├── sessions
│ │ └── new.haml
│ ├── forms
│ │ ├── index.haml
│ │ └── show.haml
│ ├── definitions
│ │ ├── show.haml
│ │ ├── index.haml
│ │ └── edit.haml
│ ├── processes
│ │ └── index.haml
│ ├── workitems
│ │ ├── index.haml
│ │ └── show.haml
│ └── layouts
│ │ └── application.haml
├── models
│ ├── form.rb
│ ├── definition.rb
│ ├── workitem.rb
│ └── user.rb
└── controllers
│ ├── processes_controller.rb
│ ├── application_controller.rb
│ ├── sessions_controller.rb
│ ├── forms_controller.rb
│ ├── definitions_controller.rb
│ └── workitems_controller.rb
├── autotest
└── discover.rb
├── CHANGELOG.txt
├── TODO.txt
├── config.ru
├── lib
├── tasks
│ ├── ruote.rake
│ └── pass.rake
└── rack
│ └── ruote_admin_only.rb
├── .gitignore
├── config
├── environment.rb
├── boot.rb
├── initializers
│ ├── mime_types.rb
│ ├── inflections.rb
│ ├── backtrace_silencers.rb
│ ├── session_store.rb
│ ├── secret_token.rb
│ └── ruote_kit.rb
├── password.test
├── password
├── routes.rb
├── database.yml
├── environments
│ ├── development.rb
│ ├── test.rb
│ └── production.rb
├── locales
│ └── en.yml
└── application.rb
├── doc
└── README_FOR_APP
├── spec
├── support
│ └── login_helper.rb
├── models
│ ├── workitems_spec.rb
│ └── forms_spec.rb
├── requests
│ ├── _ruote_spec.rb
│ └── workitems_spec.rb
└── spec_helper.rb
├── Rakefile
├── test
├── performance
│ └── browsing_test.rb
└── test_helper.rb
├── script
└── rails
├── db
├── migrate
│ ├── 20101026022237_definition.rb
│ └── 20101018081034_forms.rb
├── seeds.rb
└── schema.rb
├── LICENSE.txt
├── Gemfile
└── README.markdown
/log/production.log:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/log/server.log:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/vendor/plugins/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/stylesheets/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --colour --format documentation
2 |
--------------------------------------------------------------------------------
/NOTES.txt:
--------------------------------------------------------------------------------
1 |
2 | * done :
3 |
4 | - rails g rspec:install
5 |
6 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/autotest/discover.rb:
--------------------------------------------------------------------------------
1 | Autotest.add_discovery { "rails" }
2 | Autotest.add_discovery { "rspec2" }
3 |
--------------------------------------------------------------------------------
/public/images/rails.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tosch/ruote-on-rails/HEAD/public/images/rails.png
--------------------------------------------------------------------------------
/public/images/ruote.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tosch/ruote-on-rails/HEAD/public/images/ruote.png
--------------------------------------------------------------------------------
/CHANGELOG.txt:
--------------------------------------------------------------------------------
1 |
2 | = RuoteOnRails
3 |
4 | == 2010-10-20 ==
5 |
6 | * included TheBoard example by John Mettraux
7 |
--------------------------------------------------------------------------------
/TODO.txt:
--------------------------------------------------------------------------------
1 |
2 | [ ] delegation (list of users ?)
3 | [ ] better documentation of the example models/controllers
4 |
5 |
--------------------------------------------------------------------------------
/public/images/quaderno_buttons.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tosch/ruote-on-rails/HEAD/public/images/quaderno_buttons.png
--------------------------------------------------------------------------------
/app/views/shared/_flash.haml:
--------------------------------------------------------------------------------
1 |
2 | #flashes
3 | - %w[ notice error ].each do |key|
4 | - if f = flash()[key.intern]
5 | %div{ :class => key }
6 | = f
7 |
8 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require ::File.expand_path('../config/environment', __FILE__)
4 | run RuoteOnRails::Application
5 |
--------------------------------------------------------------------------------
/lib/tasks/ruote.rake:
--------------------------------------------------------------------------------
1 | namespace :ruote do
2 | desc "Run a worker thread for ruote"
3 | task :run_worker => :environment do
4 | RuoteKit.run_worker(RUOTE_STORAGE)
5 | end
6 | end
--------------------------------------------------------------------------------
/public/javascripts/application.js:
--------------------------------------------------------------------------------
1 | // Place your application-specific JavaScript functions and classes here
2 | // This file is automatically included by javascript_include_tag :defaults
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .rvmrc
2 | nbproject
3 | work_*
4 | ruote_work_*
5 | .bundle
6 | db/*.sqlite3
7 | log/*.log
8 | tmp/**/*
9 | Gemfile.lock
10 | .DS_Store
11 | app/.DS_Store
12 | public/.DS_Store
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the rails application
2 | require File.expand_path('../application', __FILE__)
3 |
4 | # Initialize the rails application
5 | RuoteOnRails::Application.initialize!
6 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 |
3 | # Set up gems listed in the Gemfile.
4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
5 |
6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
7 |
--------------------------------------------------------------------------------
/doc/README_FOR_APP:
--------------------------------------------------------------------------------
1 | Use this README file to introduce your application and point to useful places in the API for learning more.
2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries.
3 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-Agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 | # Mime::Type.register_alias "text/html", :iphone
6 |
--------------------------------------------------------------------------------
/spec/support/login_helper.rb:
--------------------------------------------------------------------------------
1 |
2 | module LoginHelper
3 |
4 | def login_as(username, password=nil)
5 |
6 | visit '/'
7 | fill_in 'username', :with => username
8 | fill_in 'password', :with => password || username
9 | click_button 'login'
10 | end
11 | end
12 |
13 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # Add your own tasks in files placed in lib/tasks ending in .rake,
2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3 |
4 | require File.expand_path('../config/application', __FILE__)
5 | require 'rake'
6 |
7 | RuoteOnRails::Application.load_tasks
8 |
--------------------------------------------------------------------------------
/test/performance/browsing_test.rb:
--------------------------------------------------------------------------------
1 | require 'test_helper'
2 | require 'rails/performance_test_help'
3 |
4 | # Profiling results for each test method are written to tmp/performance.
5 | class BrowsingTest < ActionController::PerformanceTest
6 | def test_homepage
7 | get '/'
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/script/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
3 |
4 | APP_PATH = File.expand_path('../../config/application', __FILE__)
5 | require File.expand_path('../../config/boot', __FILE__)
6 | require 'rails/commands'
7 |
--------------------------------------------------------------------------------
/lib/tasks/pass.rake:
--------------------------------------------------------------------------------
1 |
2 | namespace :pass do
3 |
4 | require 'bcrypt'
5 |
6 | desc %{
7 | crypts the password given as last arg
8 | }
9 | task :crypt do
10 |
11 | pass = ARGV.last
12 |
13 | puts
14 | puts BCrypt::Password.create(pass)
15 | puts
16 |
17 | exit 0
18 | end
19 | end
20 |
21 |
--------------------------------------------------------------------------------
/db/migrate/20101026022237_definition.rb:
--------------------------------------------------------------------------------
1 |
2 | class Definition < ActiveRecord::Migration
3 |
4 | def self.up
5 |
6 | create_table :definitions do |t|
7 |
8 | t.text :definition, :null => false
9 |
10 | t.timestamps
11 | end
12 | end
13 |
14 | def self.down
15 |
16 | drop_table :definitions
17 | end
18 | end
19 |
20 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
7 | # Mayor.create(:name => 'Daley', :city => cities.first)
8 |
--------------------------------------------------------------------------------
/app/models/form.rb:
--------------------------------------------------------------------------------
1 |
2 | class Form < ActiveRecord::Base
3 |
4 | def match(workitem)
5 |
6 | task = task_regex.index('|') ?
7 | "#{workitem.participant_name}|#{workitem.task}" :
8 | workitem.task
9 |
10 | (Regexp.new(task_regex[1..-2]).match(task) != nil)
11 | end
12 |
13 | def self.for(workitem)
14 |
15 | all.find { |form| form.match(workitem) }
16 | end
17 | end
18 |
19 |
--------------------------------------------------------------------------------
/config/password.test:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # The password file for the limited user model that comes with ruote-on-rails
4 | #
5 | # username:locale:password:groupa,groupb
6 | #
7 | # To encrypt a password :
8 | #
9 | # rake pass:crypt sikrit
10 | #
11 |
12 | admin:en:$2a$10$VoNeNwXUgZkJ4MJUGImaie34KPAU8zjN1mhHvC3bfrPN8zS8CJy7i:admin
13 | bob:en:$2a$10$hQdnBh6hSyYD1NRcxPnV/udXH1InArtKuWneQBb.PQc3g0PScY3RO:
14 |
15 |
--------------------------------------------------------------------------------
/app/views/sessions/new.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t '.login'
4 |
5 | .session.group
6 | = form_tag(sessions_path, :method => 'POST') do
7 | = label_tag(:username, 'username') + ':'
8 | = text_field_tag(:username)
9 | = label_tag(:password, 'password') + ':'
10 | = password_field_tag(:password)
11 | = submit_tag('login')
12 |
13 | :javascript
14 | document.getElementById('username').focus();
15 |
16 |
--------------------------------------------------------------------------------
/db/migrate/20101018081034_forms.rb:
--------------------------------------------------------------------------------
1 |
2 | class Forms < ActiveRecord::Migration
3 |
4 | def self.up
5 |
6 | create_table :forms do |t|
7 |
8 | t.string :task_regex, :null => false, :unique => true
9 | t.text :template, :null => false
10 | t.text :sample_data, :null => false
11 |
12 | t.timestamps
13 | end
14 | end
15 |
16 | def self.down
17 |
18 | drop_table :forms
19 | end
20 | end
21 |
22 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format
4 | # (all these examples are active by default):
5 | # ActiveSupport::Inflector.inflections do |inflect|
6 | # inflect.plural /^(ox)$/i, '\1en'
7 | # inflect.singular /^(ox)en/i, '\1'
8 | # inflect.irregular 'person', 'people'
9 | # inflect.uncountable %w( fish sheep )
10 | # end
11 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/app/views/forms/index.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t '.title'
4 |
5 | %table.list
6 |
7 | %thead
8 | %td
9 | = t 'form.id'
10 | %td
11 | = t 'form.task_regex'
12 | %td
13 |
14 | - @forms.each do |form|
15 | %tr
16 | %td
17 | = form.id
18 | %td
19 | = form.task_regex
20 | %td
21 | = link_to t('.show'), form_path(form)
22 |
23 | .buttons
24 | %a.button{ :href => new_form_path }
25 | = t('.new')
26 |
27 |
--------------------------------------------------------------------------------
/config/password:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # The password file for the limited user model that comes with ruote-on-rails
4 | #
5 | # username:locale:password:groupa,groupb
6 | #
7 | # To encrypt a password :
8 | #
9 | # rake pass:crypt sikrit
10 | #
11 |
12 | admin:en:$2a$10$VoNeNwXUgZkJ4MJUGImaie34KPAU8zjN1mhHvC3bfrPN8zS8CJy7i:admin
13 | john:en:$2a$10$YSnPIirxi2608nCWeAOUAecTK/ogFogGRxJNFCu1dHrIeLWi9x8Um:
14 | torsten:de:$2a$10$yG4Ql1qFpTZw2uDsJsgIz.F0WuJy6405KDSeZLLJhN4tgnjL88DDy:
15 |
16 |
--------------------------------------------------------------------------------
/config/initializers/session_store.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | RuoteOnRails::Application.config.session_store :cookie_store, :key => '_ruote_on_rails_session'
4 |
5 | # Use the database for sessions instead of the cookie-based default,
6 | # which shouldn't be used to store highly confidential information
7 | # (create the session table with "rails generate session_migration")
8 | # RuoteOnRails::Application.config.session_store :active_record_store
9 |
--------------------------------------------------------------------------------
/spec/models/workitems_spec.rb:
--------------------------------------------------------------------------------
1 |
2 | require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
3 |
4 |
5 | describe Ruote::Workitem do
6 |
7 | before(:each) do
8 |
9 | @workitem = Ruote::Workitem.new(
10 | 'fei' => {
11 | 'engineid' => 'engine',
12 | 'wfid' => "201010290-abcd",
13 | 'expid' => '0_0_0' },
14 | 'participant_name' => 'joe',
15 | 'fields' => { 'params' => { 'task' => 'task for joe' } })
16 | end
17 | end
18 |
19 |
--------------------------------------------------------------------------------
/app/views/definitions/show.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t '.title'
4 |
5 | #flow
6 | %canvas#fluo{ :width => 200, :height => 200 }
7 |
8 | %pre#definition #{@definition.definition}
9 |
10 | .buttons
11 | = link_to t('.back'), :action => :index
12 | = button_to t('.launch'), processes_path(:definition => @definition.id)
13 |
14 |
15 | :javascript
16 |
17 | document.getElementById('fluo').noOuterBorder = true;
18 |
19 | Fluo.renderFlow('fluo', #{@definition.tree_json})
20 | Fluo.crop('fluo');
21 |
22 |
--------------------------------------------------------------------------------
/app/views/processes/index.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t '.title'
4 |
5 | %table.list
6 |
7 | %thead
8 | %td
9 | = t 'process.wfid'
10 | %td
11 | = t 'process.size'
12 | %td
13 | = t 'process.position'
14 |
15 | - @processes.each do |process|
16 | %tr
17 | %td
18 | = process.wfid
19 | %td
20 | = process.expressions.size
21 | %td
22 | - process.position.each do |pos|
23 | - fei, pname, info = pos
24 | = "#{pname} #{info['task']}"
25 |
26 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 |
2 | RuoteOnRails::Application.routes.draw do
3 |
4 | resources :workitems
5 | resources :processes
6 | resources :forms
7 | resources :definitions
8 |
9 | # ruote-kit
10 | #
11 | match '/_ruote' => RuoteKit::Application
12 | match '/_ruote/*path' => RuoteKit::Application
13 |
14 | # login / logout
15 | #
16 | resources :sessions
17 | match 'login' => 'sessions#new'
18 | match 'logout' => 'sessions#destroy'
19 |
20 | # /
21 | #
22 | root :to => 'workitems#index'
23 | end
24 |
25 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV["RAILS_ENV"] = "test"
2 | require File.expand_path(File.dirname(__FILE__) + "/../config/environment")
3 | require 'rails/test_help'
4 |
5 | class ActiveSupport::TestCase
6 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
7 | #
8 | # Note: You'll currently still have to declare fixtures explicitly in integration tests
9 | # -- they do not yet inherit this setting
10 | fixtures :all
11 |
12 | # Add more helper methods to be used by all tests here...
13 | end
14 |
--------------------------------------------------------------------------------
/app/views/workitems/index.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t '.title'
4 |
5 | %table.list
6 |
7 | %thead
8 | %td
9 | = t 'workitem.id'
10 | %td
11 | = t 'workitem.participant_name'
12 | %td
13 | = t 'workitem.task'
14 | %td
15 |
16 | - @workitems.each do |workitem|
17 | %tr
18 | %td
19 | = workitem.fei.sid
20 | %td
21 | = workitem.participant_name
22 | %td
23 | = workitem.params['task']
24 | %td
25 | = link_to t('.show'), workitem_path(workitem)
26 |
27 |
--------------------------------------------------------------------------------
/config/initializers/secret_token.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Your secret key for verifying the integrity of signed cookies.
4 | # If you change this key, all old signed cookies will become invalid!
5 | # Make sure the secret is at least 30 characters and all random,
6 | # no regular words or you'll be exposed to dictionary attacks.
7 | RuoteOnRails::Application.config.secret_token = 'cf6f3d6ade4cc5d4a617198c1870fd6955224d504a332a2a92b5c5b1500ec6a2b0202aa855b62a41e0b8b82d3f3172f5cc39fdb7343c03a05a2fb84b7da7644e'
8 |
--------------------------------------------------------------------------------
/app/controllers/processes_controller.rb:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # ruote process instances
4 | #
5 | class ProcessesController < ApplicationController
6 |
7 | def index
8 |
9 | @processes = RuoteKit.engine.processes
10 | end
11 |
12 | def create
13 |
14 | definition = Definition.find(params[:definition])
15 |
16 | wfid = RuoteKit.engine.launch(definition.definition)
17 |
18 | flash[:notice] = I18n.t('flash.notice.launched', :wfid => wfid)
19 |
20 | redirect_to :controller => 'definitions', :action => 'index'
21 | end
22 | end
23 |
24 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 |
2 | class ApplicationController < ActionController::Base
3 |
4 | protect_from_forgery
5 |
6 | before_filter :login_required
7 |
8 | layout 'application'
9 |
10 | protected # if it makes any sense
11 |
12 | def login_required
13 |
14 | return true if self.class == SessionsController
15 | # no login required for the login/logout screens
16 |
17 | return true if session[:username]
18 |
19 | redirect_to :controller => 'sessions', :action => 'new'
20 | false
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/app/models/definition.rb:
--------------------------------------------------------------------------------
1 |
2 | class Definition < ActiveRecord::Base
3 |
4 | def name
5 |
6 | tree[1]['name'] || tree[1].find { |k, v| v.nil? }.first
7 | end
8 |
9 | def revision
10 |
11 | tree[1]['revision']
12 | end
13 |
14 | def tree
15 |
16 | Ruote::Reader.read(definition)
17 | end
18 |
19 | def tree_json
20 |
21 | Rufus::Json.encode(tree)
22 | end
23 |
24 | # Makes sure the 'definition' column contains a string that is Ruby code.
25 | #
26 | def rubyize!
27 |
28 | self.definition = Ruote::Reader.to_ruby(tree).strip
29 | end
30 | end
31 |
32 |
--------------------------------------------------------------------------------
/spec/requests/_ruote_spec.rb:
--------------------------------------------------------------------------------
1 |
2 | require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
3 |
4 |
5 | describe '/_ruote' do
6 |
7 | it 'should not be visible to users not logged in' do
8 |
9 | get '/_ruote'
10 |
11 | response.status.should == 403 # forbidden
12 | end
13 |
14 | it 'should not be visible to non-admin users' do
15 |
16 | get '/_ruote'
17 |
18 | response.status.should == 403 # forbidden
19 | end
20 |
21 | it 'should be visible to admin users' do
22 |
23 | login_as('admin')
24 |
25 | get '/_ruote'
26 |
27 | response.status.should == 200
28 | end
29 | end
30 |
31 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # SQLite version 3.x
2 | # gem install sqlite3-ruby (not necessary on OS X Leopard)
3 | development:
4 | adapter: sqlite3
5 | database: db/development.sqlite3
6 | pool: 5
7 | timeout: 5000
8 |
9 | # Warning: The database defined as "test" will be erased and
10 | # re-generated from your development database when you run "rake".
11 | # Do not set this db to the same as development or production.
12 | test:
13 | adapter: sqlite3
14 | database: db/test.sqlite3
15 | pool: 5
16 | timeout: 5000
17 |
18 | production:
19 | adapter: sqlite3
20 | database: db/production.sqlite3
21 | pool: 5
22 | timeout: 5000
23 |
--------------------------------------------------------------------------------
/app/views/definitions/index.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t '.title'
4 |
5 | %table.list
6 |
7 | %thead
8 | %td
9 | = t 'definition.id'
10 | %td
11 | = t 'definition.name'
12 | %td
13 | = t 'definition.revision'
14 | %td
15 |
16 | - @definitions.each do |definition|
17 | %tr
18 | %td
19 | = definition.id
20 | %td
21 | = definition.name
22 | %td
23 | = definition.revision
24 | %td
25 | = link_to t('.show'), definition_path(definition)
26 | = link_to t('.edit'), edit_definition_path(definition)
27 |
28 | .buttons
29 | %a.button{ :href => new_definition_path }
30 | = t('.new')
31 |
32 |
--------------------------------------------------------------------------------
/app/controllers/sessions_controller.rb:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # dummy session/login management
4 | #
5 | class SessionsController < ApplicationController
6 |
7 | def new
8 |
9 | # simply letting app/sessions/new.haml get shown
10 | end
11 |
12 | def create
13 |
14 | if u = User.authentify(params[:username], params[:password])
15 | session[:username] = params[:username]
16 | session[:locale] = u.locale
17 | redirect_to '/'
18 | else
19 | flash[:error] = I18n.t('flash.errors.login_failed')
20 | render :action => :new
21 | end
22 | end
23 |
24 | def destroy
25 |
26 | session.delete(:username)
27 | # logging out
28 |
29 | render :action => :new
30 | end
31 | end
32 |
33 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The change you wanted was rejected.
23 |
Maybe you tried to change something you didn't have access to.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
The page you were looking for doesn't exist.
23 |
You may have mistyped the address or the page may have moved.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
17 |
18 |
19 |
20 |
21 |
22 |
We're sorry, but something went wrong.
23 |
We've been notified about this issue and we'll take a look at it shortly.
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/rack/ruote_admin_only.rb:
--------------------------------------------------------------------------------
1 |
2 | module Rack
3 |
4 | # Only admin users may have access to the /_ruote web console
5 | #
6 | class RuoteAdminOnly
7 |
8 | def initialize(app)
9 |
10 | @app = app
11 | end
12 |
13 | def call(env)
14 |
15 | return @app.call(env) if User.admin?(env['rack.session']['username'])
16 |
17 | pi = env['PATH_INFO']
18 |
19 | if pi.index('/_ruote/javascripts/') || pi.index('/_ruote/images/')
20 | return @app.call(env)
21 | end
22 | if pi.index('/_ruote') || pi.index('/_ruote/')
23 | return forbidden
24 | end
25 | @app.call(env)
26 | end
27 |
28 | protected
29 |
30 | def forbidden
31 |
32 | [
33 | 403,
34 | { 'Content-Type' => 'text/plain', 'Content-Length' => '9' },
35 | [ 'forbidden' ]
36 | ]
37 | end
38 | end
39 | end
40 |
41 |
--------------------------------------------------------------------------------
/public/stylesheets/jagaimo.css:
--------------------------------------------------------------------------------
1 | .jagaimo {
2 | color: #e7eef5;
3 | /*background: rgba(0,0,0,.85);*/
4 | background: black;
5 | font: 10.5px/14px "Menlo regular", monaco, courier, mono;
6 | padding: 12px;
7 | margin: 1px;
8 | -webkit-border-radius: 6px;
9 | -moz-border-radius: 6px;
10 | border-radius: 6px;
11 | /*overflow: auto;*/
12 | }
13 | .jagaimo_atom {
14 | display: inline-block;
15 | }
16 | .jagaimo_key {
17 | color: #86E34B;
18 | }
19 | .jagaimo_bracket {
20 | margin-left: 3px;
21 | margin-right: 3px;
22 | }
23 | .jagaimo_brace {
24 | margin-left: 3px;
25 | margin-right: 3px;
26 | }
27 | .jagaimo_colon {
28 | margin-right: 3px;
29 | }
30 | .jagaimo_comma {
31 | margin-right: 3px;
32 | }
33 | .jagaimo_boolean {
34 | color: red;
35 | }
36 | .jagaimo_tree_line {
37 | margin-left: 14px;
38 | }
39 | .jagaimo_tree_key {
40 | color: #86E34B;
41 | }
42 | .jagaimo_tree_bullet {
43 | color: red;
44 | }
45 | .jagaimo_tree_atom {
46 | color: white;
47 | }
--------------------------------------------------------------------------------
/app/controllers/forms_controller.rb:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # quaderno forms
4 | #
5 | class FormsController < ApplicationController
6 |
7 | def index
8 |
9 | @forms = Form.all
10 | end
11 |
12 | def new
13 |
14 | @form = Form.new
15 |
16 | @form.task_regex = '/clean the car/'
17 | @form.template = %{
18 | box fields
19 | text_input .name "name"
20 | text_input .age "age"
21 | text_input .color "colour"
22 | }.strip
23 | @form.sample_data = Rufus::Json.encode({
24 | 'fields' => {}
25 | })
26 |
27 | render :action => 'show'
28 | end
29 |
30 | def show
31 |
32 | @form = Form.find(params[:id])
33 | end
34 |
35 | def create
36 |
37 | Form.new(params[:form]).save!
38 |
39 | redirect_to :action => 'index'
40 | end
41 |
42 | def update
43 |
44 | form = Form.find(params[:id])
45 | form.attributes = params[:form]
46 | form.save!
47 |
48 | redirect_to :action => 'index'
49 | end
50 | end
51 |
52 |
--------------------------------------------------------------------------------
/app/controllers/definitions_controller.rb:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # process definitions
4 | #
5 | class DefinitionsController < ApplicationController
6 |
7 | def index
8 |
9 | @definitions = Definition.all
10 | end
11 |
12 | def new
13 |
14 | @definition = Definition.new
15 | @definition.definition = %{
16 | Ruote.process_definition 'my first definition' do
17 | sequence do
18 | alice
19 | bob
20 | end
21 | end
22 | }
23 |
24 | render :action => 'edit'
25 | end
26 |
27 | def edit
28 |
29 | @definition = Definition.find(params[:id])
30 | end
31 |
32 | def show
33 |
34 | @definition = Definition.find(params[:id])
35 | end
36 |
37 | def create
38 |
39 | definition = Definition.new(params[:definition])
40 | definition.rubyize!
41 | definition.save!
42 |
43 | redirect_to :action => 'index'
44 | end
45 |
46 | def update
47 |
48 | definition = Definition.find(params[:id])
49 | definition.attributes = params[:definition]
50 | definition.rubyize!
51 | definition.save!
52 |
53 | redirect_to :action => 'index'
54 | end
55 | end
56 |
57 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | RuoteOnRails::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the webserver when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Log error messages when you accidentally call methods on nil.
10 | config.whiny_nils = true
11 |
12 | # Show full error reports and disable caching
13 | config.consider_all_requests_local = true
14 | config.action_view.debug_rjs = true
15 | config.action_controller.perform_caching = false
16 |
17 | # Don't care if the mailer can't send
18 | config.action_mailer.raise_delivery_errors = false
19 |
20 | # Print deprecation notices to the Rails logger
21 | config.active_support.deprecation = :log
22 |
23 | # Only use best-standards-support built into browsers
24 | config.action_dispatch.best_standards_support = :builtin
25 | end
26 |
27 |
--------------------------------------------------------------------------------
/spec/models/forms_spec.rb:
--------------------------------------------------------------------------------
1 |
2 | require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
3 |
4 |
5 | describe Form do
6 |
7 | before(:each) do
8 |
9 | Form.destroy_all
10 |
11 | Form.new(
12 | :task_regex => '/clean car/',
13 | :template => '',
14 | :sample_data => '{}'
15 | ).save!
16 | Form.new(
17 | :task_regex => '/bob|sell car/',
18 | :template => '',
19 | :sample_data => '{}'
20 | ).save!
21 | end
22 |
23 | it 'matches for /task name/' do
24 |
25 | workitem = OpenStruct.new(
26 | :task => 'clean car', :participant_name => 'alice')
27 |
28 | Form.all[0].match(workitem).should == true
29 | Form.all[1].match(workitem).should == false
30 | Form.for(workitem).should == Form.all[0]
31 | end
32 |
33 | it 'matches for /participant name|task name/' do
34 |
35 | workitem = OpenStruct.new(
36 | :task => 'sell car', :participant_name => 'bob')
37 |
38 | Form.all[0].match(workitem).should == false
39 | Form.all[1].match(workitem).should == true
40 | Form.for(workitem).should == Form.all[1]
41 | end
42 | end
43 |
44 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 |
2 | Copyright (c) 2010-2010, John Mettraux, Torsten Schoenebaum
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 |
22 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | # This file is copied to spec/ when you run 'rails generate rspec:install'
2 | ENV["RAILS_ENV"] ||= 'test'
3 | require File.expand_path("../../config/environment", __FILE__)
4 | require 'rspec/rails'
5 |
6 | # Requires supporting ruby files with custom matchers and macros, etc,
7 | # in spec/support/ and its subdirectories.
8 | Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f}
9 |
10 | RSpec.configure do |config|
11 | # == Mock Framework
12 | #
13 | # If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
14 | #
15 | # config.mock_with :mocha
16 | # config.mock_with :flexmock
17 | # config.mock_with :rr
18 | config.mock_with :rspec
19 |
20 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
21 | #config.fixture_path = "#{::Rails.root}/spec/fixtures"
22 |
23 | # If you're not using ActiveRecord, or you'd prefer not to run each of your
24 | # examples within a transaction, remove the following line or assign false
25 | # instead of true.
26 | config.use_transactional_fixtures = true
27 |
28 | config.include LoginHelper
29 | end
30 |
31 | require 'ostruct'
32 |
33 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 |
2 | source 'http://rubygems.org'
3 |
4 | # Rails
5 | #
6 | gem 'rails', '3.0.10'
7 | #gem 'rails', :git => 'git://github.com/rails/rails.git'
8 |
9 | gem 'sqlite3-ruby', :require => 'sqlite3'
10 |
11 | # web server
12 | #
13 | #gem 'unicorn'
14 | gem 'thin'
15 |
16 | # Deploy with Capistrano
17 | #
18 | #gem 'capistrano'
19 |
20 | # To use debugger
21 | #
22 | #gem 'ruby-debug'
23 |
24 | # Bundle the extra gems:
25 | #
26 | #gem 'bj'
27 | #gem 'nokogiri'
28 | #gem 'aws-s3', :require => 'aws/s3'
29 |
30 | # Bundle gems for the local environment. Make sure to
31 | # put test-only gems in this group so their generators
32 | # and rake tasks are available in development mode:
33 | #
34 | group :development, :test do
35 | gem 'webrat', '0.7.1'
36 | gem 'rspec', '2.0.1'
37 | gem 'rspec-rails', '2.0.1'
38 | end
39 |
40 | gem 'haml'
41 |
42 | gem 'sinatra', '~>1.2'
43 | gem 'sinatra-respond_to', '~>0.7.0'
44 |
45 | gem 'yajl-ruby', :require => 'yajl'
46 |
47 | #gem 'ruote-kit', '2.1.11'
48 | gem 'ruote', :git => 'http://github.com/jmettraux/ruote.git'
49 | gem 'ruote-kit', :git => 'http://github.com/tosch/ruote-kit.git'
50 |
51 | gem 'bcrypt-ruby'
52 | # for app/models/user.rb
53 |
54 |
--------------------------------------------------------------------------------
/public/stylesheets/reset.css:
--------------------------------------------------------------------------------
1 | /* http://meyerweb.com/eric/tools/css/reset/ */
2 | /* v1.0 | 20080212 */
3 |
4 | html, body, div, span, applet, object, iframe,
5 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
6 | a, abbr, acronym, address, big, cite, code,
7 | del, dfn, em, font, img, ins, kbd, q, s, samp,
8 | small, strike, strong, sub, sup, tt, var,
9 | b, u, i, center,
10 | dl, dt, dd, ol, ul, li,
11 | fieldset, form, label, legend,
12 | table, caption, tbody, tfoot, thead, tr, th, td {
13 | margin: 0;
14 | padding: 0;
15 | border: 0;
16 | outline: 0;
17 | font-size: 100%;
18 | vertical-align: baseline;
19 | background: transparent;
20 | }
21 | body {
22 | line-height: 1;
23 | }
24 | ol, ul {
25 | list-style: none;
26 | }
27 | blockquote, q {
28 | quotes: none;
29 | }
30 | blockquote:before, blockquote:after,
31 | q:before, q:after {
32 | content: '';
33 | content: none;
34 | }
35 |
36 | /* remember to define focus styles! */
37 | :focus {
38 | outline: 0;
39 | }
40 |
41 | /* remember to highlight inserts somehow! */
42 | ins {
43 | text-decoration: none;
44 | }
45 | del {
46 | text-decoration: line-through;
47 | }
48 |
49 | /* tables still need 'cellspacing="0"' in the markup */
50 | table {
51 | border-collapse: collapse;
52 | border-spacing: 0;
53 | }
54 |
--------------------------------------------------------------------------------
/app/controllers/workitems_controller.rb:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # ruote workitems
4 | #
5 | class WorkitemsController < ApplicationController
6 |
7 | def index
8 |
9 | @workitems = Ruote::Workitem.for_user(session[:username])
10 | end
11 |
12 | def show
13 |
14 | @workitem =
15 | RuoteKit.storage_participant[params[:id]]
16 | @tree =
17 | Rufus::Json.encode(RuoteKit.engine.process(@workitem.wfid).current_tree)
18 | @form =
19 | Form.for(@workitem)
20 | end
21 |
22 | def update
23 |
24 | fields = Rufus::Json.decode(params[:workitem][:fields])
25 |
26 | workitem = RuoteKit.storage_participant[params[:id]]
27 | workitem.fields.merge!(fields)
28 |
29 | submit = params[:workitem][:submit]
30 |
31 | if submit == 'proceed'
32 |
33 | RuoteKit.storage_participant.reply(workitem)
34 |
35 | flash[:notice] = I18n.t('flash.notice.proceeded', :fei => workitem.fei.sid)
36 | else
37 |
38 | if submit == 'release'
39 | workitem.participant_name = 'anyone'
40 | elsif submit == 'take'
41 | workitem.participant_name = session[:username]
42 | end
43 |
44 | RuoteKit.storage_participant.update(workitem)
45 | end
46 |
47 | redirect_to :action => :index
48 | end
49 | end
50 |
51 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended to check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(:version => 20101026022237) do
14 |
15 | create_table "definitions", :force => true do |t|
16 | t.text "definition", :null => false
17 | t.datetime "created_at"
18 | t.datetime "updated_at"
19 | end
20 |
21 | create_table "forms", :force => true do |t|
22 | t.string "task_regex", :null => false
23 | t.text "template", :null => false
24 | t.text "sample_data", :null => false
25 | t.datetime "created_at"
26 | t.datetime "updated_at"
27 | end
28 |
29 | end
30 |
--------------------------------------------------------------------------------
/app/views/definitions/edit.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t '.title'
4 |
5 | = form_for @definition do |f|
6 |
7 | #flow
8 | %canvas#fluo{ :width => 200, :height => 200 }
9 |
10 | #editor
11 | %div.tab
12 | %a{ :href => '', :onclick => 'return show("#textbox")'} #{t('.textbox')}
13 | #fluo_editor
14 |
15 | #textbox
16 | %div.tab
17 | %a{ :href => '', :onclick => 'return show("#editor")'} #{t('.editor')}
18 | = f.text_area :definition
19 |
20 | .buttons
21 | = link_to t('.cancel'), :action => :index
22 | = f.submit t('.submit')
23 |
24 | :javascript
25 |
26 | $('#textbox').hide();
27 |
28 | //
29 | // show
30 |
31 | function show(id) {
32 | $('#editor').hide();
33 | $('#textbox').hide();
34 | $(id).show();
35 | return false;
36 | }
37 |
38 | //
39 | // editor
40 |
41 | FluoEditor.renderFlow('fluo_editor', #{@definition.tree_json});
42 |
43 | $('#fluo_editor')[0].onChange = function (tree) {
44 | Fluo.renderFlow('fluo', tree);
45 | Fluo.crop('fluo');
46 | }
47 | $('#fluo_editor')[0].onOver = function (expid) {
48 | Fluo.highlight('fluo', expid);
49 | }
50 |
51 | $('#definition_submit').bind('click', function() {
52 | if ($('#editor')[0].style.display === 'none') return;
53 | $('#definition_definition').val(FluoEditor.asJson('fluo_editor'));
54 | });
55 |
56 | //
57 | // fluo
58 |
59 | document.getElementById('fluo').noOuterBorder = true;
60 |
61 | Fluo.renderFlow('fluo', #{@definition.tree_json})
62 | Fluo.crop('fluo');
63 |
64 |
--------------------------------------------------------------------------------
/spec/requests/workitems_spec.rb:
--------------------------------------------------------------------------------
1 |
2 | require File.join(File.dirname(__FILE__), '..', 'spec_helper.rb')
3 |
4 |
5 | describe '/workitems' do
6 |
7 | describe '/index' do
8 |
9 | context 'when there are no workitems' do
10 |
11 | it 'shows no workitems' do
12 |
13 | login_as('bob')
14 |
15 | get '/workitems'
16 |
17 | response.status.should == 200
18 | end
19 | end
20 |
21 | context 'when there are workitems' do
22 |
23 | before(:each) do
24 |
25 | %w[ alice bob ].each_with_index do |pname, i|
26 |
27 | RuoteKit.storage_participant.update(Ruote::Workitem.new(
28 | 'fei' => {
29 | 'engineid' => 'engine',
30 | 'wfid' => "20101029#{i}-abcd",
31 | 'expid' => '0_0_0' },
32 | 'participant_name' => pname,
33 | 'fields' => { 'params' => { 'task' => "task for #{pname}" } }))
34 | end
35 | end
36 |
37 | after(:each) do
38 |
39 | RuoteKit.engine.storage.purge!
40 | end
41 |
42 | it 'shows them' do
43 |
44 | login_as('bob')
45 |
46 | get '/workitems'
47 |
48 | response.status.should == 200
49 | response.should_not contain('for alice')
50 | response.should contain('for bob')
51 | end
52 |
53 | it 'shows all the workitems to admins' do
54 |
55 | login_as('admin')
56 |
57 | get '/workitems'
58 |
59 | response.status.should == 200
60 | response.should contain('for alice')
61 | response.should contain('for bob')
62 | end
63 | end
64 | end
65 | end
66 |
67 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | RuoteOnRails::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The test environment is used exclusively to run your application's
5 | # test suite. You never need to work with it otherwise. Remember that
6 | # your test database is "scratch space" for the test suite and is wiped
7 | # and recreated between test runs. Don't rely on the data there!
8 | config.cache_classes = true
9 |
10 | # Log error messages when you accidentally call methods on nil.
11 | config.whiny_nils = true
12 |
13 | # Show full error reports and disable caching
14 | config.consider_all_requests_local = true
15 | config.action_controller.perform_caching = false
16 |
17 | # Raise exceptions instead of rendering exception templates
18 | config.action_dispatch.show_exceptions = false
19 |
20 | # Disable request forgery protection in test environment
21 | config.action_controller.allow_forgery_protection = false
22 |
23 | # Tell Action Mailer not to deliver emails to the real world.
24 | # The :test delivery method accumulates sent emails in the
25 | # ActionMailer::Base.deliveries array.
26 | config.action_mailer.delivery_method = :test
27 |
28 | # Use SQL instead of Active Record's schema dumper when creating the test database.
29 | # This is necessary if your schema can't be completely dumped by the schema dumper,
30 | # like if you have constraints or database-specific column types
31 | # config.active_record.schema_format = :sql
32 |
33 | # Print deprecation notices to the stderr
34 | config.active_support.deprecation = :stderr
35 | end
36 |
--------------------------------------------------------------------------------
/app/models/workitem.rb:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # Simply opening the ruote workitem class to add some things specific
4 | # to arts (and ActiveModel).
5 | #
6 | class Ruote::Workitem
7 |
8 | #include ActiveModel::Naming
9 | #include ActiveModel::Validations
10 |
11 | def task
12 | params['task']
13 | end
14 |
15 | def participant_name=(name)
16 | @h['participant_name'] = name
17 | end
18 |
19 | def self.for_user(username)
20 |
21 | user = User.find(username)
22 |
23 | return RuoteKit.engine.storage_participant.all if user.admin?
24 |
25 | # note : ruote 2.1.12 should make it possible to write
26 | #
27 | # RuoteKit.engine.storage_participant.by_participant(
28 | # [ username, user.groups, 'anyone' ].flatten)
29 | #
30 | # directly.
31 |
32 | [ username, user.groups, 'anyone' ].flatten.collect { |pname|
33 | RuoteKit.engine.storage_participant.by_participant(pname)
34 | }.flatten
35 | end
36 |
37 | #--
38 | # active model (method need to make of workitems active models)
39 | #++
40 |
41 | # def self.model_name
42 | #
43 | # return @_model_name if @_model_name
44 | #
45 | # mn = Object.new
46 | # def mn.name; 'workitem'; end
47 | # @_model_name = ActiveModel::Name.new(mn)
48 | # end
49 | #
50 | # def to_model
51 | # self
52 | # end
53 | #
54 | # def destroyed?
55 | # fields['_destroyed'] == true
56 | # end
57 | #
58 | # def new_record?
59 | # @h['fei'] == {}
60 | # end
61 | #
62 | # def persisted?
63 | # @h['fei'] != {}
64 | # end
65 | #
66 | # def to_key
67 | # return nil unless persisted?
68 | # [ @h['_id'] ]
69 | # end
70 |
71 | def to_param
72 | fei.sid
73 | end
74 | end
75 |
76 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | RuoteOnRails::Application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb
3 |
4 | # The production environment is meant for finished, "live" apps.
5 | # Code is not reloaded between requests
6 | config.cache_classes = true
7 |
8 | # Full error reports are disabled and caching is turned on
9 | config.consider_all_requests_local = false
10 | config.action_controller.perform_caching = true
11 |
12 | # Specifies the header that your server uses for sending files
13 | config.action_dispatch.x_sendfile_header = "X-Sendfile"
14 |
15 | # For nginx:
16 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect'
17 |
18 | # If you have no front-end server that supports something like X-Sendfile,
19 | # just comment this out and Rails will serve the files
20 |
21 | # See everything in the log (default is :info)
22 | # config.log_level = :debug
23 |
24 | # Use a different logger for distributed setups
25 | # config.logger = SyslogLogger.new
26 |
27 | # Use a different cache store in production
28 | # config.cache_store = :mem_cache_store
29 |
30 | # Disable Rails's static asset server
31 | # In production, Apache or nginx will already do this
32 | config.serve_static_assets = false
33 |
34 | # Enable serving of images, stylesheets, and javascripts from an asset server
35 | # config.action_controller.asset_host = "http://assets.example.com"
36 |
37 | # Disable delivery errors, bad email addresses will be ignored
38 | # config.action_mailer.raise_delivery_errors = false
39 |
40 | # Enable threaded mode
41 | # config.threadsafe!
42 |
43 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
44 | # the I18n.default_locale when a translation can not be found)
45 | config.i18n.fallbacks = true
46 |
47 | # Send deprecation notices to registered listeners
48 | config.active_support.deprecation = :notify
49 | end
50 |
--------------------------------------------------------------------------------
/app/views/forms/show.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t('.title')
4 |
5 | = form_for @form do |f|
6 |
7 | %table.form
8 |
9 | %tr
10 | %td
11 | = t('form.task_regex') + ':'
12 | = f.text_field :task_regex
13 | %td
14 |
15 | %tr
16 | %td
17 | = t('.template') + ':'
18 | %div
19 | = f.text_area :template
20 | .buttons.right
21 | %a{ :href => '', :onclick => 'return render();' }
22 | = t('.render')
23 | %td
24 | = t('.form') + ':'
25 | #quad.quad_root
26 | .buttons
27 | %a{ :href => '', :onclick => 'return undo();' }
28 | = t('.undo')
29 | %a{ :href => '', :onclick => 'return reset();' }
30 | = t('.reset')
31 | %a{ :href => '', :onclick => 'return produce();' }
32 | = t('.produce')
33 |
34 | %tr
35 | %td
36 | = t('.data_in') + ':'
37 | %div
38 | = f.text_area :sample_data, :rows => 10
39 | -# #data_in.jagaimo
40 | %td
41 | = t('.data_out') + ':'
42 | #data_out.jagaimo
43 |
44 | .buttons
45 | = link_to t('.cancel'), :action => :index
46 | = f.submit t('.submit')
47 |
48 |
49 | :javascript
50 |
51 | //var data_in = JSON.parse('#{@form.sample_data}');
52 | //Jagaimo.render('#data_in', data_in, { mode: 'tree' });
53 |
54 | function render () {
55 | try {
56 | var data = JSON.parse($('#form_sample_data')[0].value);
57 | var template = Quaderno.parse($('#form_template')[0].value);
58 | Quaderno.render('#quad', template, data);
59 | }
60 | catch (e) {
61 | }
62 | return false;
63 | }
64 |
65 | render();
66 |
67 | function undo () {
68 | Quaderno.undo('#quad');
69 | return false;
70 | }
71 | function reset () {
72 | Quaderno.reset('#quad');
73 | return false;
74 | }
75 |
76 | function produce () {
77 |
78 | var product = Quaderno.produce('#quad');
79 | Jagaimo.render('#data_out', product, { mode: 'tree' });
80 |
81 | return false;
82 | }
83 |
84 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # See http://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
2 |
3 | en:
4 |
5 | flash:
6 | errors:
7 | login_failed: login failed
8 | notice:
9 | proceeded: "workitem %{fei} proceeded"
10 | launched: "process %{wfid} launched"
11 |
12 | form:
13 | id: id
14 | task_regex: task regex
15 |
16 | process:
17 | wfid: wfid
18 | size: size
19 | position: position
20 |
21 | workitem:
22 | id: id
23 | participant_name: participant
24 | task: task
25 |
26 | definition:
27 | id: id
28 | name: name
29 | revision: revision
30 |
31 | layouts:
32 | application:
33 | logout: logout
34 | workitems: workitems
35 | processes: processes
36 | forms: forms
37 | definitions: definitions
38 | ruote: ruote
39 |
40 | sessions:
41 | new:
42 | login: login
43 |
44 | forms:
45 | index:
46 | title: forms
47 | new: new form
48 | show: show
49 | show:
50 | title: form
51 | template: template
52 | form: form
53 | data_in: data in
54 | data_out: data out
55 | render: render →
56 | undo: undo
57 | reset: reset
58 | produce: produce ↓
59 | submit: submit
60 | cancel: cancel
61 |
62 | processes:
63 | index:
64 | title: processes
65 |
66 | workitems:
67 | index:
68 | title: workitems
69 | show: show
70 | show:
71 | title: workitem
72 | fields: fields
73 | cancel: cancel
74 | release: for everyone
75 | take: for myself
76 | save: save
77 | proceed: proceed
78 |
79 | definitions:
80 | index:
81 | title: process definitions
82 | show: show
83 | edit: edit
84 | new: new process definition
85 | show:
86 | title: process definition
87 | back: back
88 | launch: launch
89 | edit:
90 | title: process definition
91 | editor: editor
92 | textbox: textbox
93 | cancel: cancel
94 | submit: save
95 |
96 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 |
2 | #
3 | # This is a very simple User model, ready to be torn off and replaced by
4 | # another mechanism / library, like for example :
5 | #
6 | # http://github.com/hassox/warden/
7 | #
8 |
9 | require 'bcrypt'
10 |
11 |
12 | #
13 | # User information is stored in config/passwd
14 | #
15 | # username:locale:pass_bcrypted:groupa,groupb
16 | #
17 | class User
18 |
19 | attr_reader :name
20 | attr_reader :locale
21 | attr_reader :pass
22 | attr_reader :groups
23 |
24 | def initialize(name, locale, pass, groups)
25 |
26 | @name = name
27 | @locale = locale
28 | @pass = pass
29 | @groups = groups
30 | end
31 |
32 | def in_group?(group_name)
33 |
34 | @groups.include?(group_name)
35 | end
36 |
37 | def admin?
38 |
39 | in_group?('admin')
40 | end
41 |
42 | def self.admin?(name)
43 |
44 | if u = find(name)
45 | u.admin?
46 | else
47 | false
48 | end
49 | end
50 |
51 | def self.find(name)
52 |
53 | all.find { |u| u.name == name }
54 | end
55 |
56 | def self.authentify(user, pass)
57 |
58 | all.each do |u|
59 | next unless u.name == user
60 | return u if u.pass == pass
61 | end
62 |
63 | nil
64 | end
65 |
66 | def self.all
67 |
68 | fn = Rails.root.join('config', "password.#{Rails.env}")
69 | fn = File.exist?(fn) ? fn : Rails.root.join(*%w[ config password ])
70 |
71 | lines = File.readlines(fn)
72 |
73 | lines.inject([]) { |a, line|
74 |
75 | line = line.strip
76 |
77 | if line.length > 0 && ( ! line.match(/^#/))
78 |
79 | name, locale, password, groups = line.split(':')
80 | password = BCrypt::Password.new(password)
81 | groups = (groups || '').split(',')
82 |
83 | a << User.new(name, locale, password, groups)
84 | end
85 |
86 | a
87 | }
88 | end
89 |
90 | # Returns a list of all the known groups (all the groups mentioned in
91 | # the passwd file).
92 | #
93 | def self.groups
94 |
95 | all.collect { |user| user.groups }.flatten.uniq.sort
96 | end
97 | end
98 |
99 |
--------------------------------------------------------------------------------
/app/views/workitems/show.haml:
--------------------------------------------------------------------------------
1 |
2 | %h1
3 | = t '.title'
4 |
5 | #flow
6 | %canvas#fluo{ :width => 1150, :height => 700 }
7 |
8 | #form
9 |
10 | - form_tag(workitem_path(@workitem), :method => 'PUT') do
11 |
12 | = hidden_field_tag 'workitem[submit]', 'save'
13 |
14 | - if @form
15 |
16 | #quad.quad_root
17 |
18 | = hidden_field_tag 'workitem[fields]', Rufus::Json.encode(@workitem.fields)
19 |
20 | - else
21 |
22 | %div #{t('.fields') + ':'}
23 | = text_area_tag 'workitem[fields]', Rufus::Json.encode(@workitem.fields), :rows => 10, :cols => 77
24 |
25 | .buttons
26 |
27 | = link_to t('.cancel'), :action => :index
28 |
29 | - if @workitem.participant_name != 'anyone'
30 | = submit_tag t('.release'), :id => 'release_button'
31 | - else
32 | = submit_tag t('.take'), :id => 'take_button'
33 |
34 | = submit_tag t('.save'), :id => 'save_button'
35 | = submit_tag t('.proceed'), :id => 'proceed_button'
36 |
37 |
38 | :javascript
39 |
40 | $('#release_button').bind('click', function() {
41 | $('#workitem_submit').val('release');
42 | return true;
43 | });
44 | $('#take_button').bind('click', function() {
45 | $('#workitem_submit').val('take');
46 | return true;
47 | });
48 | $('#proceed_button').bind('click', function() {
49 | $('#workitem_submit').val('proceed');
50 | return true;
51 | });
52 |
53 | //
54 | // fluo
55 |
56 | document.getElementById('fluo').noOuterBorder = true;
57 |
58 | Fluo.renderFlow(
59 | 'fluo', #{@tree}, { workitems: [ '#{@workitem.fei.expid}' ] });
60 |
61 | Fluo.crop('fluo');
62 |
63 | - if @form
64 |
65 | :javascript
66 |
67 | //
68 | // quad
69 |
70 | var data = JSON.parse($('#workitem_fields')[0].value);
71 | var template = Quaderno.parse(#{@form.template.inspect});
72 | Quaderno.render('#quad', template, data);
73 |
74 | var produce = function() {
75 | var product = JSON.stringify(Quaderno.produce('#quad'));
76 | $('#workitem_fields').val(product);
77 | return true;
78 | };
79 |
80 | $('#save_button').bind('click', produce);
81 | $('#proceed_button').bind('click', produce);
82 |
83 |
--------------------------------------------------------------------------------
/config/initializers/ruote_kit.rb:
--------------------------------------------------------------------------------
1 | # make changes when needed
2 | #
3 | # you may use another persistent storage for example or include a worker so that
4 | # you don't have to run it in a separate instance
5 | #
6 | # See http://ruote.rubyforge.org/configuration.html for configuration options of
7 | # ruote.
8 |
9 | # we will use yajl for json encoding/decoding
10 | # you may whish to use another one (json, json_pure) if yajl is not available
11 | #
12 | require 'yajl'
13 | Rufus::Json.backend = :yajl
14 |
15 | require 'ruote/storage/fs_storage'
16 |
17 | RUOTE_STORAGE = Ruote::FsStorage.new("ruote_work_#{Rails.env}")
18 |
19 | RuoteKit.engine = Ruote::Engine.new(Ruote::Worker.new(RUOTE_STORAGE))
20 |
21 | # By default, there is a running worker when you start the Rails server. That is
22 | # convenient in development, but may be (or not) a problem in deployment.
23 | #
24 | # Please keep in mind that there should always be a running worker or schedules
25 | # may get triggered to late. Some deployments (like Passenger) won't guarantee
26 | # the Rails server process is running all the time, so that there's no always-on
27 | # worker. Also beware that the Ruote::HashStorage only supports one worker.
28 | #
29 | # If you don't want to start a worker thread within your Rails server process,
30 | # replace the line before this comment with the following:
31 | #
32 | # RuoteKit.engine = Ruote::Engine.new(RUOTE_STORAGE)
33 | #
34 | # To run a worker in its own process, there's a rake task available:
35 | #
36 | # rake ruote:run_worker
37 | #
38 | # Stop the task by pressing Ctrl+C
39 |
40 | unless $RAKE_TASK
41 | # don't register participants when the run is triggered by a rake task
42 |
43 | RuoteKit.engine.register do
44 |
45 | # register your own participants using the participant method
46 | #
47 | # Example: participant 'alice', Ruote::StorageParticipant see
48 | # http://ruote.rubyforge.org/participants.html for more info
49 |
50 | # register the catchall storage participant named '.+'
51 | catchall
52 | end
53 | end
54 |
55 | # when true, the engine will be very noisy (stdout)
56 | #
57 | RuoteKit.engine.context.logger.noisy = false
58 |
59 | require Rails.root.join('app/models/workitem.rb')
60 |
61 |
--------------------------------------------------------------------------------
/app/views/layouts/application.haml:
--------------------------------------------------------------------------------
1 |
2 | %html
3 |
4 | %head
5 |
6 | %meta( http-equiv="Content-Type" content="text/html; charset=UTF-8" )
7 |
8 | %title ruote-on-rails
9 |
10 | %link( href='/images/favicon.png' type='image/png' rel='icon' )
11 | %link{ :href => '/stylesheets/reset.css', :type => 'text/css', :rel => 'stylesheet' }
12 | %link{ :href => '/stylesheets/ruote-fluo-editor.css', :type => 'text/css', :rel => 'stylesheet' }
13 | %link{ :href => '/stylesheets/quaderno.css', :type => 'text/css', :rel => 'stylesheet' }
14 | %link{ :href => '/stylesheets/jagaimo.css', :type => 'text/css', :rel => 'stylesheet' }
15 | %link{ :href => '/stylesheets/ruote-on-rails.css', :type => 'text/css', :rel => 'stylesheet' }
16 |
17 | /[if !IE7]
18 | :css
19 | .container { display: table; height: 100%; }
20 |
21 | %script{ :src => '/javascripts/jquery-1.4.3.min.js' }
22 | %script{ :src => '/_ruote/javascripts/ruote-fluo.js' }
23 | %script{ :src => '/_ruote/javascripts/ruote-fluo-editor.js' }
24 | %script{ :src => '/javascripts/quaderno.js' }
25 | %script{ :src => '/javascripts/jagaimo.js' }
26 |
27 | %body
28 |
29 | .container
30 |
31 | .header
32 | %a.home{ :href => '/' }
33 | %img{ :src => '/images/ruote.png', :alt => 'ruote-on-rails' }
34 |
35 | - if session[:username]
36 | %a.button.user{ :href => '/logout' } #{t('.logout')} #{session[:username]}
37 | .nav
38 | %ul
39 | %li
40 | = link_to t('.workitems'), workitems_path
41 | %li
42 | = link_to t('.processes'), processes_path
43 | %li
44 | = link_to t('.forms'), forms_path
45 | %li
46 | = link_to t('.definitions'), definitions_path
47 | - if User.admin?(session[:username])
48 | %li
49 | = link_to t('.ruote'), '/_ruote'
50 |
51 | = render :partial => 'shared/flash'
52 |
53 | .main_content
54 | = yield :layout
55 |
56 | .footer
57 | %p
58 | powered by
59 | %a{ :href => 'http://ruote.rubyforge.org' }
60 | ruote
61 | and
62 | %a{ :href => 'http://github.com/tosch/ruote-kit' }
63 | ruote-kit
64 |
65 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path('../boot', __FILE__)
2 |
3 | require 'rails/all'
4 |
5 | # If you have a Gemfile, require the gems listed there, including any gems
6 | # you've limited to :test, :development, or :production.
7 | Bundler.require(:default, Rails.env) if defined?(Bundler)
8 |
9 | module RuoteOnRails
10 | class Application < Rails::Application
11 | # Settings in config/environments/* take precedence over those specified here.
12 | # Application configuration should go into files in config/initializers
13 | # -- all .rb files in that directory are automatically loaded.
14 |
15 | # Custom directories with classes and modules you want to be autoloadable.
16 | # config.autoload_paths += %W(#{config.root}/extras)
17 |
18 | # Only load the plugins named here, in the order given (default is alphabetical).
19 | # :all can be used as a placeholder for all plugins not explicitly named.
20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ]
21 |
22 | # Activate observers that should always be running.
23 | # config.active_record.observers = :cacher, :garbage_collector, :forum_observer
24 |
25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
27 | # config.time_zone = 'Central Time (US & Canada)'
28 |
29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
31 | # config.i18n.default_locale = :de
32 |
33 | # JavaScript files you want as :defaults (application.js is always included).
34 | # config.action_view.javascript_expansions[:defaults] = %w(jquery rails)
35 |
36 | # Configure the default encoding used in templates for Ruby 1.9.
37 | config.encoding = "utf-8"
38 |
39 | # Configure sensitive parameters which will be filtered from the log file.
40 | config.filter_parameters += [:password]
41 |
42 | require Rails.root.join('lib/rack/ruote_admin_only')
43 | config.middleware.use Rack::RuoteAdminOnly
44 |
45 | # we don't put RuoteKit into the Rack middleware stack as we would duplicate
46 | # routing between Rails and RuoteKit (it's a Sinatra app). Instead, we use
47 | # Rails' routes.rb to pass requests on /_ruote(.*) to RuoteKit.
48 | #
49 | # See config/routes.rb
50 | #
51 | #config.middleware.use 'RuoteKit::Application'
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/public/stylesheets/ruote-fluo-editor.css:
--------------------------------------------------------------------------------
1 | /* @override http://127.0.0.1:3000/stylesheets/ruote-fluo-editor.css */
2 | #fluo_editor {
3 | font: 13px/1.3em monaco, mono;
4 | text-align: left;
5 | color: #000;
6 | border: 1px solid #c1c1c1;
7 | border-top-color: #848484;
8 | border-bottom-color: #ddd;
9 | padding: 6px;
10 | margin: 6px 4px;
11 | background: #fff;
12 | background: -webkit-gradient(linear, 0% 0%, 0% 6, from(#dbe0e2), to(#fff));
13 | background: -moz-linear-gradient(top, #dbe0e2, #fff 6px);
14 | }
15 | .rfe_expression {
16 |
17 | }
18 | .rfe_expression_atts,
19 | .rfe_text,
20 | .rfe_expression_name,
21 | .rfe_expression_string,
22 | .rfe_ghost_button {
23 |
24 | }
25 | .rfe_expression_name {
26 | color: #8b8f91;
27 | }
28 | .rfe_expression_atts {
29 |
30 | }
31 | .rfe_expression_string {
32 | color: #82dd48;
33 | }
34 | .rfe_exp_span {
35 |
36 | }
37 | .rfe_buttons {
38 | display: inline-block;
39 | white-space: nowrap;
40 | margin-left: 6px;
41 | }
42 | .rfe_buttons:hover {
43 | cursor: default;
44 | }
45 | .rfe_button {
46 | cursor: pointer;
47 | padding-left: 1px;
48 | }
49 | a.rfe_button {
50 | display: inline-block;
51 | padding: 0;
52 | margin: 0 -1px;
53 | width: 12px;
54 | height: 12px;
55 | background: transparent url(/_ruote/images/ruote_buttons.png) no-repeat;
56 | }
57 | a.rfe_add {
58 | background-position: 0 0;
59 | }
60 | a.rfe_cut {
61 | background-position: 0 -20px;
62 | }
63 | a.rfe_moveup {
64 | background-position: 0 -40px;
65 | }
66 | a.rfe_movedown {
67 | background-position: 0 -60px;
68 | }
69 | a.rfe_paste {
70 | background-position: 0 -80px;
71 | }
72 | a.rfe_add:hover {
73 | background-position: -20px 0;
74 | }
75 | a.rfe_cut:hover {
76 | background-position: -20px -20px;
77 | }
78 | a.rfe_moveup:hover {
79 | background-position: -20px -40px;
80 | }
81 | a.rfe_movedown:hover {
82 | background-position: -20px -60px;
83 | }
84 | a.rfe_paste:hover {
85 | background-position: -20px -80px;
86 | }
87 | .rfe_exp > input[type="text"] {
88 | color: #000;
89 | font: 14px/1em monaco, mono;
90 | border: 0;
91 | padding: 0 10px;
92 | padding-top: 1px;
93 | margin: 0;
94 | margin-left: -11px;
95 | margin-bottom: 1px;
96 | width: 400px;
97 | background: #86e74c;
98 | border-radius: 15px;
99 | -webkit-border-radius: 15px;
100 | -moz-border-radius: 15px;
101 | }
102 | img.rfe_button {
103 | margin: 0;
104 | vertical-align: middle;
105 | }
106 | /* safari */
107 | @media screen and (-webkit-min-device-pixel-ratio:0) {
108 | .rfe_exp > input[type="text"] {
109 | padding-top: 2px;
110 | margin-bottom: 0;
111 | }
112 | }
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Ruote on Rails
2 | ==============
3 |
4 | Is an example Rails 3 app for demonstrating the usage of
5 | [ruote](http://ruote.rubyforge.org) in [Rails](http://rubyonrails.org) via
6 | [ruote-kit](http://github.com/kennethkalmer/ruote-kit) and
7 | [quaderno](http://github.com/jmettraux/quaderno).
8 |
9 | Installation
10 | ------------
11 |
12 | * clone (or do whatever you like to get the code) this repo:
13 | $ git clone git://github.com/tosch/ruote-on-rails.git
14 | $ cd ruote-on-rails
15 | * install [bundler](http://rubybundler.com) if not present
16 | $ gem install bundler
17 | * make sure all dependencies are met
18 | $ bundle install
19 | * create database
20 | $ rake db:migrate
21 | * create temp dir
22 | $ mkdir -p mkdir tmp/pids
23 |
24 |
25 | Generate a new Rails app using ruote[-kit]
26 | ------------------------------------------
27 |
28 | [See ruote-kit's Readme on that](http://github.com/tosch/ruote-kit/blob/master/README.rdoc)
29 |
30 | Note: You'll get a plain ruote[-kit] integration, no examples will be installed
31 | to your app. You won't get [quaderno](http://github.com/jmettraux/quaderno) this
32 | way, either.
33 |
34 |
35 | Configuration
36 | -------------
37 |
38 | Just tailor config/initializers/ruote_kit.rb to your needs.
39 |
40 |
41 | Run
42 | ---
43 |
44 | $ rails server
45 |
46 | Browse to http://localhost:3000/_ruote and you'll see there are no running
47 | processes. You could change that using the "Launch process" link ;-)
48 |
49 |
50 | Using Ruote from within Rails
51 | -----------------------------
52 |
53 | You can access Ruote's engine anywhere in your Rails code by calling
54 | RuoteKit.engine
55 | So launching a workflow process is as easy as
56 | RuoteKit.engine.launch your_process_definition
57 | The storage participant (used by the catchall participant) is available at
58 | RuoteKit.storage_participant
59 |
60 |
61 | Ruote Workers
62 | -------------
63 |
64 | When using ruote, you'll often here the words 'engine', 'storage' and 'worker'.
65 | Those are different parts of ruote. Basically, the engine is just a dashboard to
66 | the storage, you use it to launch processes or get the state of running
67 | processes. The engine itself doesn't do any work (triggering participants for
68 | example), it just puts messages into the storage. The work is done by one (or
69 | more) workers (oh yes, that was obvious). They look into the storage to find out
70 | what to do next.
71 |
72 | You may instanciate a ruote engine with or without a worker. In the latter case,
73 | you may launch processes, but they will never appear in the processes list
74 | until you start a worker: The launch request will be put in the storage and lie
75 | there unprocessed.
76 |
77 | By default, RuoteOnRails does include a worker in the engine when running the
78 | Rails server process. That way, you'll get started quickly. Please beware that
79 | you'll have to be careful when deploying to production. Ruote's worker thread
80 | should be always on, or schedules like timeouts won't be triggered in time (they
81 | won't be forgotten, the next time the worker starts they'll be triggered, but
82 | that may be too late). Especially [Passenger](http://modrails.com) stops Rails
83 | server processes after some time by default and would thus stop the ruote
84 | worker, too (in Passenger 3, you may configure that there should be at least one
85 | process running all the time, though).
86 |
87 | RuoteOnRails ships with a rake task that starts a worker and keeps running until
88 | you stop the task. You may launch it by calling
89 |
90 | $ rake ruote:run_worker
91 |
92 | Stop the task by pressing Ctrl+C.
93 |
94 | If you don't like to have a worker thread running inside your Rails server
95 | process, have a look at config/initializers/ruote-kit.rb. You'll find
96 | instructions there how to instanciate the ruote engine together without a
97 | worker.
98 |
99 |
100 | TheBoard example
101 | ----------------
102 |
103 | The auth is minimal (I'd say: pathetic), but see yourself. Please make sure to
104 | replace it with your own if you base your work on this…
105 |
106 | TODO: Write more about TheBoard
107 |
108 |
109 | Other Examples
110 | --------
111 |
112 | * [ruote-rails-example](http://github.com/threetee/ruote-rails-example) by
113 | [threetee](http://github.com/threetee) is a Rails 2 test app which integrates
114 | ruote, ruote-amqp, ruote-kit and ruote-on-rails
115 |
116 |
117 | Links
118 | -----
119 |
120 | * [ruote](http://ruote.rubyforge.org)
121 | * [ruote-kit](http://github.com/tosch/ruote-kit)
122 | * [quaderno](http://github.com/jmettraux/quaderno) - ([presentation blog](http://jmettraux.wordpress.com/2010/09/22/quaderno/))
123 |
124 |
125 | License
126 | -------
127 |
128 | MIT
129 |
130 |
131 | Authors
132 | -------
133 |
134 | John Mettraux
135 | Torsten Schönebaum
--------------------------------------------------------------------------------
/public/javascripts/jagaimo.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2010, John Mettraux, jmettraux@gmail.com
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | //
22 |
23 | var Jagaimo = function () {
24 |
25 | //
26 | // render()
27 |
28 | function create (container, tagName, className, innerText) {
29 |
30 | var e = document.createElement(tagName);
31 | if (className) e.className = className;
32 | if (innerText) e.appendChild(document.createTextNode(innerText));
33 | if (container) container.appendChild(e);
34 | return e;
35 | }
36 |
37 | function isArray (o) {
38 | try { return o.constructor.toString().match(/function Array\(/); }
39 | catch (e) { return false; }
40 | }
41 |
42 | function renderFlat (container, o) {
43 |
44 | if (isArray(o)) {
45 |
46 | create(container, 'span', 'jagaimo_bracket', '[');
47 | for (var i = 0; i < o.length; i++) {
48 | renderFlat(container, o[i]);
49 | if (i < o.length - 1) {
50 | create(container, 'span', 'jagaimo_comma', ',');
51 | }
52 | }
53 | create(container, 'span', 'jagaimo_bracket', ']');
54 | }
55 | else if ((typeof o) === 'object') {
56 |
57 | create(container, 'span', 'jagaimo_brace', '{');
58 |
59 | var keys = [];
60 | for (var k in o) keys.push(k);
61 |
62 | for (var i = 0; i < keys.length; i++) {
63 | create(container, 'span', 'jagaimo_key', keys[i]);
64 | create(container, 'span', 'jagaimo_colon', ':');
65 | renderFlat(container, o[keys[i]]);
66 | if (i < keys.length - 1) {
67 | create(container, 'span', 'jagaimo_comma', ',');
68 | }
69 | }
70 | create(container, 'span', 'jagaimo_brace', '}');
71 | }
72 | else {
73 |
74 | create(
75 | container,
76 | 'span',
77 | 'jagaimo_atom jagaimo_' + (typeof o),
78 | JSON.stringify(o));
79 | }
80 | }
81 |
82 | function renderTree (container, o) {
83 |
84 | if ((typeof o) !== 'object') {
85 |
86 | create(
87 | container,
88 | 'span',
89 | 'jagaimo_tree_atom jagaimo_' + (typeof o),
90 | o.toString());
91 | return;
92 | }
93 |
94 | var line = create(container, 'div', 'jagaimo_tree_line');
95 |
96 | if (isArray(o)) {
97 |
98 | for (var i = 0; i < o.length; i++) {
99 | var bullet = create(line, 'div', 'jagaimo_tree_bullet', '- ');
100 | renderTree(bullet, o[i]);
101 | }
102 | }
103 | else {
104 |
105 | var keys = [];
106 | for (var k in o) keys.push(k);
107 | keys = keys.sort();
108 |
109 | for (var i = 0; i < keys.length; i++) {
110 | var key = create(line, 'div', 'jagaimo_tree_key', keys[i] + ': ');
111 | renderTree(key, o[keys[i]]);
112 | }
113 | }
114 | }
115 |
116 | function render (containerId, o, opts) {
117 |
118 | var container = containerId;
119 | opts = opts || {};
120 |
121 | if ((typeof containerId) === 'string') {
122 | if (containerId.match(/^#/)) containerId = containerId.slice(1);
123 | container = document.getElementById(containerId);
124 | }
125 |
126 | var fc; while (fc = container.firstChild) { container.removeChild(fc); }
127 |
128 | var flat = create(container, 'div', 'jagaimo_flat');
129 |
130 | renderFlat(flat, o);
131 |
132 | var tree = create(container, 'div', 'jagaimo_tree');
133 | tree.style.display = 'none';
134 |
135 | renderTree(tree, o);
136 |
137 | container.onclick = function () {
138 | if (flat.style.display === 'none') {
139 | flat.style.display = 'block';
140 | tree.style.display = 'none';
141 | }
142 | else {
143 | flat.style.display = 'none';
144 | tree.style.display = 'block';
145 | }
146 | };
147 | container.style.cursor = 'pointer';
148 |
149 | if (opts.mode === 'tree') container.onclick();
150 | }
151 |
152 | return {
153 |
154 | render: render
155 | };
156 | }();
157 |
158 |
--------------------------------------------------------------------------------
/public/stylesheets/quaderno.css:
--------------------------------------------------------------------------------
1 | /* @override http://localhost:3000/stylesheets/quaderno.css */
2 | /* quad */
3 | table.quad_tab_group {
4 | border-collapse: separate;
5 | border-spacing: 0;
6 | }
7 | .quad_tab_group td {
8 | vertical-align: middle;
9 | padding: 0;
10 | }
11 | .quad_tab {
12 | border: 1px solid #919191;
13 | border-bottom-color: black;
14 | -webkit-border-top-right-radius: 6px;
15 | -webkit-border-top-left-radius: 6px;
16 | -moz-border-radius-topright: 6px;
17 | -moz-border-radius-topleft: 6px;
18 | border-top-right-radius: 6px;
19 | border-top-left-radius: 6px;
20 | background: #f6f6f6;
21 | }
22 | .quad_tab a {
23 | display: block;
24 | padding: 8px;
25 | color: #919191;
26 | line-height: 1.25em;
27 | text-align: center;
28 | text-decoration: none;
29 | font-size: 13px;
30 | font-weight: bold;
31 | }
32 | .quad_tab.quad_selected {
33 | background: white;
34 | border-color: black;
35 | border-bottom-color: transparent;
36 | }
37 | .quad_tab.quad_selected a {
38 | color: black;
39 | }
40 | .quad_tab_body {
41 | padding: 12px;
42 | border: 1px solid black;
43 | border-top: none;
44 | -webkit-border-bottom-right-radius: 6px;
45 | -webkit-border-bottom-left-radius: 6px;
46 | -moz-border-radius-bottomright: 6px;
47 | -moz-border-radius-bottomleft: 6px;
48 | border-bottom-right-radius: 6px;
49 | border-bottom-left-radius: 6px;
50 | }
51 | .quad_tab_element {
52 | display: none;
53 | }
54 | .quad_element {
55 | margin-bottom: 4px;
56 | }
57 | .quad_new_element {
58 | margin: 16px 4px 12px 86px;
59 | }
60 | /*span.quad_type {
61 | width: 84px;
62 | display: inline-block;
63 | }*/
64 | div.quad_key, span.quad_key {
65 | color: #919191;
66 | display: inline-block;
67 | width: 140px;
68 | margin-right: 4px;
69 | text-align: right;
70 | }
71 | div.quad_key.quad_text {
72 | width: auto;
73 | margin-bottom: 4px;
74 | }
75 | .quad_label {
76 | color: #919191;
77 | }
78 | .quad_date_separator {
79 | color: #919191;
80 | display: inline-block;
81 | width: 1.25em;
82 | margin-right: 2px;
83 | text-align: right;
84 | }
85 | .quad_link_target {
86 | width: 70px;
87 | }
88 | .quad_circle {
89 | cursor: pointer;
90 | margin-right: 4px;
91 | }
92 | /* text_input */
93 | input.quad_value {
94 |
95 | }
96 | /* checkbox */
97 | input.quad_checkbox {
98 | margin-left: 129px;
99 | margin-right: 5px;
100 | }
101 | span.quad_checkbox_key {
102 | color: #8b8f91;
103 | display: inline-block;
104 | text-align: left;
105 | margin-right: 4px;
106 | }
107 | /* textarea */
108 | textarea.quad_value {
109 | display: inline-block;
110 | width: auto;
111 | height: 75px;
112 | font-size: .9em;
113 | }
114 | /* buttons */
115 | input, select {
116 | margin-right: 4px;
117 | }
118 | a.quadButton, .quad_rep {
119 | margin: 0 1px;
120 | padding: 2px 6px 3px 6px;
121 | background: #aaaeb0;
122 | color: #fcfcfc;
123 | font-size: 14px;
124 | line-height: 1.25em;
125 | font-weight: bold;
126 | -webkit-border-radius: 6px;
127 | -moz-border-radius: 6px;
128 | border-radius: 6px;
129 | text-decoration: none;
130 | }
131 | .quad_rep {
132 | margin: 0 3px;
133 | background: #b9bcbe;
134 | color: #fff;
135 | font-size: 11px;
136 | border-radius: 12px;
137 | -webkit-border-radius: 12px;
138 | -moz-border-radius: 12px;
139 | }
140 | .quad_box {
141 | display: block;
142 | padding: 8px 12px;
143 | background: #fff;
144 | border: 1px solid #dbdbdb;
145 | -webkit-border-radius: 6px;
146 | -moz-border-radius: 6px;
147 | border-radius: 6px;
148 | }
149 | .quad_box_head {
150 | padding-bottom: 4px;
151 | margin-bottom: 4px;
152 | border-bottom: 1px solid #dbdbdb;
153 | }
154 | a.quad_button {
155 | display: inline-block;
156 | margin-right: 1px;
157 | width: 12px;
158 | height: 12px;
159 | background: transparent url(../images/quaderno/quaderno_buttons.png) no-repeat;
160 | }
161 | a.quad_plus_button {
162 | background-position: 0 0;
163 | }
164 | a.quad_minus_button {
165 | background-position: 0 -20px;
166 | }
167 | a.quad_up_button {
168 | background-position: 0 -40px;
169 | }
170 | a.quad_down_button {
171 | background-position: 0 -60px;
172 | }
173 | a.quad_cut_button {
174 | background-position: 0 -20px;
175 | }
176 | a.quad_paste_button {
177 | background-position: 0 -80px;
178 | }
179 | a.quad_go_button {
180 | background-position: 0 -100px;
181 | }
182 | a.quad_copy_button {
183 | background-position: 0 -120px;
184 | }
185 | a.quad_left_button {
186 | background-position: 0 -140px;
187 | }
188 | a.quad_right_button {
189 | background-position: 0 -160px;
190 | }
191 | a.quad_plus_button:hover {
192 | background-position: -20px 0;
193 | }
194 | a.quad_minus_button:hover {
195 | background-position: -20px -20px;
196 | }
197 | a.quad_up_button:hover {
198 | background-position: -20px -40px;
199 | }
200 | a.quad_down_button:hover {
201 | background-position: -20px -60px;
202 | }
203 | a.quad_cut_button:hover {
204 | background-position: -20px -20px;
205 | }
206 | a.quad_paste_button:hover {
207 | background-position: -20px -80px;
208 | }
209 | a.quad_go_button:hover {
210 | background-position: -20px -100px;
211 | }
212 | a.quad_copy_button:hover {
213 | background-position: -20px -120px;
214 | }
215 | a.quad_left_button:hover {
216 | background-position: -20px -140px;
217 | }
218 | a.quad_right_button:hover {
219 | background-position: -20px -160px;
220 | }
221 | a.array_remove_button {
222 |
223 | }
--------------------------------------------------------------------------------
/public/stylesheets/ruote-on-rails.css:
--------------------------------------------------------------------------------
1 | /* @override http://127.0.0.1:3000/stylesheets/ruote-on-rails.css */
2 |
3 | /* @group new sticky footer */
4 | /* http://www.cssstickyfooter.com/ */
5 | html, body {
6 | height: 100%;
7 | }
8 | .container {
9 | min-height: 100%;
10 | }
11 | .main_content {
12 | overflow: auto;
13 | padding-bottom: 96px;
14 | }
15 | .footer {
16 | position: relative;
17 | margin-top: -96px;
18 | height: 96px;
19 | clear: both;
20 | }
21 | /*Opera Fix*/
22 | body:before {
23 | content: "";
24 | height: 100%;
25 | float: left;
26 | width: 0;
27 | margin-top: -32767px;
28 | }
29 | /* @end */
30 | /* main styles */
31 | body {
32 | font: 14px/1.75em "Helvetica Neue", Helvetica, Geneva, sans-serif;
33 | color: #485055;
34 | background: #f1f1f1;
35 | }
36 | a {
37 | color: #3d90e3;
38 | text-decoration: none;
39 | }
40 | a:hover {
41 | text-decoration: underline;
42 | }
43 | a:visited {
44 | color: #2f74bc;
45 | }
46 | h1 {
47 | color: #85939b;
48 | font-size: 200%;
49 | margin-bottom: .5em;
50 | text-transform: capitalize;
51 | text-shadow: 1px 1px 0 #fff;
52 | }
53 | textarea:focus {
54 |
55 | }
56 | .right {
57 | text-align: right;
58 | }
59 | .buttons {
60 | white-space: nowrap;
61 | padding: 12px 0 6px 0;
62 | }
63 | .buttons form {
64 | display: inline-block;
65 | }
66 | .buttons a, a.button, input[type=submit] {
67 | vertical-align: baseline;
68 | margin: 0 2px;
69 | padding: 2px 8px 4px 8px;
70 | background: #99a9b3;
71 | color: #fcfcfc;
72 | font-size: 14px;
73 | line-height: 1.25em;
74 | font-weight: bold;
75 | border: none;
76 | -webkit-border-radius: 4px;
77 | -moz-border-radius: 4px;
78 | border-radius: 4px;
79 | text-decoration: none;
80 | -webkit-box-shadow: 1px 1px 0 rgba(255,255,255,0.4), -1px -1px 0 rgba(0,0,0,0.4);
81 | -moz-box-shadow: 1px 1px 0 rgba(255,255,255,0.4), -1px -1px 0 rgba(0,0,0,0.4);
82 | box-shadow: 1px 1px 0 rgba(255,255,255,0.4), -1px -1px 0 rgba(0,0,0,0.4);
83 | }
84 | .buttons a:hover, a.button:hover, input[type=submit]:hover {
85 | cursor: pointer;
86 | text-shadow: 0 1px 0 rgba(0,0,0,0.4);
87 | }
88 | .buttons a:active, a.button:active {
89 | background: #839099;
90 | }
91 | input[type=submit] {
92 | background: #5d94dd;
93 | -webkit-box-shadow: inset 0px 0px 6px rgba(0,0,0,0.6), 1px 1px 0 rgba(255,255,255,0.4), -1px -1px 0 rgba(0,0,0,0.4);
94 | -moz-box-shadow: inset 0px 0px 6px rgba(0,0,0,0.6), 1px 1px 0 rgba(255,255,255,0.4), -1px -1px 0 rgba(0,0,0,0.4);
95 | box-shadow: inset 0px 0px 6px rgba(0,0,0,0.6), 1px 1px 0 rgba(255,255,255,0.4), -1px -1px 0 rgba(0,0,0,0.4);
96 | }
97 | .header {
98 | height: 64px;
99 | border-top: 6px solid #2f0403;
100 | -webkit-box-shadow: inset 0px 0px 72px rgba(0,0,0,0.6);
101 | -moz-box-shadow: inset 0px 0px 72px rgba(0,0,0,0.6);
102 | box-shadow: inset 0px 0px 72px rgba(0,0,0,0.6);
103 | background: #4776a8;
104 | }
105 | a.home {
106 | margin-top: 7px;
107 | margin-left: 4px;
108 | padding: 8px 16px;
109 | color: #fff;
110 | color: rgba(255,255,255,.75);
111 | display: inline-block;
112 | }
113 | a.home:hover {
114 | color: #fff;
115 | text-decoration: none;
116 | }
117 | a.home img {
118 | }
119 | a.user {
120 | position: absolute;
121 | top: 26px;
122 | right: 24px;
123 | color: #fff;
124 | color: rgba(255,255,255,.75);
125 | font-size: 12px;
126 | background: #980905;
127 | background: rgba(0,0,0,0.25);
128 | }
129 | a.user:hover {
130 | color: #fff;
131 | text-decoration: none;
132 | }
133 | .nav, .nav ul, .nav li, .nav a {
134 | height: 36px;
135 | }
136 | div.nav {
137 | background: #d9d9d9;
138 | background: -webkit-gradient(linear, left top, left bottom,
139 | color-stop(0, #d9d9d9),
140 | color-stop(0.2, #fff),
141 | color-stop(0.53, #fff),
142 | color-stop(0.54, #e2e2e2),
143 | color-stop(1, #c5c5c5));
144 | background: -moz-linear-gradient(top, #d9d9d9, #fff 20%, #fff 53%, #e2e2e2 54%, #c5c5c5 100%);
145 | }
146 | .nav {
147 | display: block;
148 | background: #fff;
149 | padding: 0 24px;
150 | border-top: 1px solid #cde1ed;
151 | border-bottom: 1px solid #acacac;
152 | }
153 | .nav ul {
154 | display: inline-block;
155 | border-left: 1px solid #cde1ed;
156 | border-right: 1px solid #e6c8cc;
157 | white-space: nowrap;
158 | }
159 | .nav li {
160 | display: inline-block;
161 | float: left;
162 | border-left: 1px solid #e6c8cc;
163 | border-right: 1px solid #cde1ed;
164 | }
165 | .nav a:link, .nav a:visited {
166 | display: block;
167 | padding: 0 18px;
168 | color: #7f8c95;
169 | text-transform: capitalize;
170 | font-size: 14px;
171 | line-height: 2.66em;
172 | text-shadow: 1px 1px 0 rgba(255,255,255,0.75);
173 | font-weight: bold;
174 | }
175 | .nav a:hover {
176 | color: #3d90e3;
177 | text-decoration: none;
178 | }
179 | .session {
180 | display: inline-block;
181 | padding: 9px;
182 | background: #fff;
183 | border: 1px solid #abb7c2;
184 | -webkit-border-radius: 4px;
185 | -moz-border-radius: 4px;
186 | border-radius: 4px;
187 | }
188 | .main_content {
189 | padding: 36px 24px;
190 | margin-bottom: 110px;
191 | border-top: 1px solid #fffeff;
192 | background: -webkit-gradient(linear, 0% 0%, 0% 64, from(#d9d9d9), to(#f1f1f1));
193 | background: -moz-linear-gradient(top, #d9d9d9, #f1f1f1 64px);
194 | }
195 |
196 | .footer {
197 | background: #dfe4ec;
198 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#dfe4ec), to(#ccd2dd));
199 | background: -moz-linear-gradient(top, #dfe4ec, #ccd2dd);
200 | }
201 | .footer p {
202 | display: block;
203 | border-top: 1px solid #cacdd6;
204 | padding-top: 6px;
205 | text-align: center;
206 | color: #85939b;
207 | font-size: 12px;
208 | }
209 | .footer a {
210 | color: #485055;
211 | }
212 |
213 | /* tables */
214 | table.list {
215 | width: 100%;
216 | background: #fff;
217 | }
218 | table.list td {
219 | border: 1px solid #abb7c2;
220 | padding: 3px 6px;
221 | }
222 | table.list thead td {
223 | padding: 6px;
224 | color: #85939b;
225 | background: #dfe4ec;
226 | background: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#dfe4ec), to(#f3f5f8));
227 | background: -moz-linear-gradient(top, #dfe4ec, #f3f5f8);
228 | font-weight: bold;
229 | text-shadow: 1px 1px 0 rgba(255,255,255,0.75);
230 | }
231 | table.form {
232 | width: 100%;
233 | }
234 | table.form td {
235 | padding: 3px;
236 | vertical-align: top;
237 | }
238 | table.form textarea {
239 | width: 100%;
240 | }
241 | /* misc. */
242 | input#form_task_regex,
243 | textarea#form_template,
244 | textarea#form_sample_data,
245 | #fluo_editor, textarea#definition_definition {
246 | background: #fff;
247 | border: 1px solid #abb7c2;
248 | -webkit-border-radius: 4px;
249 | -moz-border-radius: 4px;
250 | border-radius: 4px;
251 | padding: 4px;
252 | margin: 2px 0;
253 | width: 100%;
254 | }
255 | input#form_task_regex:focus,
256 | textarea#form_template:focus,
257 | textarea#form_sample_data:focus {
258 | -webkit-box-shadow: 0 0 12px #5d94dd;
259 | -moz-box-shadow: 0 0 12px #5d94dd;
260 | box-shadow: 0 0 12px #5d94dd;
261 | }
262 | #quad, #data_out {
263 | margin: 2px 0;
264 | }
265 | .quad_tab {
266 | border-color: #abb7c2;
267 | -webkit-border-top-right-radius: 4px;
268 | -webkit-border-top-left-radius: 4px;
269 | -moz-border-radius-topright: 4px;
270 | -moz-border-radius-topleft: 4px;
271 | border-top-right-radius: 4px;
272 | border-top-left-radius: 4px;
273 | background: #dfe4ec;
274 | }
275 | .quad_tab.quad_selected {
276 | border-color: #abb7c2;
277 | border-bottom-color: transparent;
278 | }
279 | .quad_tab a {
280 | color: #85939b;
281 | }
282 | .quad_tab.quad_selected a {
283 | color: #485055;
284 | }
285 | tr.quad_tab_group td {
286 | padding: 0;
287 | }
288 | .quad_tab_body {
289 | background: #fff;
290 | border-color: #abb7c2;
291 | -webkit-border-bottom-right-radius: 4px;
292 | -webkit-border-bottom-left-radius: 4px;
293 | -moz-border-radius-bottomright: 4px;
294 | -moz-border-radius-bottomleft: 4px;
295 | border-bottom-right-radius: 4px;
296 | border-bottom-left-radius: 4px;
297 | }
298 | div.quad_key, span.quad_key {
299 | color: #485055;
300 | }
301 | #flow {
302 | float: right;
303 | background: #fff;
304 | border: 1px solid #abb7c2;
305 | -webkit-border-radius: 4px;
306 | -moz-border-radius: 4px;
307 | border-radius: 4px;
308 | padding: 4px;
309 | }
310 | #form {
311 | float: left;
312 | margin-right: 14px;
313 | }
314 | #textbox textarea, #fluo_editor {
315 | width: 560px;
316 | margin-top: 0;
317 | }
318 | #editor .tab, #textbox .tab {
319 | font-size: 90%;
320 | width: 548px;
321 | padding: 1px 6px;
322 | text-align: right;
323 | margin-bottom: -2em;
324 | z-index: 1;
325 | position: relative;
326 | }
327 | pre#definition {
328 | background: #fff;
329 | border: 1px solid #abb7c2;
330 | -webkit-border-radius: 4px;
331 | -moz-border-radius: 4px;
332 | border-radius: 4px;
333 | width: 60%;
334 | font: 12px/1.3 Monaco, mono;
335 | padding: 3px;
336 | margin: 12px 0px;
337 | }
--------------------------------------------------------------------------------
/public/javascripts/quaderno.js:
--------------------------------------------------------------------------------
1 | //
2 | // Copyright (c) 2010, John Mettraux, jmettraux@gmail.com
3 | //
4 | // Permission is hereby granted, free of charge, to any person obtaining a copy
5 | // of this software and associated documentation files (the "Software"), to deal
6 | // in the Software without restriction, including without limitation the rights
7 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | // copies of the Software, and to permit persons to whom the Software is
9 | // furnished to do so, subject to the following conditions:
10 | //
11 | // The above copyright notice and this permission notice shall be included in
12 | // all copies or substantial portions of the Software.
13 | //
14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | // THE SOFTWARE.
21 | //
22 |
23 | // depends on the excellent jquery[-1.4.2]
24 |
25 |
26 | var Quaderno = function () {
27 |
28 | //
29 | // misc
30 |
31 | function clog (o) {
32 | try {
33 | if (arguments.length == 1) console.log(arguments[0]);
34 | else console.log(arguments);
35 | }
36 | catch (e) {
37 | //if (navigator.userAgent...)
38 | if (arguments.length == 1) print(JSON.stringify(arguments[0]));
39 | else print(JSON.stringify(arguments));
40 | }
41 | }
42 |
43 | function deepCopy (o) {
44 | return JSON.parse(JSON.stringify(o));
45 | }
46 |
47 | function removeClassDot (cname) {
48 | return (cname.match(/^\./)) ? cname.slice(1) : cname;
49 | }
50 |
51 | function hide (container, cname, value) {
52 | cname = removeClassDot(cname);
53 | return create(
54 | container, 'input', { 'class': cname, 'type': 'hidden', 'value': value });
55 | }
56 |
57 | function create (container, tagName, attributes, innerText) {
58 |
59 | var atts = attributes || {};
60 |
61 | if (attributes && ((typeof attributes) === 'string')) {
62 | atts = { 'class': attributes };
63 | }
64 | if (atts['class']) {
65 | atts['class'] = $.trim((atts['class'] || '').split('.').join(' '));
66 | }
67 |
68 | var e = $('<' + tagName + '/>', atts)[0];
69 |
70 | if (innerText) {
71 | //e.innerHTML = innerText;
72 | // doesn't work with Safari and doesn't escape text
73 | e.appendChild(document.createTextNode(innerText));
74 | // is fine
75 | }
76 |
77 | if (container) {
78 | container.appendChild(e);
79 | }
80 |
81 | return e;
82 | }
83 |
84 | function button (container, cname, onclick, title) {
85 |
86 | if ( ! onclick.match(/return false;$/)) onclick += " return false;";
87 | var m = cname.match(/quad_([^_]+)_button/);
88 |
89 | title = title || m ? {
90 | 'plus': translate(container, 'quaderno.button.add', 'add'),
91 | 'minus': translate(container, 'quaderno.button.remove', 'remove'),
92 | 'up': translate(container, 'quaderno.button.up', 'move up'),
93 | 'down': translate(container, 'quaderno.button.down', 'move down'),
94 | 'copy': translate(container, 'quaderno.button.duplicate', 'duplicate'),
95 | //'cut': translate(container, 'quaderno.button.cut', 'cut'),
96 | //'paste': translate(container, 'quaderno.button.paste', 'paste'),
97 | //'go': translate(container, 'quaderno.button.go', 'go'),
98 | //'left': translate(container, 'quaderno.button.left', 'left'),
99 | //'right': translate(container, 'quaderno.button.right', 'right')
100 | }[m[1]] : undefined;
101 |
102 | return create(
103 | container,
104 | 'a',
105 | { 'href': '',
106 | 'class': cname + '.quad_button',
107 | 'title': title,
108 | 'onClick': onclick });
109 | }
110 |
111 | //
112 | // lookup and set
113 |
114 | function lookup (coll, key) {
115 |
116 | //clog([ "lu", key, coll ]);
117 |
118 | if (coll === undefined) return undefined;
119 | if (key === undefined) return undefined;
120 |
121 | if ( ! $.isArray(key)) key = key.split('.');
122 | if (key.length < 1) return coll;
123 |
124 | return lookup(coll[key.shift()], key);
125 | }
126 |
127 | function set (coll, key, value) {
128 |
129 | //clog([ "set", hash, key, value ]);
130 |
131 | if ( ! key) return;
132 |
133 | if ( ! $.isArray(key)) key = key.split('.');
134 |
135 | var k = key.shift();
136 |
137 | if (key.length === 0) {
138 | coll[k] = value;
139 | return;
140 | }
141 |
142 | var scoll = coll[k];
143 |
144 | if (
145 | scoll === undefined &&
146 | ($.isArray(coll) || ((typeof coll) === 'object'))
147 | ) {
148 | var o = (key[0] && key[0].match(/^\d+$/)) ? [] : {};
149 | coll[k] = o;
150 | scoll = coll[k];
151 | }
152 |
153 | set(scoll, key, value);
154 | }
155 |
156 | //
157 | // parsing
158 |
159 | function parseAttributes (s) {
160 |
161 | // id "text" value \[ values \] "title" [disabled]
162 |
163 | // TODO : >value< if necessary
164 |
165 | var atts = {};
166 | var m;
167 |
168 | // id
169 |
170 | if ((typeof s) !== 'string') return atts;
171 |
172 | m = s.match(/^([^ "]+) ?(.+)?$/)
173 |
174 | if (m && m[1]) {
175 | atts.id = m[1];
176 | s = m[2] || '';
177 | }
178 |
179 | // "text"
180 |
181 | if ((typeof s) !== 'string') return atts;
182 |
183 | m = s.match(/^"([^"]+)" ?(.+)?$/)
184 |
185 | if (m && m[1]) {
186 | atts.text = m[1];
187 | s = m[2] || '';
188 | }
189 |
190 | // values
191 |
192 | m = s.match(/^(\[.+\]) ?(.+)?$/)
193 |
194 | if (m && m[1]) {
195 | var vs = m[1].slice(1, -1).split(',');
196 | var values = [];
197 | for (var i = 0; i < vs.length; i++) { values.push($.trim(vs[i])); }
198 | atts.values = values.length === 1 ? values[0] : values;
199 | s = m[2] || '';
200 | }
201 |
202 | // title
203 |
204 | m = s.match(/^"([^"]+)" ?(.+)?$/)
205 |
206 | if (m) {
207 | atts.title = m[1];
208 | s = m[2] || '';
209 | }
210 |
211 | // disabled
212 |
213 | if (s.match(/disabled$/)) atts.disabled = true;
214 |
215 | return atts;
216 | }
217 |
218 | function parse (s) {
219 |
220 | var lines = s.split('\n');
221 |
222 | var current;
223 | var clevel = -1;
224 | var definitions = {};
225 |
226 | for (var i = 0; i < lines.length; i++) {
227 |
228 | var line = lines[i];
229 | var tline = $.trim(line);
230 |
231 | if (tline == '') continue;
232 | if (tline.match(/^\/\//)) continue; // // comment line
233 | if (tline.match(/^#/)) continue; // # comment line
234 |
235 | var m = line.match(/^([ ]*)([^ ]+) ?(.+)?\r?$/)
236 | // notice the \r?
237 |
238 | var nlevel = m[1].length / 2;
239 |
240 | var key = $.trim(m[2]);
241 |
242 | var def = definitions[key];
243 | var elt = [ key, parseAttributes($.trim(m[3])), [] ];
244 |
245 | if (nlevel > clevel) {
246 | elt.parent = current;
247 | }
248 | else if (nlevel == clevel) {
249 | elt.parent = current.parent;
250 | }
251 | else /* nlevel < clevel */ {
252 | for (var j = 0; j <= clevel - nlevel; j++) {
253 | current = current.parent;
254 | }
255 | elt.parent = current;
256 | }
257 |
258 | if (def) {
259 | def = deepCopy(def);
260 | for (var j = 0; j < def.length; j++) {
261 | elt.parent[2].push(def[j]);
262 | }
263 | }
264 | else if (elt[0] === 'define') {
265 | definitions[elt[1].id] = elt[2];
266 | }
267 | else if (elt.parent) { // don't place macros in parent
268 | elt.parent[2].push(elt);
269 | }
270 |
271 | current = elt;
272 | clevel = nlevel;
273 | }
274 |
275 | // get back to 'root'
276 |
277 | while (current.parent) { current = current.parent; }
278 |
279 | // done
280 |
281 | return current;
282 | }
283 |
284 | //
285 | // rendering and producing
286 |
287 | var renderers = {};
288 | var handlers = {};
289 |
290 | renderers.render_ = function (container, template, data, options) {
291 | create(container, 'span', {}, JSON.stringify(template));
292 | }
293 |
294 | function renderChildren (container, template, data, options) {
295 | for (var i = 0; i < template[2].length; i++) {
296 | renderElement(container, template[2][i], data, options);
297 | }
298 | }
299 |
300 | renderers.produce_ = function (container, data) {
301 | var type = childValue(container, '.quad_type');
302 | if ( ! data._quad_produce_failures) data._quad_produce_failures = [];
303 | data._quad_produce_failures.push("can't deal with '" + type + "'");
304 | }
305 |
306 | function produceChildren (container, data) {
307 | $(container).children('.quad_element').each(function (i, e) {
308 | produceElement(e, data, i);
309 | });
310 | }
311 |
312 | renderers.produce__array = function (container, data) {
313 |
314 | produceChildren(container, data);
315 |
316 | // truncate array to desired length if necessary
317 |
318 | var a = lookup(data, currentId(container));
319 | var targetLength = $(container).children('.quad_element').length;
320 |
321 | while (a && a.length > targetLength) { a.pop(); }
322 | }
323 |
324 | function translate (elt, text, def) {
325 |
326 | if ($.isArray(text)) text = text[0];
327 |
328 | if ( ! text) return def;
329 | if (text.match(/\s/)) return def || text;
330 |
331 | var opts = root(elt).options;
332 | var t = lookup(opts.translations[opts.lang || 'en'], text)
333 | return ((typeof t) === 'string') ? t : def || text;
334 | }
335 |
336 | function getKey (container, template, data, id) {
337 |
338 | var text = template[1].text;
339 |
340 | if (text) {
341 | if (text.match(/^\./)) {
342 | var key = currentId(container, text);
343 | var d = lookup(data, key);
344 | if (d) return d;
345 | }
346 | return translate(container, text);
347 | }
348 |
349 | text = translate(container, id)
350 |
351 | if (text && text !== id) return text;
352 |
353 | return template[1].id;
354 | }
355 |
356 | //
357 | // stacking for undoing
358 |
359 | function stack (elt) {
360 |
361 | var r = root(elt);
362 | var firstElt = $(r).children('.quad_element')[0];
363 | var copy = firstElt.cloneNode(true);
364 |
365 | var notFirstPush = r.stack.length > 0;
366 |
367 | r.stack.push(copy);
368 | while (r.stack.length > 14) r.stack.shift();
369 |
370 | var callback = r.onQuadernoChange;
371 | if (notFirstPush && callback) {
372 | var product = (callback.length > 1) ? produce(r) : null;
373 | callback(elt, product);
374 | }
375 | }
376 |
377 | handlers.stackOnKey = function (elt) {
378 |
379 | if (elt.stacked) return;
380 |
381 | $(elt).attr('value', elt.value);
382 | stack(elt);
383 | elt.stacked = true;
384 | $(elt).val('');
385 | }
386 |
387 | handlers.stackOnClick = function (elt) {
388 |
389 | // checkboxes
390 |
391 | var $elt = $(elt);
392 |
393 | var checked = $elt.attr('checked');
394 | $elt.attr('checked', ! checked);
395 | stack(elt);
396 | $elt.attr('checked', checked);
397 | }
398 |
399 | handlers.stackOnChange = function (elt) {
400 |
401 | var $elt = $(elt);
402 | var tagname = elt.tagName.toLowerCase();
403 |
404 | if (elt.type === 'text') {
405 |
406 | elt.stacked = false;
407 | }
408 | else if (tagname === 'select') {
409 |
410 | var newValue = elt.value;
411 | setSelectValue(elt, elt.previousValue);
412 | stack(elt);
413 | setSelectValue(elt, newValue);
414 | elt.previousValue = newValue;
415 | }
416 | else if (tagname === 'textarea') {
417 |
418 | stack(elt);
419 | $elt.text(elt.value);
420 | }
421 | }
422 |
423 | //
424 | // select helpers
425 |
426 | // Setting the value in hard (to make cloneNode()'s work easier...
427 | //
428 | function setSelectValue (sel, value) {
429 |
430 | value = '' + value;
431 |
432 | var opts = $(sel).children('option');
433 |
434 | for (var i = 0; i < opts.length; i++) {
435 |
436 | var opt = opts[i]; var $opt = $(opt);
437 |
438 | var atts = { 'value': $opt.attr('value') };
439 | if ($opt.attr('value') === value) atts.selected = 'selected';
440 | var text = $opt.text();
441 |
442 | $opt.remove();
443 |
444 | create(sel, 'option', atts, text);
445 | }
446 | }
447 |
448 | function createSelect (container, cname) {
449 |
450 | return create(
451 | container,
452 | 'select',
453 | { 'class': cname,
454 | 'onFocus': 'this.previousValue = this.value;',
455 | 'onChange': 'Quaderno.handlers.stackOnChange(this);' });
456 | }
457 |
458 | //
459 | // select
460 |
461 | renderers.render_select = function (container, template, data, options) {
462 |
463 | var id = currentId(container);
464 |
465 | create(
466 | container, 'span', '.quad_key', getKey(container, template, data, id));
467 |
468 | var select = createSelect(container, '.quad_value');
469 |
470 | if (id) select.id = 'quad__' + id.replace(/[\.]/, '_', 'g');
471 | // for webrat / capybara
472 |
473 | var value = lookup(data, id);
474 | var values = template[1].values || [];
475 |
476 | if ( ! $.isArray(values)) values = lookup(data, values);
477 | if ( ! $.isArray(values)) values = [ '' + values ];
478 |
479 | for (var i = 0; i < values.length; i++) {
480 |
481 | var v = values[i];
482 | var t = translate(container, v);
483 | if (t && v !== t && ( ! $.isArray(v))) v = v.match(/[^\.]+$/)[0];
484 | if ($.isArray(v)) v = v[1];
485 |
486 | var opt = create(select, 'option', { 'value': v }, t);
487 |
488 | //if (value && values[i] === value) $(opt).attr('selected', 'selected');
489 | }
490 |
491 | if (value) setSelectValue(select, value);
492 |
493 | if (template[1].disabled || options.mode === 'view') {
494 | $(select).attr('disabled', 'disabled');
495 | }
496 | }
497 |
498 | renderers.produce_select = function (container, data) {
499 |
500 | var sel = $(container).children('.quad_value')[0];
501 | set(data, currentId(container), sel.value);
502 | }
503 |
504 | //
505 | // checkbox
506 |
507 | renderers.render_checkbox = function (container, template, data, options) {
508 |
509 | var id = currentId(container);
510 | //var text = template[1].text || template[1].id;
511 |
512 | var checkbox = create(
513 | container,
514 | 'input',
515 | { 'class': 'quad_checkbox',
516 | 'type': 'checkbox',
517 | 'onClick': 'Quaderno.handlers.stackOnClick(this);' });
518 |
519 | if (id) {
520 |
521 | checkbox.id = 'quad__' + id.replace(/[\.]/, '_', 'g');
522 | // for webrat / capybara
523 |
524 | var value = lookup(data, id) || '';
525 | }
526 |
527 | if (value === true) $(checkbox).attr('checked', 'checked');
528 |
529 | if (template[1].disabled || options.mode === 'view') {
530 | $(checkbox).attr('disabled', 'disabled');
531 | }
532 |
533 | create(
534 | container, 'span', '.quad_checkbox_key', getKey(
535 | container, template, data, id));
536 | }
537 |
538 | renderers.produce_checkbox = function (container, data) {
539 |
540 | var cb = $(container).children('.quad_checkbox')[0];
541 | set(data, currentId(container), $(cb).attr('checked'));
542 | }
543 |
544 | //
545 | // text_input
546 |
547 | renderers.render_text_input = function (container, template, data, options) {
548 |
549 | var id = currentId(container);
550 |
551 | create(
552 | container, 'span', '.quad_key', getKey(container, template, data, id));
553 |
554 | var input = create(
555 | container,
556 | 'input',
557 | { 'class': 'quad_value',
558 | 'type': 'text',
559 | 'onKeyPress': 'Quaderno.handlers.stackOnKey(this);',
560 | 'onChange': 'Quaderno.handlers.stackOnChange(this);' });
561 |
562 | if (id) {
563 |
564 | input.id = 'quad__' + id.replace(/[\.]/, '_', 'g');
565 | // for webrat / capybara
566 |
567 | $(input).attr('value', lookup(data, id) || '');
568 | }
569 |
570 | if (template[1].disabled || options.mode === 'view') {
571 | $(input).attr('disabled', 'disabled');
572 | }
573 | }
574 |
575 | renderers.produce_text_input = function (container, data) {
576 | var value = childValue(container, '.quad_value');
577 | set(data, currentId(container), value);
578 | }
579 |
580 | //
581 | // text_area
582 |
583 | renderers.render_text_area = function (container, template, data, options) {
584 |
585 | var id = currentId(container);
586 |
587 | create(
588 | container, 'span', '.quad_key', getKey(container, template, data, id));
589 |
590 | var value = '';
591 | var aid = '';
592 |
593 | if (id) {
594 |
595 | value = lookup(data, id) || '';
596 |
597 | aid = 'quad__' + id.replace(/[\.]/, '_', 'g');
598 | // for webrat / capybara
599 | }
600 |
601 | var area = create(
602 | container,
603 | 'textarea',
604 | { 'id': aid,
605 | 'class': 'quad_value',
606 | 'onKeyPress' : 'Quaderno.handlers.stackOnKey(this);',
607 | 'onChange': 'Quaderno.handlers.stackOnChange(this);' },
608 | value);
609 |
610 | if (template[1].disabled || options.mode === 'view') {
611 | $(area).attr('disabled', 'disabled');
612 | }
613 | }
614 |
615 | renderers.produce_text_area = function (container, data) {
616 | var value = childValue(container, '.quad_value');
617 | set(data, currentId(container), value);
618 | }
619 |
620 | //
621 | // text
622 |
623 | renderers.render_text = function (container, template, data, options) {
624 |
625 | var id = currentId(container);
626 |
627 | if (template[1].id) {
628 | create(
629 | container, 'span', '.quad_key', getKey(container, template, data, id));
630 | create(
631 | container, 'span', '.quad_value.quad_text', lookup(data, id));
632 | }
633 | else {
634 | var text = template[1].text || lookup(data, id) || '';
635 | create(
636 | container, 'div', '.quad_key.quad_text', translate(container, text));
637 | }
638 | }
639 |
640 | renderers.produce_text = function (container, data) {
641 | // nothing to do
642 | }
643 |
644 | //
645 | // date
646 |
647 | renderers.render_date = function (container, template, data, options) {
648 |
649 | var id = currentId(container);
650 |
651 | create(
652 | container, 'span', '.quad_key', getKey(container, template, data, id));
653 |
654 | var type = template[0].split('_')[1] || 'ymd';
655 |
656 | // year
657 |
658 | var year;
659 |
660 | if (type.match(/y/)) {
661 |
662 | create(
663 | container, 'span', '.quad_date_separator',
664 | translate(container, 'date.y', 'y'));
665 |
666 | var y = (new Date()).getYear() + 1900;
667 | year = createSelect(container, '.quad_date_year');
668 | for (var i = 2000; i < 2050; i++) {
669 | create(year, 'option', { 'value': '' + i }, i);
670 | }
671 | setSelectValue(year, y);
672 | $(year).attr('onChange', 'Quaderno.handlers.checkDate(this, "' + type + '");');
673 |
674 | if (id) { // for webrat / capybara
675 | year.id = 'quad__' + id.replace(/[\.]/, '_', 'g') + '__year';
676 | }
677 | }
678 |
679 | // month
680 |
681 | var month;
682 |
683 | if (type.match(/m/)) {
684 |
685 | create(
686 | container, 'span', '.quad_date_separator',
687 | translate(container, 'date.m', 'm'));
688 |
689 | month = createSelect(container, '.quad_date_month');
690 | for (var i = 1; i <= 12; i++) {
691 | create(month, 'option', { 'value': '' + i }, i);
692 | }
693 | $(month).attr('onChange', 'Quaderno.handlers.checkDate(this, "' + type + '");');
694 |
695 | if (id) { // for webrat / capybara
696 | month.id = 'quad__' + id.replace(/[\.]/, '_', 'g') + '__month';
697 | }
698 | }
699 |
700 | // day
701 |
702 | var day;
703 |
704 | if (type.match(/d/)) {
705 |
706 | create(
707 | container, 'span', '.quad_date_separator',
708 | translate(container, 'date.d', 'd'));
709 |
710 | day = createSelect(container, '.quad_date_day');
711 | for (var i = 1; i <= 31; i++) {
712 | create(day, 'option', { 'value': '' + i }, i);
713 | }
714 |
715 | if (id) { // for webrat / capybara
716 | day.id = 'quad__' + id.replace(/[\.]/, '_', 'g') + '__day';
717 | }
718 | }
719 |
720 | // set value
721 |
722 | var value = lookup(data, id);
723 |
724 | if (value) {
725 |
726 | value = value.split('/');
727 |
728 | if (year) setSelectValue(year, new Number(value.shift()));
729 | if (month) setSelectValue(month, new Number(value.shift()));
730 | if (day) setSelectValue(day, new Number(value.shift()));
731 | }
732 |
733 | // mode view => disable
734 |
735 | if (template[1].disabled || options.mode === 'view') {
736 |
737 | if (year) $(year).attr('disabled', 'disabled');
738 | if (month) $(month).attr('disabled', 'disabled');
739 | if (day) $(day).attr('disabled', 'disabled');
740 | }
741 | }
742 | renderers.render_date_ymd = renderers.render_date;
743 | renderers.render_date_y = renderers.render_date;
744 | renderers.render_date_ym = renderers.render_date;
745 | renderers.render_date_md = renderers.render_date;
746 |
747 | renderers.produce_date = function (container, data) {
748 |
749 | var dateElt = $(container);
750 | var year = dateElt.children('.quad_date_year')[0];
751 | var month = dateElt.children('.quad_date_month')[0];
752 | var day = dateElt.children('.quad_date_day')[0];
753 |
754 | var a = [];
755 | if (year) a.push(year.value);
756 | if (month) a.push(month.value);
757 | if (day) a.push(day.value);
758 |
759 | set(data, currentId(container), a.join('/'));
760 | }
761 | renderers.produce_date_ymd = renderers.produce_date;
762 | renderers.produce_date_y = renderers.produce_date;
763 | renderers.produce_date_ym = renderers.produce_date;
764 | renderers.produce_date_md = renderers.produce_date;
765 |
766 | var MD = [ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
767 |
768 | function isLeapYear (year) {
769 | var d = new Date();
770 | d.setFullYear(year, 1, 29);
771 | return (d.getMonth() == 1);
772 | }
773 |
774 | handlers.checkDate = function (elt, type) {
775 |
776 | handlers.stackOnChange(elt);
777 |
778 | if ( ! type.match(/d/)) return;
779 |
780 | var dateElt = $(elt.parentNode);
781 | var year = dateElt.children('.quad_date_year')[0];
782 | var month = dateElt.children('.quad_date_month')[0];
783 | var day = dateElt.children('.quad_date_day')[0];
784 |
785 | if (type === 'ymd') {
786 |
787 | var d = new Date();
788 |
789 | d.setFullYear(
790 | parseInt(year.value), parseInt(month.value) - 1, parseInt(day.value));
791 |
792 | setSelectValue(year, d.getFullYear());
793 | setSelectValue(month, d.getMonth() + 1);
794 | setSelectValue(day, d.getDate());
795 | }
796 |
797 | // adjust days (february and co)
798 |
799 | var d = day.value;
800 |
801 | while (day.firstChild) { day.removeChild(day.firstChild); }
802 |
803 | var days = MD[month.value];
804 | if (
805 | month && month.value == 2 && year && isLeapYear(year.value)
806 | ) days = days + 1;
807 |
808 | for (var i = 1; i <= days; i++) {
809 | create(day, 'option', { 'value': '' + i }, i);
810 | }
811 |
812 | setSelectValue(day, d);
813 | }
814 |
815 | //
816 | // box
817 |
818 | renderers.render_box = function (container, template, data, options) {
819 |
820 | $(container).addClass('quad_box');
821 |
822 | var label = getKey(container, template, data, currentId(container));
823 |
824 | if (label != template[1].id) {
825 | create(container, 'span', '.quad_label', label);
826 | }
827 |
828 | renderChildren(container, template, data, options);
829 | }
830 |
831 | renderers.produce_box = function (container, data) {
832 | produceChildren(container, data);
833 | }
834 |
835 | //
836 | // group
837 |
838 | renderers.render_group = function (container, template, data, options) {
839 |
840 | var label = getKey(container, template, data, currentId(container));
841 |
842 | if (label != template[1].id) {
843 | create(container, 'span', '.quad_label', label);
844 | }
845 |
846 | renderChildren(container, template, data, options);
847 | }
848 |
849 | renderers.produce_group = function (container, data) {
850 | produceChildren(container, data);
851 | }
852 |
853 | //
854 | // tabs
855 |
856 | renderers.render_tab = function (container, template, data, options) {
857 | renderChildren(container, template, data, options);
858 | }
859 |
860 | renderers.render_tab_label = function (container, template, data, options) {
861 |
862 | var td = create(container, 'td', '.quad_tab');
863 |
864 | var id = currentId(container);
865 | var text = template[1].text || template[1].id;
866 |
867 | var a = $(create(td, 'a', {}, translate(container, text)));
868 | a.attr('href', '');
869 | a.attr('onClick', 'return Quaderno.handlers.showTab(this.parentNode);');
870 |
871 | return td;
872 | }
873 |
874 | renderers.render_tabs = function (container, template, data, options) {
875 |
876 | var tabs = template[2];
877 |
878 | var table = create(container, 'table', '.quad_tab_group');
879 |
880 | // tabs
881 |
882 | var tr0 = create(table, 'tr', '.quad_tab_group');
883 |
884 | for (var i = 0; i < tabs.length; i++) {
885 | renderers.render_tab_label(tr0, tabs[i], data, options);
886 | }
887 |
888 | var tab = $(tr0).find('td.quad_tab')[0];
889 | $(tab).addClass('quad_selected');
890 |
891 | // content
892 |
893 | var tr = create(table, 'tr', '.quad_tab_group');
894 | var td = create(tr, 'td', { 'colspan': tabs.length });
895 | var qtb = create(td, 'div', '.quad_tab_body');
896 |
897 | for (var i = 0; i < tabs.length; i++) {
898 | var div = renderElement(qtb, tabs[i], data, options);
899 | if (i != 0) div.style.display = 'none';
900 | }
901 |
902 | return table;
903 | }
904 |
905 | function computeSiblingOffset (elt, sel) {
906 | var cs = $(elt.parentNode).children(sel);
907 | for (var i = 0; i < cs.length; i++) {
908 | if (cs[i] == elt) return i;
909 | }
910 | return -1;
911 | }
912 | function findTabBody (elt) {
913 | //var td = $(elt).parents('td')[0];
914 | var td = elt[0];
915 | var index = computeSiblingOffset(td);
916 | var table = $(elt).parents('table')[0];
917 | var tr = $(table).children('tr')[1];
918 | return $(tr).find('td > .quad_tab_body > .quad_element')[index];
919 | }
920 |
921 | function showTab (td) {
922 |
923 | for (var i = 0; i < td.parentNode.children.length; i++) {
924 | var tab = $(td.parentNode.children[i]).children('.quad_tab');
925 | tab.removeClass('quad_selected');
926 | }
927 | //var tab = $(td).children('.quad_tab');
928 | var tab = $(td);
929 | tab.addClass('quad_selected');
930 |
931 | var tab_body = findTabBody(tab);
932 |
933 | for (var i = 0; i < tab_body.parentNode.children.length; i++) {
934 | tab_body.parentNode.children[i].style.display = 'none';
935 | }
936 | tab_body.style.display = 'block';
937 |
938 | return false; // no further HTTP request...
939 | }
940 | handlers.showTab = showTab;
941 |
942 | renderers.produce_tabs = function (elt, data) {
943 | var body = $(elt).find('.quad_tab_body')[0];
944 | produceChildren(body, data);
945 | }
946 |
947 | renderers.produce_tab = function (elt, data) {
948 | produceChildren(elt, data);
949 | }
950 |
951 | //
952 | // array handlers
953 |
954 | function addRemoveButton (elt) {
955 | button(
956 | elt,
957 | '.quad_minus_button.array_remove_button',
958 | 'Quaderno.handlers.removeFromArray(this);');
959 | }
960 | function addReorderButtons (elt) {
961 | button(
962 | elt,
963 | '.quad_up_button.array_move_button',
964 | 'Quaderno.handlers.moveInArray(this, "up");');
965 | button(
966 | elt,
967 | '.quad_down_button.array_move_button',
968 | 'Quaderno.handlers.moveInArray(this, "down");');
969 | }
970 | function addDuplicateButton (elt) {
971 | button(
972 | elt,
973 | '.quad_copy_button.array_duplicate_button',
974 | 'Quaderno.handlers.duplicateInArray(this);');
975 | }
976 |
977 | handlers.addToArray = function (elt) {
978 |
979 | stack(elt);
980 |
981 | var t = JSON.parse(childValue(elt.parentNode, '.quad_array_template'));
982 | var tid = t[1].id;
983 | t[1].id = '.0';
984 |
985 | var r = root(elt);
986 |
987 | renderElement(elt.parentNode, t, r.data, r.options);
988 | elt.parentNode.insertBefore(elt.nextSibling, elt);
989 |
990 | if (tid.match(/[\*-]/)) addRemoveButton(elt.previousSibling);
991 | if (tid.match(/\^$/)) addReorderButtons(elt.previousSibling);
992 | if (tid.match(/[\*\+]/)) addDuplicateButton(elt.previousSibling);
993 | }
994 |
995 | handlers.removeFromArray = function (elt) {
996 |
997 | stack(elt);
998 |
999 | $(elt.parentNode).remove();
1000 | }
1001 |
1002 | handlers.moveInArray = function (elt, direction) {
1003 |
1004 | stack(elt);
1005 |
1006 | elt = elt.parentNode;
1007 |
1008 | if (direction === 'up') {
1009 | if (elt.previousSibling) {
1010 | elt.parentNode.insertBefore(elt, elt.previousSibling);
1011 | }
1012 | }
1013 | else if (elt.nextSibling) {
1014 | elt.parentNode.insertBefore(elt.nextSibling, elt);
1015 | }
1016 | }
1017 |
1018 | handlers.duplicateInArray = function (elt) {
1019 |
1020 | stack(elt);
1021 |
1022 | elt = elt.parentNode;
1023 |
1024 | elt.parentNode.insertBefore(elt.cloneNode(true), elt);
1025 | }
1026 |
1027 | //
1028 | // render and produce, surface methods
1029 |
1030 | function root (elt) {
1031 | var $elt = $(elt);
1032 | if ($elt.hasClass('quad_root')) return elt;
1033 | return $elt.parents('.quad_root')[0];
1034 | }
1035 |
1036 | function childValue (elt, cname) {
1037 | return $(elt).children(cname)[0].value;
1038 | }
1039 |
1040 | function localId (elt) {
1041 | var e = $(elt).children('.quad_id')[0];
1042 | if ( ! e) return undefined;
1043 | return $(e).attr('value');
1044 | }
1045 |
1046 | function parentId (elt) {
1047 | return elt.parentNode ? localId(elt.parentNode) : undefined;
1048 | }
1049 |
1050 | function currentId (elt, lId) {
1051 |
1052 | var id = lId || localId(elt);
1053 |
1054 | if ( ! id) {
1055 | if (elt.parentNode) return currentId(elt.parentNode);
1056 | return undefined;
1057 | }
1058 |
1059 | if (id.match(/^\./) && elt.parentNode) {
1060 | if (id === '.0') id = '.' + computeSiblingOffset(elt, '.quad_element');
1061 | return currentId(elt.parentNode) + id;
1062 | }
1063 |
1064 | return id;
1065 | }
1066 |
1067 | function toElement (x) {
1068 |
1069 | if ((typeof x) !== 'string') return x;
1070 |
1071 | if (x.match(/^#/)) x = x.slice(1);
1072 | return document.getElementById(x);
1073 | }
1074 |
1075 | function extractArrayId (div, template) {
1076 |
1077 | var id = template[1].id;
1078 | if ( ! id) return undefined;
1079 |
1080 | var m = id.match(/(.+\.)([*+-])?(\^)?$/);
1081 | if ( ! m) return undefined;
1082 |
1083 | var h = {};
1084 | h.originalId = id;
1085 | h.id = m[1];
1086 | h.slicedId = m[1].slice(0, -1);
1087 | h.canAdd = (m[2] === '*' || m[2] === '+');
1088 | h.canRemove = (m[2] === '*' || m[2] === '-');
1089 | h.canReorder = (m[3] === '^');
1090 |
1091 | return h;
1092 | }
1093 |
1094 | function renderElement (container, template, data, options) {
1095 |
1096 | var func = renderers['render_' + template[0]] || renderers['render_'];
1097 |
1098 | var div = create(container, 'div', '.quad_element');
1099 |
1100 | var arrayId = extractArrayId(container, template);
1101 |
1102 | if (arrayId) {
1103 | //
1104 | // array
1105 |
1106 | hide(div, '.quad_id', arrayId.slicedId);
1107 | hide(div, '.quad_type', '_array');
1108 | hide(div, '.quad_array_template', JSON.stringify(template));
1109 |
1110 | var a = lookup(data, currentId(div));
1111 |
1112 | if (a) {
1113 | for (var i = 0; i < a.length; i++) {
1114 |
1115 | templ = deepCopy(template);
1116 |
1117 | templ[1].id = '.0';
1118 | var e = renderElement(div, templ, data, options);
1119 |
1120 | if (arrayId.canRemove) addRemoveButton(e);
1121 | if (arrayId.canReorder) addReorderButtons(e);
1122 | if (arrayId.canAdd) addDuplicateButton(e);
1123 | }
1124 | }
1125 | if (arrayId.canAdd) {
1126 | button(div, '.quad_plus_button', 'Quaderno.handlers.addToArray(this);');
1127 | }
1128 |
1129 | return div;
1130 | }
1131 |
1132 | // vanilla stuff, no repetition
1133 |
1134 | var id = template[1].id;
1135 |
1136 | if (id) hide(div, '.quad_id', id);
1137 |
1138 | if (template[1].title) $(div).attr('title', template[1].title);
1139 | hide(div, '.quad_type', template[0]);
1140 |
1141 | func(div, template, data, options);
1142 |
1143 | return div;
1144 | }
1145 |
1146 | function produceElement (container, data) {
1147 |
1148 | var type = childValue(container, '.quad_type');
1149 | var func = renderers['produce_' + type] || renderers['produce_'];
1150 |
1151 | func(container, data);
1152 | }
1153 |
1154 | function render (container, template, data, options) {
1155 |
1156 | container = toElement(container);
1157 |
1158 | options = options || {};
1159 | options.translations = options.translations || {};
1160 |
1161 | container.data = data;
1162 | container.options = options;
1163 | container.stack = [];
1164 |
1165 | while (container.firstChild) container.removeChild(container.firstChild);
1166 |
1167 | if ((typeof template) === 'string') template = parse(template);
1168 | renderElement(container, template, data, options);
1169 |
1170 | stack(container);
1171 | container.original = container.stack[0].cloneNode(true);
1172 | }
1173 |
1174 | function produce (container, data) {
1175 |
1176 | container = toElement(container);
1177 |
1178 | data = data || container.data;
1179 |
1180 | produceElement($(container).children('.quad_element')[0], data, 0);
1181 |
1182 | return data;
1183 | }
1184 |
1185 | function undo (container) {
1186 | container = toElement(container);
1187 | while (container.firstChild) container.removeChild(container.firstChild);
1188 | var tree = container.stack.pop() || container.original.cloneNode(true);
1189 | container.appendChild(tree);
1190 | }
1191 |
1192 | function reset (container) {
1193 | container = toElement(container);
1194 | while (container.firstChild) container.removeChild(container.firstChild);
1195 | container.appendChild(container.original.cloneNode(true));
1196 | }
1197 |
1198 | return {
1199 |
1200 | VERSION: '1.3.1',
1201 |
1202 | // only for testing
1203 | //
1204 | _lookup: lookup,
1205 | _set: set,
1206 |
1207 | // The hash of all the rendering functions, ready for insertion of new
1208 | // render_x functions or for overriding existing render_y functions
1209 | //
1210 | renderers: renderers,
1211 |
1212 | // A hash for 'handlers', like for example, the showTab function.
1213 | //
1214 | handlers: handlers,
1215 |
1216 | parse: parse,
1217 | render: render,
1218 | produce: produce,
1219 |
1220 | undo: undo,
1221 | reset: reset
1222 | }
1223 | }();
1224 |
1225 |
--------------------------------------------------------------------------------
/public/javascripts/jquery-1.4.3.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * jQuery JavaScript Library v1.4.3
3 | * http://jquery.com/
4 | *
5 | * Copyright 2010, John Resig
6 | * Dual licensed under the MIT or GPL Version 2 licenses.
7 | * http://jquery.org/license
8 | *
9 | * Includes Sizzle.js
10 | * http://sizzlejs.com/
11 | * Copyright 2010, The Dojo Foundation
12 | * Released under the MIT, BSD, and GPL Licenses.
13 | *
14 | * Date: Thu Oct 14 23:10:06 2010 -0400
15 | */
16 | (function(E,A){function U(){return false}function ba(){return true}function ja(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ga(a){var b,d,e=[],f=[],h,k,l,n,s,v,B,D;k=c.data(this,this.nodeType?"events":"__events__");if(typeof k==="function")k=k.events;if(!(a.liveFired===this||!k||!k.live||a.button&&a.type==="click")){if(a.namespace)D=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var H=k.live.slice(0);for(n=0;nd)break;a.currentTarget=f.elem;a.data=f.handleObj.data;
18 | a.handleObj=f.handleObj;D=f.handleObj.origHandler.apply(f.elem,arguments);if(D===false||a.isPropagationStopped()){d=f.level;if(D===false)b=false}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(Ha,"`").replace(Ia,"&")}function ka(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Ja.test(b))return c.filter(b,
19 | e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function la(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var k in e[h])c.event.add(this,h,e[h][k],e[h][k].data)}}})}function Ka(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}
20 | function ma(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?La:Ma,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,"margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function ca(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Na.test(a)?e(a,h):ca(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)?
21 | e(a,""):c.each(b,function(f,h){ca(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(na.concat.apply([],na.slice(0,b)),function(){d[this]=a});return d}function oa(a){if(!da[a]){var b=c("<"+a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";da[a]=d}return da[a]}function ea(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var u=E.document,c=function(){function a(){if(!b.isReady){try{u.documentElement.doScroll("left")}catch(i){setTimeout(a,
22 | 1);return}b.ready()}}var b=function(i,r){return new b.fn.init(i,r)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,k=/\S/,l=/^\s+/,n=/\s+$/,s=/\W/,v=/\d/,B=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,D=/^[\],:{}\s]*$/,H=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,G=/(?:^|:|,)(?:\s*\[)+/g,M=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,j=/(msie) ([\w.]+)/,o=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false,
23 | q=[],t,x=Object.prototype.toString,C=Object.prototype.hasOwnProperty,P=Array.prototype.push,N=Array.prototype.slice,R=String.prototype.trim,Q=Array.prototype.indexOf,L={};b.fn=b.prototype={init:function(i,r){var y,z,F;if(!i)return this;if(i.nodeType){this.context=this[0]=i;this.length=1;return this}if(i==="body"&&!r&&u.body){this.context=u;this[0]=u.body;this.selector="body";this.length=1;return this}if(typeof i==="string")if((y=h.exec(i))&&(y[1]||!r))if(y[1]){F=r?r.ownerDocument||r:u;if(z=B.exec(i))if(b.isPlainObject(r)){i=
24 | [u.createElement(z[1])];b.fn.attr.call(i,r,true)}else i=[F.createElement(z[1])];else{z=b.buildFragment([y[1]],[F]);i=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,i)}else{if((z=u.getElementById(y[2]))&&z.parentNode){if(z.id!==y[2])return f.find(i);this.length=1;this[0]=z}this.context=u;this.selector=i;return this}else if(!r&&!s.test(i)){this.selector=i;this.context=u;i=u.getElementsByTagName(i);return b.merge(this,i)}else return!r||r.jquery?(r||f).find(i):b(r).find(i);
25 | else if(b.isFunction(i))return f.ready(i);if(i.selector!==A){this.selector=i.selector;this.context=i.context}return b.makeArray(i,this)},selector:"",jquery:"1.4.3",length:0,size:function(){return this.length},toArray:function(){return N.call(this,0)},get:function(i){return i==null?this.toArray():i<0?this.slice(i)[0]:this[i]},pushStack:function(i,r,y){var z=b();b.isArray(i)?P.apply(z,i):b.merge(z,i);z.prevObject=this;z.context=this.context;if(r==="find")z.selector=this.selector+(this.selector?" ":
26 | "")+y;else if(r)z.selector=this.selector+"."+r+"("+y+")";return z},each:function(i,r){return b.each(this,i,r)},ready:function(i){b.bindReady();if(b.isReady)i.call(u,b);else q&&q.push(i);return this},eq:function(i){return i===-1?this.slice(i):this.slice(i,+i+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(i){return this.pushStack(b.map(this,function(r,y){return i.call(r,
27 | y,r)}))},end:function(){return this.prevObject||b(null)},push:P,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var i=arguments[0]||{},r=1,y=arguments.length,z=false,F,I,K,J,fa;if(typeof i==="boolean"){z=i;i=arguments[1]||{};r=2}if(typeof i!=="object"&&!b.isFunction(i))i={};if(y===r){i=this;--r}for(;r0)){if(q){for(var r=0;i=q[r++];)i.call(u,b);q=null}b.fn.triggerHandler&&b(u).triggerHandler("ready")}}},bindReady:function(){if(!p){p=true;if(u.readyState==="complete")return setTimeout(b.ready,
29 | 1);if(u.addEventListener){u.addEventListener("DOMContentLoaded",t,false);E.addEventListener("load",b.ready,false)}else if(u.attachEvent){u.attachEvent("onreadystatechange",t);E.attachEvent("onload",b.ready);var i=false;try{i=E.frameElement==null}catch(r){}u.documentElement.doScroll&&i&&a()}}},isFunction:function(i){return b.type(i)==="function"},isArray:Array.isArray||function(i){return b.type(i)==="array"},isWindow:function(i){return i&&typeof i==="object"&&"setInterval"in i},isNaN:function(i){return i==
30 | null||!v.test(i)||isNaN(i)},type:function(i){return i==null?String(i):L[x.call(i)]||"object"},isPlainObject:function(i){if(!i||b.type(i)!=="object"||i.nodeType||b.isWindow(i))return false;if(i.constructor&&!C.call(i,"constructor")&&!C.call(i.constructor.prototype,"isPrototypeOf"))return false;for(var r in i);return r===A||C.call(i,r)},isEmptyObject:function(i){for(var r in i)return false;return true},error:function(i){throw i;},parseJSON:function(i){if(typeof i!=="string"||!i)return null;i=b.trim(i);
31 | if(D.test(i.replace(H,"@").replace(w,"]").replace(G,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(i):(new Function("return "+i))();else b.error("Invalid JSON: "+i)},noop:function(){},globalEval:function(i){if(i&&k.test(i)){var r=u.getElementsByTagName("head")[0]||u.documentElement,y=u.createElement("script");y.type="text/javascript";if(b.support.scriptEval)y.appendChild(u.createTextNode(i));else y.text=i;r.insertBefore(y,r.firstChild);r.removeChild(y)}},nodeName:function(i,r){return i.nodeName&&i.nodeName.toUpperCase()===
32 | r.toUpperCase()},each:function(i,r,y){var z,F=0,I=i.length,K=I===A||b.isFunction(i);if(y)if(K)for(z in i){if(r.apply(i[z],y)===false)break}else for(;Fa";var f=d.getElementsByTagName("*"),h=d.getElementsByTagName("a")[0],k=u.createElement("select"),l=k.appendChild(u.createElement("option"));if(!(!f||!f.length||!h)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(h.getAttribute("style")),
38 | hrefNormalized:h.getAttribute("href")==="/a",opacity:/^0.55$/.test(h.style.opacity),cssFloat:!!h.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:l.selected,optDisabled:false,checkClone:false,scriptEval:false,noCloneEvent:true,boxModel:null,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableHiddenOffsets:true};k.disabled=true;c.support.optDisabled=!l.disabled;b.type="text/javascript";try{b.appendChild(u.createTextNode("window."+e+"=1;"))}catch(n){}a.insertBefore(b,
39 | a.firstChild);if(E[e]){c.support.scriptEval=true;delete E[e]}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function s(){c.support.noCloneEvent=false;d.detachEvent("onclick",s)});d.cloneNode(true).fireEvent("onclick")}d=u.createElement("div");d.innerHTML="";a=u.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var s=u.createElement("div");
40 | s.style.width=s.style.paddingLeft="1px";u.body.appendChild(s);c.boxModel=c.support.boxModel=s.offsetWidth===2;if("zoom"in s.style){s.style.display="inline";s.style.zoom=1;c.support.inlineBlockNeedsLayout=s.offsetWidth===2;s.style.display="";s.innerHTML="";c.support.shrinkWrapBlocks=s.offsetWidth!==2}s.innerHTML="";var v=s.getElementsByTagName("td");c.support.reliableHiddenOffsets=v[0].offsetHeight===
41 | 0;v[0].style.display="";v[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&v[0].offsetHeight===0;s.innerHTML="";u.body.removeChild(s).style.display="none"});a=function(s){var v=u.createElement("div");s="on"+s;var B=s in v;if(!B){v.setAttribute(s,"return;");B=typeof v[s]==="function"}return B};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",
42 | cellspacing:"cellSpacing",rowspan:"rowSpan",colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};var pa={},Oa=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?pa:a;var e=a.nodeType,f=e?a[c.expando]:null,h=c.cache;if(!(e&&!f&&typeof b==="string"&&d===A)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]=
43 | c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==A)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?pa:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);else if(d)delete f[e];else for(var k in a)delete a[k]}},acceptData:function(a){if(a.nodeName){var b=
44 | c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){if(typeof a==="undefined")return this.length?c.data(this[0]):null;else if(typeof a==="object")return this.each(function(){c.data(this,a)});var d=a.split(".");d[1]=d[1]?"."+d[1]:"";if(b===A){var e=this.triggerHandler("getData"+d[1]+"!",[d[0]]);if(e===A&&this.length){e=c.data(this[0],a);if(e===A&&this[0].nodeType===1){e=this[0].getAttribute("data-"+a);if(typeof e===
45 | "string")try{e=e==="true"?true:e==="false"?false:e==="null"?null:!c.isNaN(e)?parseFloat(e):Oa.test(e)?c.parseJSON(e):e}catch(f){}else e=A}}return e===A&&d[1]?this.data(d[0]):e}else return this.each(function(){var h=c(this),k=[d[0],b];h.triggerHandler("setData"+d[1]+"!",k);c.data(this,a,b);h.triggerHandler("changeData"+d[1]+"!",k)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=c.data(a,b);if(!d)return e||
46 | [];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===A)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,
47 | a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var qa=/[\n\t]/g,ga=/\s+/,Pa=/\r/g,Qa=/^(?:href|src|style)$/,Ra=/^(?:button|input)$/i,Sa=/^(?:button|input|object|select|textarea)$/i,Ta=/^a(?:rea)?$/i,ra=/^(?:radio|checkbox)$/i;c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,
48 | a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(s){var v=c(this);v.addClass(a.call(this,s,v.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ga),d=0,e=this.length;d-1)return true;return false},
51 | val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h=0;else if(c.nodeName(this,"select")){var B=c.makeArray(v);c("option",this).each(function(){this.selected=
53 | c.inArray(c(this).val(),B)>=0});if(!B.length)this.selectedIndex=-1}else this.value=v}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return A;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==A;b=e&&c.props[b]||b;if(a.nodeType===1){var h=Qa.test(b);if((b in a||a[b]!==A)&&e&&!h){if(f){b==="type"&&Ra.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");
54 | if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&b.specified?b.value:Sa.test(a.nodeName)||Ta.test(a.nodeName)&&a.href?0:A;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return A;a=!c.support.hrefNormalized&&e&&
55 | h?a.getAttribute(b,2):a.getAttribute(b);return a===null?A:a}}});var X=/\.(.*)$/,ha=/^(?:textarea|input|select)$/i,Ha=/\./g,Ia=/ /g,Ua=/[^\w\s.|`]/g,Va=function(a){return a.replace(Ua,"\\$&")},sa={focusin:0,focusout:0};c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var k=a.nodeType?"events":"__events__",l=h[k],n=h.handle;if(typeof l===
56 | "function"){n=l.handle;l=l.events}else if(!l){a.nodeType||(h[k]=h=function(){});h.events=l={}}if(!n)h.handle=n=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(n.elem,arguments):A};n.elem=a;b=b.split(" ");for(var s=0,v;k=b[s++];){h=f?c.extend({},f):{handler:d,data:e};if(k.indexOf(".")>-1){v=k.split(".");k=v.shift();h.namespace=v.slice(0).sort().join(".")}else{v=[];h.namespace=""}h.type=k;if(!h.guid)h.guid=d.guid;var B=l[k],D=c.event.special[k]||{};if(!B){B=l[k]=[];
57 | if(!D.setup||D.setup.call(a,e,v,n)===false)if(a.addEventListener)a.addEventListener(k,n,false);else a.attachEvent&&a.attachEvent("on"+k,n)}if(D.add){D.add.call(a,h);if(!h.handler.guid)h.handler.guid=d.guid}B.push(h);c.event.global[k]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,k=0,l,n,s,v,B,D,H=a.nodeType?"events":"__events__",w=c.data(a),G=w&&w[H];if(w&&G){if(typeof G==="function"){w=G;G=G.events}if(b&&b.type){d=b.handler;b=b.type}if(!b||
58 | typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in G)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[k++];){v=f;l=f.indexOf(".")<0;n=[];if(!l){n=f.split(".");f=n.shift();s=RegExp("(^|\\.)"+c.map(n.slice(0).sort(),Va).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(B=G[f])if(d){v=c.event.special[f]||{};for(h=e||0;h=0){a.type=
60 | f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===8)return A;a.result=A;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)===
61 | false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){e=a.target;var k,l=f.replace(X,""),n=c.nodeName(e,"a")&&l==="click",s=c.event.special[l]||{};if((!s._default||s._default.call(d,a)===false)&&!n&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[l]){if(k=e["on"+l])e["on"+l]=null;c.event.triggered=true;e[l]()}}catch(v){}if(k)e["on"+l]=k;c.event.triggered=false}}},handle:function(a){var b,d,e;
62 | d=[];var f,h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var k=d.length;f-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ha.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=va(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===A||f===e))if(e!=null||f){a.type="change";a.liveFired=
71 | A;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",va(a))}},setup:function(){if(this.type===
72 | "file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ha.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ha.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}u.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){sa[b]++===0&&u.addEventListener(a,d,true)},teardown:function(){--sa[b]===
73 | 0&&u.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=A}var k=b==="one"?c.proxy(f,function(n){c(this).unbind(n,k);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var l=this.length;h0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
78 | (function(){function a(g,j,o,m,p,q){p=0;for(var t=m.length;p0){C=x;break}}x=x[g]}m[p]=C}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,k=true;[0,0].sort(function(){k=false;return 0});var l=function(g,j,o,m){o=o||[];var p=j=j||u;if(j.nodeType!==1&&j.nodeType!==9)return[];if(!g||typeof g!=="string")return o;var q=[],t,x,C,P,N=true,R=l.isXML(j),Q=g,L;do{d.exec("");if(t=d.exec(Q)){Q=t[3];q.push(t[1]);if(t[2]){P=t[3];
80 | break}}}while(t);if(q.length>1&&s.exec(g))if(q.length===2&&n.relative[q[0]])x=M(q[0]+q[1],j);else for(x=n.relative[q[0]]?[j]:l(q.shift(),j);q.length;){g=q.shift();if(n.relative[g])g+=q.shift();x=M(g,x)}else{if(!m&&q.length>1&&j.nodeType===9&&!R&&n.match.ID.test(q[0])&&!n.match.ID.test(q[q.length-1])){t=l.find(q.shift(),j,R);j=t.expr?l.filter(t.expr,t.set)[0]:t.set[0]}if(j){t=m?{expr:q.pop(),set:D(m)}:l.find(q.pop(),q.length===1&&(q[0]==="~"||q[0]==="+")&&j.parentNode?j.parentNode:j,R);x=t.expr?l.filter(t.expr,
81 | t.set):t.set;if(q.length>0)C=D(x);else N=false;for(;q.length;){t=L=q.pop();if(n.relative[L])t=q.pop();else L="";if(t==null)t=j;n.relative[L](C,t,R)}}else C=[]}C||(C=x);C||l.error(L||g);if(f.call(C)==="[object Array]")if(N)if(j&&j.nodeType===1)for(g=0;C[g]!=null;g++){if(C[g]&&(C[g]===true||C[g].nodeType===1&&l.contains(j,C[g])))o.push(x[g])}else for(g=0;C[g]!=null;g++)C[g]&&C[g].nodeType===1&&o.push(x[g]);else o.push.apply(o,C);else D(C,o);if(P){l(P,p,o,m);l.uniqueSort(o)}return o};l.uniqueSort=function(g){if(w){h=
82 | k;g.sort(w);if(h)for(var j=1;j0};l.find=function(g,j,o){var m;if(!g)return[];for(var p=0,q=n.order.length;p":function(g,j){var o=typeof j==="string",m,p=0,q=g.length;if(o&&!/\W/.test(j))for(j=j.toLowerCase();p=0))o||m.push(t);else if(o)j[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var j=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=j[1]+(j[2]||1)-0;g[3]=j[3]-0}g[0]=e++;return g},ATTR:function(g,j,o,
89 | m,p,q){j=g[1].replace(/\\/g,"");if(!q&&n.attrMap[j])g[1]=n.attrMap[j];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,j,o,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=l(g[3],null,null,j);else{g=l.filter(g[3],j,o,true^p);o||m.push.apply(m,g);return false}else if(n.match.POS.test(g[0])||n.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===
90 | true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,j,o){return!!l(o[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===
91 | g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,j){return j===0},last:function(g,j,o,m){return j===m.length-1},even:function(g,j){return j%2===0},odd:function(g,j){return j%2===1},lt:function(g,j,o){return jo[3]-0},nth:function(g,j,o){return o[3]-
92 | 0===j},eq:function(g,j,o){return o[3]-0===j}},filter:{PSEUDO:function(g,j,o,m){var p=j[1],q=n.filters[p];if(q)return q(g,o,j,m);else if(p==="contains")return(g.textContent||g.innerText||l.getText([g])||"").indexOf(j[3])>=0;else if(p==="not"){j=j[3];o=0;for(m=j.length;o=0}},ID:function(g,j){return g.nodeType===1&&g.getAttribute("id")===j},TAG:function(g,j){return j==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===
94 | j},CLASS:function(g,j){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(j)>-1},ATTR:function(g,j){var o=j[1];o=n.attrHandle[o]?n.attrHandle[o](g):g[o]!=null?g[o]:g.getAttribute(o);var m=o+"",p=j[2],q=j[4];return o==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&o!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,j,o,m){var p=n.setFilters[j[2]];
95 | if(p)return p(g,o,j,m)}}},s=n.match.POS,v=function(g,j){return"\\"+(j-0+1)},B;for(B in n.match){n.match[B]=RegExp(n.match[B].source+/(?![^\[]*\])(?![^\(]*\))/.source);n.leftMatch[B]=RegExp(/(^(?:.|\r|\n)*?)/.source+n.match[B].source.replace(/\\(\d+)/g,v))}var D=function(g,j){g=Array.prototype.slice.call(g,0);if(j){j.push.apply(j,g);return j}return g};try{Array.prototype.slice.call(u.documentElement.childNodes,0)}catch(H){D=function(g,j){var o=j||[],m=0;if(f.call(g)==="[object Array]")Array.prototype.push.apply(o,
96 | g);else if(typeof g.length==="number")for(var p=g.length;m";var o=u.documentElement;o.insertBefore(g,o.firstChild);if(u.getElementById(j)){n.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:A:[]};n.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}o.removeChild(g);
99 | o=g=null})();(function(){var g=u.createElement("div");g.appendChild(u.createComment(""));if(g.getElementsByTagName("*").length>0)n.find.TAG=function(j,o){var m=o.getElementsByTagName(j[1]);if(j[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")n.attrHandle.href=function(j){return j.getAttribute("href",2)};g=null})();u.querySelectorAll&&
100 | function(){var g=l,j=u.createElement("div");j.innerHTML="";if(!(j.querySelectorAll&&j.querySelectorAll(".TEST").length===0)){l=function(m,p,q,t){p=p||u;if(!t&&!l.isXML(p))if(p.nodeType===9)try{return D(p.querySelectorAll(m),q)}catch(x){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var C=p.id,P=p.id="__sizzle__";try{return D(p.querySelectorAll("#"+P+" "+m),q)}catch(N){}finally{if(C)p.id=C;else p.removeAttribute("id")}}return g(m,p,q,t)};for(var o in g)l[o]=g[o];
101 | j=null}}();(function(){var g=u.documentElement,j=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,o=false;try{j.call(u.documentElement,":sizzle")}catch(m){o=true}if(j)l.matchesSelector=function(p,q){try{if(o||!n.match.PSEUDO.test(q))return j.call(p,q)}catch(t){}return l(q,null,null,[p]).length>0}})();(function(){var g=u.createElement("div");g.innerHTML="";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===
102 | 0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){n.order.splice(1,0,"CLASS");n.find.CLASS=function(j,o,m){if(typeof o.getElementsByClassName!=="undefined"&&!m)return o.getElementsByClassName(j[1])};g=null}}})();l.contains=u.documentElement.contains?function(g,j){return g!==j&&(g.contains?g.contains(j):true)}:function(g,j){return!!(g.compareDocumentPosition(j)&16)};l.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var M=function(g,
103 | j){for(var o=[],m="",p,q=j.nodeType?[j]:j;p=n.match.PSEUDO.exec(g);){m+=p[0];g=g.replace(n.match.PSEUDO,"")}g=n.relative[g]?g+"*":g;p=0;for(var t=q.length;p0)for(var h=d;h0},closest:function(a,
105 | b){var d=[],e,f,h=this[0];if(c.isArray(a)){var k={},l,n=1;if(h&&a.length){e=0;for(f=a.length;e-1:c(h).is(e))d.push({selector:l,elem:h,level:n})}h=h.parentNode;n++}}return d}k=$a.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h||
106 | !h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}});
107 | c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",
108 | d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Wa.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||Ya.test(e))&&Xa.test(a))f=f.reverse();return this.pushStack(f,a,Za.call(arguments).join(","))}});
109 | c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===A||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var xa=/ jQuery\d+="(?:\d+|null)"/g,
110 | $=/^\s+/,ya=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,za=/<([\w:]+)/,ab=/\s]+\/)>/g,O={option:[1,""],legend:[1,""],thead:[1,""],tr:[2,""],td:[3,""],col:[2,""],
111 | area:[1,""],_default:[0,"",""]};O.optgroup=O.option;O.tbody=O.tfoot=O.colgroup=O.caption=O.thead;O.th=O.td;if(!c.support.htmlSerialize)O._default=[1,"div","
"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==A)return this.empty().append((this[0]&&this[0].ownerDocument||u).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,
112 | d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},
113 | unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=
114 | c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*"));
115 | c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(xa,"").replace(cb,'="$1">').replace($,
116 | "")],e)[0]}else return this.cloneNode(true)});if(a===true){la(this,b);la(this.find("*"),b.find("*"))}return b},html:function(a){if(a===A)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(xa,""):null;else if(typeof a==="string"&&!Aa.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!O[(za.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(ya,"<$1>$2>");try{for(var b=0,d=this.length;b0||e.cacheable||this.length>1?l.cloneNode(true):l)}k.length&&c.each(k,Ka)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:u;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===u&&!Aa.test(a[0])&&(c.support.checkClone||
120 | !Ba.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h=
121 | d.length;f0?this.clone(true):this).get();c(d[f])[b](k);e=e.concat(k)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||u;if(typeof b.createElement==="undefined")b=b.ownerDocument||b[0]&&b[0].ownerDocument||u;for(var f=[],h=0,k;(k=a[h])!=null;h++){if(typeof k==="number")k+="";if(k){if(typeof k==="string"&&!bb.test(k))k=b.createTextNode(k);else if(typeof k==="string"){k=k.replace(ya,"<$1>$2>");var l=(za.exec(k)||["",""])[1].toLowerCase(),n=O[l]||O._default,
122 | s=n[0],v=b.createElement("div");for(v.innerHTML=n[1]+k+n[2];s--;)v=v.lastChild;if(!c.support.tbody){s=ab.test(k);l=l==="table"&&!s?v.firstChild&&v.firstChild.childNodes:n[1]===""&&!s?v.childNodes:[];for(n=l.length-1;n>=0;--n)c.nodeName(l[n],"tbody")&&!l[n].childNodes.length&&l[n].parentNode.removeChild(l[n])}!c.support.leadingWhitespace&&$.test(k)&&v.insertBefore(b.createTextNode($.exec(k)[0]),v.firstChild);k=v.childNodes}if(k.nodeType)f.push(k);else f=c.merge(f,k)}}if(d)for(h=0;f[h];h++)if(e&&
123 | c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,k=0,l;(l=a[k])!=null;k++)if(!(l.nodeName&&c.noData[l.nodeName.toLowerCase()]))if(d=l[c.expando]){if((b=e[d])&&b.events)for(var n in b.events)f[n]?
124 | c.event.remove(l,n):c.removeEvent(l,n,b.handle);if(h)delete l[c.expando];else l.removeAttribute&&l.removeAttribute(c.expando);delete e[d]}}});var Ca=/alpha\([^)]*\)/i,db=/opacity=([^)]*)/,eb=/-([a-z])/ig,fb=/([A-Z])/g,Da=/^-?\d+(?:px)?$/i,gb=/^-?\d/,hb={position:"absolute",visibility:"hidden",display:"block"},La=["Left","Right"],Ma=["Top","Bottom"],W,ib=u.defaultView&&u.defaultView.getComputedStyle,jb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===A)return this;
125 | return c.access(this,a,b,true,function(d,e,f){return f!==A?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),k=a.style,l=c.cssHooks[h];b=c.cssProps[h]||
126 | h;if(d!==A){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!l||!("set"in l)||(d=l.set(a,d))!==A)try{k[b]=d}catch(n){}}}else{if(l&&"get"in l&&(f=l.get(a,false,e))!==A)return f;return k[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==A)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]=
127 | e[f]},camelCase:function(a){return a.replace(eb,jb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=ma(d,b,f);else c.swap(d,hb,function(){h=ma(d,b,f)});return h+"px"}},set:function(d,e){if(Da.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return db.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":
128 | b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=d.filter||"";d.filter=Ca.test(f)?f.replace(Ca,e):d.filter+" "+e}};if(ib)W=function(a,b,d){var e;d=d.replace(fb,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return A;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};else if(u.documentElement.currentStyle)W=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b],
129 | h=a.style;if(!Da.test(f)&&gb.test(f)){d=h.left;e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f};if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var kb=c.now(),lb=/