├── .ruby-version ├── public ├── javascripts │ ├── staffers.js │ ├── jquery.tablesorter.pager.js │ ├── jquery.tablesorter.min.js │ ├── donorbanner.js │ └── jquery.qtip-1.0.0-rc3.min.js ├── favicon.ico ├── images │ ├── info.png │ ├── bg_tile.jpg │ ├── hsd_logo.png │ ├── main_bg.jpg │ ├── browsearea.png │ ├── browsetitle.png │ ├── cornerpiece.png │ ├── featurebar.png │ ├── footerbar.png │ ├── hsd_logo2.png │ ├── hsd_star_15.png │ ├── join_button.png │ ├── newsearch.png │ ├── personbar.jpg │ ├── searchtitle.png │ ├── sfapp_logo.png │ ├── sort_arrows.png │ ├── test-image.jpg │ ├── toggle-off.png │ ├── toggle-on.png │ ├── cornerpiece2.png │ ├── facebook_icon.png │ ├── resultstitle.png │ ├── search_button.png │ ├── sf_mini_logo.png │ ├── twitter_icon.png │ ├── download_button.png │ ├── findingstaffer.png │ ├── newsearch_button.png │ ├── searchbar_tile.jpg │ ├── sf_mini_nav_bg.jpg │ └── sfapp_bg_header.jpg └── css │ ├── donorbanner.css │ └── style.css ├── vendor └── cache │ ├── bson-4.3.0.gem │ ├── hashie-3.5.7.gem │ ├── i18n-0.9.5.gem │ ├── kgio-2.11.2.gem │ ├── mongo-1.3.1.gem │ ├── rack-1.6.10.gem │ ├── tilt-1.4.1.gem │ ├── wirble-0.1.3.gem │ ├── bson_ext-1.3.1.gem │ ├── builder-3.0.4.gem │ ├── congress-0.3.0.gem │ ├── faraday-0.9.2.gem │ ├── geocoder-1.4.9.gem │ ├── mongoid-2.0.2.gem │ ├── sinatra-1.2.6.gem │ ├── stringex-1.2.2.gem │ ├── tzinfo-0.3.54.gem │ ├── unicorn-5.4.0.gem │ ├── multi_json-1.13.1.gem │ ├── raindrops-0.19.0.gem │ ├── safe_yaml-1.0.4.gem │ ├── mongoid_slug-0.7.2.gem │ ├── activemodel-3.2.22.5.gem │ ├── activesupport-3.2.22.5.gem │ ├── concurrent-ruby-1.0.5.gem │ ├── multipart-post-2.0.0.gem │ ├── sinatra-content-for-0.2.gem │ └── faraday_middleware-0.10.1.gem ├── config.ru.example ├── .gitignore ├── CONTRIBUTING.md ├── config ├── config.yml.example └── environment.rb ├── unicorn.rb.example ├── Gemfile ├── Rakefile ├── data ├── README.md ├── offices.csv └── load.rake ├── Gemfile.lock ├── fabfile.py ├── views ├── staffer.erb ├── index.erb ├── _search.erb ├── positions.erb ├── office.erb ├── offices.erb ├── layout.erb └── faq.erb ├── models.rb ├── README.md ├── csv.rb ├── staffers.rb └── helpers.rb /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.2.3 2 | -------------------------------------------------------------------------------- /public/javascripts/staffers.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/images/info.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/info.png -------------------------------------------------------------------------------- /public/images/bg_tile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/bg_tile.jpg -------------------------------------------------------------------------------- /public/images/hsd_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/hsd_logo.png -------------------------------------------------------------------------------- /public/images/main_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/main_bg.jpg -------------------------------------------------------------------------------- /public/images/browsearea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/browsearea.png -------------------------------------------------------------------------------- /public/images/browsetitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/browsetitle.png -------------------------------------------------------------------------------- /public/images/cornerpiece.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/cornerpiece.png -------------------------------------------------------------------------------- /public/images/featurebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/featurebar.png -------------------------------------------------------------------------------- /public/images/footerbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/footerbar.png -------------------------------------------------------------------------------- /public/images/hsd_logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/hsd_logo2.png -------------------------------------------------------------------------------- /public/images/hsd_star_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/hsd_star_15.png -------------------------------------------------------------------------------- /public/images/join_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/join_button.png -------------------------------------------------------------------------------- /public/images/newsearch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/newsearch.png -------------------------------------------------------------------------------- /public/images/personbar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/personbar.jpg -------------------------------------------------------------------------------- /public/images/searchtitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/searchtitle.png -------------------------------------------------------------------------------- /public/images/sfapp_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/sfapp_logo.png -------------------------------------------------------------------------------- /public/images/sort_arrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/sort_arrows.png -------------------------------------------------------------------------------- /public/images/test-image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/test-image.jpg -------------------------------------------------------------------------------- /public/images/toggle-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/toggle-off.png -------------------------------------------------------------------------------- /public/images/toggle-on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/toggle-on.png -------------------------------------------------------------------------------- /vendor/cache/bson-4.3.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/bson-4.3.0.gem -------------------------------------------------------------------------------- /vendor/cache/hashie-3.5.7.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/hashie-3.5.7.gem -------------------------------------------------------------------------------- /vendor/cache/i18n-0.9.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/i18n-0.9.5.gem -------------------------------------------------------------------------------- /vendor/cache/kgio-2.11.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/kgio-2.11.2.gem -------------------------------------------------------------------------------- /vendor/cache/mongo-1.3.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/mongo-1.3.1.gem -------------------------------------------------------------------------------- /vendor/cache/rack-1.6.10.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/rack-1.6.10.gem -------------------------------------------------------------------------------- /vendor/cache/tilt-1.4.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/tilt-1.4.1.gem -------------------------------------------------------------------------------- /vendor/cache/wirble-0.1.3.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/wirble-0.1.3.gem -------------------------------------------------------------------------------- /public/images/cornerpiece2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/cornerpiece2.png -------------------------------------------------------------------------------- /public/images/facebook_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/facebook_icon.png -------------------------------------------------------------------------------- /public/images/resultstitle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/resultstitle.png -------------------------------------------------------------------------------- /public/images/search_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/search_button.png -------------------------------------------------------------------------------- /public/images/sf_mini_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/sf_mini_logo.png -------------------------------------------------------------------------------- /public/images/twitter_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/twitter_icon.png -------------------------------------------------------------------------------- /vendor/cache/bson_ext-1.3.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/bson_ext-1.3.1.gem -------------------------------------------------------------------------------- /vendor/cache/builder-3.0.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/builder-3.0.4.gem -------------------------------------------------------------------------------- /vendor/cache/congress-0.3.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/congress-0.3.0.gem -------------------------------------------------------------------------------- /vendor/cache/faraday-0.9.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/faraday-0.9.2.gem -------------------------------------------------------------------------------- /vendor/cache/geocoder-1.4.9.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/geocoder-1.4.9.gem -------------------------------------------------------------------------------- /vendor/cache/mongoid-2.0.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/mongoid-2.0.2.gem -------------------------------------------------------------------------------- /vendor/cache/sinatra-1.2.6.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/sinatra-1.2.6.gem -------------------------------------------------------------------------------- /vendor/cache/stringex-1.2.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/stringex-1.2.2.gem -------------------------------------------------------------------------------- /vendor/cache/tzinfo-0.3.54.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/tzinfo-0.3.54.gem -------------------------------------------------------------------------------- /vendor/cache/unicorn-5.4.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/unicorn-5.4.0.gem -------------------------------------------------------------------------------- /public/images/download_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/download_button.png -------------------------------------------------------------------------------- /public/images/findingstaffer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/findingstaffer.png -------------------------------------------------------------------------------- /public/images/newsearch_button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/newsearch_button.png -------------------------------------------------------------------------------- /public/images/searchbar_tile.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/searchbar_tile.jpg -------------------------------------------------------------------------------- /public/images/sf_mini_nav_bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/sf_mini_nav_bg.jpg -------------------------------------------------------------------------------- /public/images/sfapp_bg_header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/public/images/sfapp_bg_header.jpg -------------------------------------------------------------------------------- /vendor/cache/multi_json-1.13.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/multi_json-1.13.1.gem -------------------------------------------------------------------------------- /vendor/cache/raindrops-0.19.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/raindrops-0.19.0.gem -------------------------------------------------------------------------------- /vendor/cache/safe_yaml-1.0.4.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/safe_yaml-1.0.4.gem -------------------------------------------------------------------------------- /vendor/cache/mongoid_slug-0.7.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/mongoid_slug-0.7.2.gem -------------------------------------------------------------------------------- /vendor/cache/activemodel-3.2.22.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/activemodel-3.2.22.5.gem -------------------------------------------------------------------------------- /vendor/cache/activesupport-3.2.22.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/activesupport-3.2.22.5.gem -------------------------------------------------------------------------------- /vendor/cache/concurrent-ruby-1.0.5.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/concurrent-ruby-1.0.5.gem -------------------------------------------------------------------------------- /vendor/cache/multipart-post-2.0.0.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/multipart-post-2.0.0.gem -------------------------------------------------------------------------------- /vendor/cache/sinatra-content-for-0.2.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/sinatra-content-for-0.2.gem -------------------------------------------------------------------------------- /vendor/cache/faraday_middleware-0.10.1.gem: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/propublica/staffers/HEAD/vendor/cache/faraday_middleware-0.10.1.gem -------------------------------------------------------------------------------- /config.ru.example: -------------------------------------------------------------------------------- 1 | ENV['RACK_ENV'] = "development" 2 | 3 | require 'rubygems' 4 | require 'bundler/setup' 5 | 6 | require './staffers' 7 | run Sinatra::Application -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /config/config.yml 2 | /data/csv 3 | /config.ru 4 | 5 | /vendor/gems 6 | 7 | .DS_Store 8 | *.pyc 9 | *.swp 10 | *.txt 11 | *.pid 12 | 13 | /unicorn.rb -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ### Contributing 2 | 3 | By submitting a pull request to this repository, you're agreeing to license your contribution under this project's [LICENSE](LICENSE). -------------------------------------------------------------------------------- /config/config.yml.example: -------------------------------------------------------------------------------- 1 | sunlight_api_key: # fill in 2 | 3 | mongoid: 4 | host: localhost 5 | database: staffers 6 | raise_not_found_error: false 7 | logger: # leaving blank suppresses logging, to enable delete this line -------------------------------------------------------------------------------- /unicorn.rb.example: -------------------------------------------------------------------------------- 1 | worker_processes 1 # this should be >= nr_cpus 2 | pid "/path/to/unicorn.pid" 3 | 4 | working_directory "/path/to/staffers" 5 | 6 | # in production, direct all logs to /dev/null 7 | # (this happens by default on daemonization anyway) 8 | stdout_path "/dev/null" 9 | stderr_path "/dev/null" -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'congress' 4 | 5 | gem 'sinatra', '1.2.6' 6 | gem 'sinatra-content-for', '0.2' 7 | 8 | gem 'safe_yaml' 9 | 10 | gem "mongo", "1.3.1" 11 | gem "mongoid", '2.0.2' 12 | gem "mongoid_slug", '0.7.2' 13 | gem 'bson_ext', '1.3.1' 14 | 15 | group :development do 16 | gem 'wirble' 17 | gem 'unicorn' 18 | end 19 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | desc 'Loads environment' 2 | task :environment do 3 | require 'rubygems' 4 | require 'bundler/setup' 5 | require './config/environment' 6 | end 7 | 8 | load 'data/load.rake' 9 | 10 | task create_indexes: :environment do 11 | [Quarter, Staffer, Title, Office, Position].each do |klass| 12 | klass.create_indexes 13 | puts "Created indexes for #{klass}." 14 | end 15 | end -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | require 'sinatra' 2 | require 'mongoid' 3 | require 'mongoid/slug' 4 | require 'safe_yaml' 5 | 6 | def config 7 | @config ||= YAML.safe_load_file File.join(File.dirname(__FILE__), "config.yml") 8 | end 9 | 10 | configure do 11 | Mongoid.configure {|c| c.from_hash config['mongoid']} 12 | SafeYAML::OPTIONS[:default_mode] = :safe 13 | end 14 | 15 | require './models' -------------------------------------------------------------------------------- /data/README.md: -------------------------------------------------------------------------------- 1 | This directory expects four CSV files: 2 | 3 | * `staffers.csv` - Unique staffer names as they appear in the original disbursement data, and any standardizations or corrections to those titles. 4 | * `titles.csv` - Unique titles as they appear in the original disbursement data, and any standardizations or corrections to those titles. 5 | * `offices.csv` - Unique office names as they appear in the original disbursement data, and any standardizations or corrections to those titles. Expanded details for each office (committee ID, building, room, telephone number) are added by hand. 6 | * `positions.csv` - The "join table" that links staffer names, title names, and office names together as a "position" for each quarter. -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | activemodel (3.2.22.5) 5 | activesupport (= 3.2.22.5) 6 | builder (~> 3.0.0) 7 | activesupport (3.2.22.5) 8 | i18n (~> 0.6, >= 0.6.4) 9 | multi_json (~> 1.0) 10 | bson (4.3.0) 11 | bson_ext (1.3.1) 12 | builder (3.0.4) 13 | concurrent-ruby (1.0.5) 14 | congress (0.3.0) 15 | faraday (~> 0.9.0) 16 | faraday_middleware (~> 0.10.0) 17 | geocoder (~> 1.2) 18 | hashie (~> 3.0) 19 | faraday (0.9.2) 20 | multipart-post (>= 1.2, < 3) 21 | faraday_middleware (0.10.1) 22 | faraday (>= 0.7.4, < 1.0) 23 | geocoder (1.4.9) 24 | hashie (3.5.7) 25 | i18n (0.9.5) 26 | concurrent-ruby (~> 1.0) 27 | kgio (2.11.2) 28 | mongo (1.3.1) 29 | bson (>= 1.3.1) 30 | mongoid (2.0.2) 31 | activemodel (~> 3.0) 32 | mongo (~> 1.3) 33 | tzinfo (~> 0.3.22) 34 | mongoid_slug (0.7.2) 35 | mongoid (~> 2.0.0) 36 | stringex (~> 1.2.0) 37 | multi_json (1.13.1) 38 | multipart-post (2.0.0) 39 | rack (1.6.10) 40 | raindrops (0.19.0) 41 | safe_yaml (1.0.4) 42 | sinatra (1.2.6) 43 | rack (~> 1.1) 44 | tilt (>= 1.2.2, < 2.0) 45 | sinatra-content-for (0.2) 46 | sinatra 47 | stringex (1.2.2) 48 | tilt (1.4.1) 49 | tzinfo (0.3.54) 50 | unicorn (5.4.0) 51 | kgio (~> 2.6) 52 | raindrops (~> 0.7) 53 | wirble (0.1.3) 54 | 55 | PLATFORMS 56 | ruby 57 | 58 | DEPENDENCIES 59 | bson_ext (= 1.3.1) 60 | congress 61 | mongo (= 1.3.1) 62 | mongoid (= 2.0.2) 63 | mongoid_slug (= 0.7.2) 64 | safe_yaml 65 | sinatra (= 1.2.6) 66 | sinatra-content-for (= 0.2) 67 | unicorn 68 | wirble 69 | 70 | BUNDLED WITH 71 | 1.15.1 72 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | import time 2 | from fabric.api import run, execute, env 3 | 4 | env.use_ssh_config = True 5 | env.hosts = ["staffers@rubyhaus"] 6 | 7 | branch = "master" 8 | repo = "git://github.com/sunlightlabs/staffers.git" 9 | 10 | home = "/projects/staffers" 11 | shared_path = "%s/shared" % home 12 | versions_path = "%s/versions" % home 13 | version_path = "%s/%s" % (versions_path, time.strftime("%Y%m%d%H%M%S")) 14 | current_path = "%s/current" % home 15 | 16 | # how many old releases to be kept at deploy-time 17 | keep = 10 18 | 19 | 20 | ## can be run only as part of deploy 21 | 22 | def checkout(): 23 | run('git clone -q -b %s %s %s' % (branch, repo, version_path)) 24 | 25 | def links(): 26 | run("ln -s %s/config.yml %s/config/config.yml" % (shared_path, version_path)) 27 | run("ln -s %s/config.ru %s/config.ru" % (shared_path, version_path)) 28 | run("ln -s %s/unicorn.rb %s/unicorn.rb" % (shared_path, version_path)) 29 | run("ln -s %s/csv %s/data/csv" % (shared_path, version_path)) 30 | 31 | def dependencies(): 32 | run("cd %s && bundle install --local" % version_path) 33 | 34 | def create_indexes(): 35 | run("cd %s && rake create_indexes" % version_path) 36 | 37 | def make_current(): 38 | run('rm -f %s && ln -s %s %s' % (current_path, version_path, current_path)) 39 | 40 | def cleanup(): 41 | versions = run("ls -x %s" % versions_path).split() 42 | # destroy all but the most recent X 43 | destroy = versions[:-keep] 44 | 45 | for version in destroy: 46 | command = "rm -rf %s/%s" % (versions_path, version) 47 | run(command) 48 | 49 | 50 | ## can be run on their own 51 | 52 | def start(): 53 | run("cd %s && unicorn -D -l %s/staffers.sock -c unicorn.rb" % (current_path, shared_path)) 54 | 55 | def stop(): 56 | run("kill `cat %s/unicorn.pid`" % shared_path) 57 | 58 | def restart(): 59 | execute(stop) 60 | execute(start) 61 | 62 | def deploy(): 63 | execute(checkout) 64 | execute(links) 65 | execute(dependencies) 66 | execute(create_indexes) 67 | execute(make_current) 68 | execute(restart) 69 | execute(cleanup) 70 | -------------------------------------------------------------------------------- /data/offices.csv: -------------------------------------------------------------------------------- 1 | OFFICE OF THE SPEAKER,OFFICE OF THE SPEAKER 2 | OFFICE OF THE MAJORITY LEADER,OFFICE OF THE MAJORITY LEADER 3 | OFFICE OF THE MINORITY LEADER,OFFICE OF THE MINORITY LEADER 4 | OFFICE OF THE MAJORITY WHIP,OFFICE OF THE MAJORITY WHIP 5 | OFFICE OF THE MINORITY WHIP,OFFICE OF THE MINORITY WHIP 6 | DEMOCRATIC CAUCUS,DEMOCRATIC CAUCUS 7 | REPUBLICAN CONFERENCE,REPUBLICAN CONFERENCE 8 | CHIEF ADMIN OFCR OF THE HOUSE,CHIEF ADMIN OFCR OF THE HOUSE 9 | CLERK OF THE HOUSE,CLERK OF THE HOUSE 10 | OFFICE OF GENERAL COUNSEL,OFFICE OF GENERAL COUNSEL 11 | SERGEANT AT ARMS,SERGEANT AT ARMS 12 | OFFICE OF THE PARLIAMENTARIAN,OFFICE OF THE PARLIAMENTARIAN 13 | COMPILATION OF PRECEDENTS,COMPILATION OF PRECEDENTS 14 | TECHNICAL ASSISTANTS,TECHNICAL ASSISTANTS 15 | OFFICE OF INSPECTOR GENERAL,OFFICE OF INSPECTOR GENERAL 16 | LAW REVISION COUNSEL,LAW REVISION COUNSEL 17 | LEGISLATIVE COUNSEL,LEGISLATIVE COUNSEL 18 | INTERPARLIAMENTARY AFFAIRS,INTERPARLIAMENTARY AFFAIRS 19 | COMMITTEE ON APPROPRIATIONS,COMMITTEE ON APPROPRIATIONS 20 | COMMITTEE ON BUDGET,COMMITTEE ON BUDGET 21 | COMMITTEE ON AGRICULTURE,COMMITTEE ON AGRICULTURE 22 | ARMED SERVICES COMMITTEE,ARMED SERVICES COMMITTEE 23 | COMMITTEE ON FINANCIAL SERVICE,COMMITTEE ON FINANCIAL SERVICE 24 | COMM ON EDUCATION & WORKFORCE,COMM ON EDUCATION & WORKFORCE 25 | COMMITTEE ON ENERGY & COMMERCE,COMMITTEE ON ENERGY & COMMERCE 26 | COMM ON OVRSIGHT & GOVT REFORM,COMM ON OVRSIGHT & GOVT REFORM 27 | HOUSE ADMINISTRATION,HOUSE ADMINISTRATION 28 | COMMITTEE ON NATURAL RESOURCES,COMMITTEE ON NATURAL RESOURCES 29 | COMMITTEE ON FOREIGN AFFAIRS,COMMITTEE ON FOREIGN AFFAIRS 30 | COMMITTEE ON BENGHAZI,COMMITTEE ON BENGHAZI 31 | COMMITTEE ON JUDICIARY,COMMITTEE ON JUDICIARY 32 | TRANSPORTATION-INFRASTRUCTURE,TRANSPORTATION-INFRASTRUCTURE 33 | COMMITTEE ON RULES,COMMITTEE ON RULES 34 | "COMM ON SCIENCE, SPACE & TECH","COMM ON SCIENCE, SPACE & TECH" 35 | COMMITTEE ON SMALL BUSINESS,COMMITTEE ON SMALL BUSINESS 36 | COMMITTEE ON ETHICS,COMMITTEE ON ETHICS 37 | VETERANS' AFFAIRS,VETERANS' AFFAIRS 38 | COMMITTEE ON WAYS AND MEANS,COMMITTEE ON WAYS AND MEANS 39 | INTELLIGENCE,INTELLIGENCE 40 | HOMELAND SECURITY,HOMELAND SECURITY 41 | GENERAL ADMIN - SPEC & SELECT,GENERAL ADMIN - SPEC & SELECT 42 | HOUSE CHILD CARE CENTER,HOUSE CHILD CARE CENTER 43 | JOINT COMMMITTEE ON TAXATION,JOINT COMMMITTEE ON TAXATION 44 | OFFICE OF CONGRESSIONAL ETHICS,OFFICE OF CONGRESSIONAL ETHICS 45 | -------------------------------------------------------------------------------- /views/staffer.erb: -------------------------------------------------------------------------------- 1 | <% content_for :footer_id do %>footer-person<% end %> 2 | 3 | 16 | 17 |
18 | 19 |
20 |

<%= display_name staffer %>

21 |
22 | Download As CSV 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | <% positions.each do |position| %> 35 | 36 | 37 | 42 | 45 | 48 | 51 | 52 | <% end %> 53 | 54 |
TitleOfficeContact NumberRoom/BuildingYear/Quarter
<%= title_listing position %> 38 | 39 | <%= position['office']['name'] %> 40 | 41 | 43 | <%= position['office']['phone'] %> 44 | 46 | <%= room_for position['office']['building'], position['office']['room'] %> 47 | 49 | <%= position['quarter'] %> 50 |
55 |
56 | 57 |
58 |
59 | 64 |
65 | 66 |
67 |
68 | 69 |
70 |
-------------------------------------------------------------------------------- /models.rb: -------------------------------------------------------------------------------- 1 | class Position 2 | include Mongoid::Document 3 | include Mongoid::Timestamps 4 | 5 | # finding all positions by an office or staffer 6 | index "office.slug" 7 | index "staffer.slug" 8 | 9 | # various search form options 10 | index :quarter 11 | index "title.name" 12 | index "office.member.state" 13 | index "office.member.party" 14 | index "office.member.chamber" 15 | index "staffer.first_name" 16 | index "staffer.last_name" 17 | 18 | validates_presence_of :quarter 19 | end 20 | 21 | class Staffer 22 | include Mongoid::Document 23 | include Mongoid::Timestamps 24 | include Mongoid::Slug 25 | 26 | slug :name 27 | validates_uniqueness_of :slug 28 | index :slug 29 | 30 | field :name 31 | field :original_names, :type => Array 32 | field :first_name 33 | field :last_name 34 | 35 | index :original_names 36 | index [[:last_name, Mongo::ASCENDING], [:first_name, Mongo::ASCENDING]] 37 | 38 | # scope :alphabetical, 39 | end 40 | 41 | class Office 42 | include Mongoid::Document 43 | include Mongoid::Timestamps 44 | include Mongoid::Slug 45 | 46 | index :slug 47 | slug :name 48 | validates_uniqueness_of :slug 49 | 50 | field :original_names, :type => Array 51 | field :name 52 | field :office_type 53 | field :chamber 54 | 55 | index :original_names 56 | index :chamber 57 | index :name 58 | index :office_type 59 | 60 | index "committee.id" 61 | index "member.bioguide_id" 62 | 63 | 64 | scope :members, :where => {:office_type => "member"} 65 | scope :committees, :where => {:office_type => "committee"} 66 | scope :others, :where => {:office_type => "other"} 67 | 68 | scope :house, :where => {:chamber => 'house'} 69 | scope :senate, :where => {:chamber => 'senate'} 70 | 71 | def member? 72 | office_type == 'member' 73 | end 74 | 75 | def committee? 76 | office_type == 'committee' 77 | end 78 | 79 | def other? 80 | office_type == 'other' 81 | end 82 | end 83 | 84 | class Title 85 | include Mongoid::Document 86 | include Mongoid::Timestamps 87 | 88 | field :name 89 | field :original_names, :type => Array 90 | 91 | index :name 92 | index :original_names 93 | 94 | validates_uniqueness_of :name 95 | end 96 | 97 | class Quarter 98 | include Mongoid::Document 99 | include Mongoid::Timestamps 100 | 101 | field :name 102 | index :name 103 | 104 | validates_uniqueness_of :name 105 | end 106 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## House Staff Directory 2 | 3 | A staff directory for the House of Representatives, using data culled from the House' [quarterly disbursement records](http://disbursements.house.gov/). 4 | 5 | The House publishes each quarter's data by the end of the following quarter, so the records will generally be 3-6 months out of date. 6 | 7 | ### Setup 8 | 9 | Depends on Ruby 1.8.7 - not tested on anything higher. This needs to be updated. 10 | 11 | Install dependencies with bundler: 12 | 13 | ```bash 14 | bundle install 15 | ``` 16 | 17 | Copy config.ru: 18 | 19 | ```bash 20 | cp config.ru.example config.ru 21 | ``` 22 | 23 | Copy config/config.yml.example: 24 | 25 | ```cp config/config.yml.example config/config.yml ``` 26 | 27 | Run with unicorn: 28 | 29 | ```bash 30 | bundle exec unicorn 31 | ``` 32 | 33 | ### Loading new staff records 34 | 35 | It's an old, blunt, violent process. 36 | 37 | 1. Get the CSVs generated from the [sunlightlabs/disbursements](https://github.com/sunlightlabs/disbursements) process. 38 | 39 | 2. Put them into the `data/` directory and run: 40 | 41 | ```bash 42 | rake load:all 43 | ``` 44 | 45 | 3. Wait for an hour or more as the data is blown away and reloaded from scratch. It's not very efficient, and the website will show incomplete data for that time. 46 | 47 | 4. Update the 'as of' date displayed on the home page in views/index.erb to the last day of the quarter processed. 48 | 49 | 50 | ### data/ expectations 51 | 52 | The `data/` directory expects four CSV files: 53 | 54 | * `staffers.csv`: Unique staffer names as they appear in the original disbursement data, and any standardizations or corrections to those titles. 55 | * `titles.csv`: Unique titles as they appear in the original disbursement data, and any standardizations or corrections to those titles. 56 | * `offices.csv`: Unique office names as they appear in the original disbursement data, and any standardizations or corrections to those titles. Expanded details for each office (committee ID, building, room, telephone number) are added by hand. 57 | * `positions.csv`: The "join table" that links staffer names, title names, and office names together as a "position" for each quarter. 58 | 59 | ### Why not the Senate? 60 | 61 | The [Senate's expenditure data](http://www.senate.gov/legislative/common/generic/report_secsen.htm) is published every 6 months, can be quite out of date, and is much more difficult to parse than the House's. 62 | 63 | Our [parser for the House is here](https://github.com/sunlightlabs/disbursements). The Senate parser is still experimental, but data can be found [here](http://sunlightfoundation.com/blog/2014/08/05/now-its-easier-to-account-for-how-the-senate-spends-your-money/). 64 | 65 | ### License 66 | 67 | Currently [GPLv3](LICENSE). -------------------------------------------------------------------------------- /views/index.erb: -------------------------------------------------------------------------------- 1 | <% content_for :header do %> 2 | 9 | <% end %> 10 | 11 | <% content_for :footer_id do %>footer-home<% end %> 12 | 13 |
14 | 15 |
16 |
17 | 24 |
25 | 26 | 75 | 76 |
-------------------------------------------------------------------------------- /views/_search.erb: -------------------------------------------------------------------------------- 1 | <% 2 | id = "directory-search-form" 3 | if defined?(position) 4 | id = "#{id}-#{position}" 5 | end 6 | %> 7 | 8 |
9 | 80 |
-------------------------------------------------------------------------------- /csv.rb: -------------------------------------------------------------------------------- 1 | require 'csv' 2 | 3 | def csv? 4 | (params[:format] == 'csv') or (params[:captures] and (params[:captures][0] == '.csv')) 5 | end 6 | 7 | def csv_out(filename) 8 | response['Content-Type'] = 'text/csv' 9 | response['Content-Disposition'] = "attachment;filename=#{filename}" 10 | end 11 | 12 | 13 | 14 | def members_to_csv(members) 15 | csv_out 'members.csv' 16 | 17 | CSV.generate do |csv| 18 | csv << [ 19 | "Name", "Phone", "Building", "Room", 20 | "Bioguide ID", "Title", "First Name", "Last Name", "Suffix", 21 | "Party", "State", "District", "In Office" 22 | ] 23 | 24 | members.each do |member| 25 | leg = member['member'] 26 | csv << [ 27 | member.name, member.phone, member.building, member.room, 28 | leg['bioguide_id'], leg['title'], leg['firstname'], leg['lastname'], leg['name_suffix'], 29 | leg['party'], leg['state'], leg['district'], leg['in_office'] 30 | ] 31 | end 32 | end 33 | end 34 | 35 | def committees_to_csv(committees) 36 | csv_out 'committees.csv' 37 | 38 | CSV.generate do |csv| 39 | csv << [ 40 | "Name", "Phone", "Building", "Room", 41 | "Committee ID" 42 | ] 43 | 44 | committees.each do |committee| 45 | csv << [ 46 | committee.name, committee.phone, committee.building, committee.room, 47 | committee['committee']['id'] 48 | ] 49 | end 50 | end 51 | end 52 | 53 | def offices_to_csv(offices) 54 | csv_out 'offices.csv' 55 | 56 | CSV.generate do |csv| 57 | csv << [ 58 | "Name", "Phone", "Building", "Room" 59 | ] 60 | 61 | offices.each do |office| 62 | csv << [office.name, office.phone, office.building, office.room] 63 | end 64 | end 65 | end 66 | 67 | def office_to_csv(office, positions) 68 | names = {'committee' => 'committee', 'member' => 'member', 'other' => 'office'} 69 | csv_out "#{names[office.office_type]}.csv" 70 | 71 | CSV.generate do |csv| 72 | csv << [ 73 | "Office", "Quarter", "Staffer", "Title" 74 | ] 75 | 76 | positions.each do |position| 77 | csv << [position['office']['name'], position['quarter'], position['staffer']['name'], position['title']['name']] 78 | end 79 | end 80 | end 81 | 82 | def staffer_to_csv(staffer, positions) 83 | csv_out 'staffer.csv' 84 | 85 | CSV.generate do |csv| 86 | csv << [ 87 | "Staffer", "Quarter", "Title", "Office", "Phone", "Building", "Room" 88 | ] 89 | 90 | positions.each do |position| 91 | csv << [staffer.name, position['quarter'], position['title']['name'], position['office']['name'], position['office']['phone'], position['office']['building'], position['office']['room']] 92 | end 93 | end 94 | end 95 | 96 | def positions_to_csv(positions) 97 | csv_out 'positions.csv' 98 | 99 | CSV.generate do |csv| 100 | csv << [ 101 | "Staffer", "Quarter", "Title", "Office", "Phone", "Building", "Room" 102 | ] 103 | 104 | positions.each do |position| 105 | csv << [position['staffer']['name'], position['quarter'], position['title']['name'], position['office']['name'], position['office']['phone'], position['office']['building'], position['office']['room']] 106 | end 107 | end 108 | end -------------------------------------------------------------------------------- /views/positions.erb: -------------------------------------------------------------------------------- 1 | <% content_for :header_class do %>header-internal<% end %> 2 | <% content_for :footer_id do %>footer-internal<% end %> 3 | 4 | 30 | 31 |
32 | 33 | <% if positions %> 34 | 47 | 48 |
49 | 50 |
51 | 52 | <% if positions.any? %> 53 | 54 | 55 | Download As CSV 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | <% positions.each do |position| %> 75 | 76 | 81 | 82 | 83 | 88 | 91 | 94 | 95 | <% end %> 96 | 97 | 98 |
NameYear/QuarterTitle/PositionOfficeContact NumberOffice/Building
77 | 78 | <%= display_name position['staffer'] %> 79 | 80 | <%= position['quarter'] %><%= title_listing position %> 84 | 85 | <%= position['office']['name'] %> 86 | 87 | 89 | <%= position['office']['phone'] %> 90 | 92 | <%= room_for position['office']['building'], position['office']['room'] %> 93 |
99 | 100 | <% else %> 101 | 102 |

103 | No staffers found for that search. 104 |

105 | 106 | <% end %> 107 | 108 |
109 | 110 | <% else %> 111 | 112 |

113 | You need to specify at least one field in your search. 114 |

115 | 116 | <% end %> 117 | 118 |
-------------------------------------------------------------------------------- /views/office.erb: -------------------------------------------------------------------------------- 1 | <% content_for :footer_id do %>footer-person<% end %> 2 | 3 | 15 | 16 | <% if office['office_type'] != 'member' %> 17 |
18 | 19 |
20 |

<%= office['name'] %>

21 |
22 | Download As CSV 23 | 24 | 25 | 26 | 27 | <% else %> 28 |
29 | <% member = office['member'] %> 30 | 31 | 68 | 69 |
74 | <% end %> 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | <% positions.each do |position| %> 89 | 90 | 93 | 96 | 99 | 100 | <% end %> 101 | 102 | 103 |
NameTitle/PositionYear/Quarter
91 | <%= list_name position['staffer'] %> 92 | 94 | <%= title_listing position %> 95 | 97 | <%= position['quarter'] %> 98 |
104 |
105 | 106 |
107 |
108 | 114 |
115 | 116 |
117 |
118 | 119 |
120 |
-------------------------------------------------------------------------------- /public/javascripts/jquery.tablesorter.pager.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.extend({ 3 | tablesorterPager: new function() { 4 | 5 | function updatePageDisplay(c) { 6 | var s = $(c.cssPageDisplay,c.container).val((c.page+1) + c.seperator + c.totalPages); 7 | } 8 | 9 | function setPageSize(table,size) { 10 | var c = table.config; 11 | c.size = size; 12 | c.totalPages = Math.ceil(c.totalRows / c.size); 13 | c.pagerPositionSet = false; 14 | moveToPage(table); 15 | fixPosition(table); 16 | } 17 | 18 | function fixPosition(table) { 19 | var c = table.config; 20 | if(!c.pagerPositionSet && c.positionFixed) { 21 | var c = table.config, o = $(table); 22 | if(o.offset) { 23 | c.container.css({ 24 | top: o.offset().top + o.height() + 'px', 25 | position: 'absolute' 26 | }); 27 | } 28 | c.pagerPositionSet = true; 29 | } 30 | } 31 | 32 | function moveToFirstPage(table) { 33 | var c = table.config; 34 | c.page = 0; 35 | moveToPage(table); 36 | } 37 | 38 | function moveToLastPage(table) { 39 | var c = table.config; 40 | c.page = (c.totalPages-1); 41 | moveToPage(table); 42 | } 43 | 44 | function moveToNextPage(table) { 45 | var c = table.config; 46 | c.page++; 47 | if(c.page >= (c.totalPages-1)) { 48 | c.page = (c.totalPages-1); 49 | } 50 | moveToPage(table); 51 | } 52 | 53 | function moveToPrevPage(table) { 54 | var c = table.config; 55 | c.page--; 56 | if(c.page <= 0) { 57 | c.page = 0; 58 | } 59 | moveToPage(table); 60 | } 61 | 62 | 63 | function moveToPage(table) { 64 | var c = table.config; 65 | if(c.page < 0 || c.page > (c.totalPages-1)) { 66 | c.page = 0; 67 | } 68 | 69 | renderTable(table,c.rowsCopy); 70 | } 71 | 72 | function renderTable(table,rows) { 73 | 74 | var c = table.config; 75 | var l = rows.length; 76 | var s = (c.page * c.size); 77 | var e = (s + c.size); 78 | if(e > rows.length ) { 79 | e = rows.length; 80 | } 81 | 82 | 83 | var tableBody = $(table.tBodies[0]); 84 | 85 | // clear the table body 86 | 87 | $.tablesorter.clearTableBody(table); 88 | 89 | for(var i = s; i < e; i++) { 90 | 91 | //tableBody.append(rows[i]); 92 | 93 | var o = rows[i]; 94 | var l = o.length; 95 | for(var j=0; j < l; j++) { 96 | 97 | tableBody[0].appendChild(o[j]); 98 | 99 | } 100 | } 101 | 102 | fixPosition(table,tableBody); 103 | 104 | $(table).trigger("applyWidgets"); 105 | 106 | if( c.page >= c.totalPages ) { 107 | moveToLastPage(table); 108 | } 109 | 110 | updatePageDisplay(c); 111 | } 112 | 113 | this.appender = function(table,rows) { 114 | 115 | var c = table.config; 116 | 117 | c.rowsCopy = rows; 118 | c.totalRows = rows.length; 119 | c.totalPages = Math.ceil(c.totalRows / c.size); 120 | 121 | renderTable(table,rows); 122 | }; 123 | 124 | this.defaults = { 125 | size: 10, 126 | offset: 0, 127 | page: 0, 128 | totalRows: 0, 129 | totalPages: 0, 130 | container: null, 131 | cssNext: '.next', 132 | cssPrev: '.prev', 133 | cssFirst: '.first', 134 | cssLast: '.last', 135 | cssPageDisplay: '.pagedisplay', 136 | cssPageSize: '.pagesize', 137 | seperator: "/", 138 | positionFixed: true, 139 | appender: this.appender 140 | }; 141 | 142 | this.construct = function(settings) { 143 | 144 | return this.each(function() { 145 | 146 | config = $.extend(this.config, $.tablesorterPager.defaults, settings); 147 | 148 | var table = this, pager = config.container; 149 | 150 | $(this).trigger("appendCache"); 151 | 152 | config.size = parseInt($(".pagesize",pager).val()); 153 | 154 | $(config.cssFirst,pager).click(function() { 155 | moveToFirstPage(table); 156 | return false; 157 | }); 158 | $(config.cssNext,pager).click(function() { 159 | moveToNextPage(table); 160 | return false; 161 | }); 162 | $(config.cssPrev,pager).click(function() { 163 | moveToPrevPage(table); 164 | return false; 165 | }); 166 | $(config.cssLast,pager).click(function() { 167 | moveToLastPage(table); 168 | return false; 169 | }); 170 | $(config.cssPageSize,pager).change(function() { 171 | setPageSize(table,parseInt($(this).val())); 172 | return false; 173 | }); 174 | }); 175 | }; 176 | 177 | } 178 | }); 179 | // extend plugin scope 180 | $.fn.extend({ 181 | tablesorterPager: $.tablesorterPager.construct 182 | }); 183 | 184 | })(jQuery); -------------------------------------------------------------------------------- /staffers.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require './config/environment' 4 | require 'sinatra/content_for' 5 | require './csv' 6 | require './helpers' 7 | 8 | 9 | set :public, 'public' 10 | set :views, 'views' 11 | 12 | # used by the search dropdown 13 | before do 14 | @committees = Office.committees.order_by([[:name, :asc]]).all 15 | @offices = Office.others.order_by([[:name, :asc]]).all 16 | @quarter_names = Quarter.all.distinct(:name).sort.reverse 17 | end 18 | 19 | get '/' do 20 | # first 10 only 21 | members = Office.members.house.order_by([["member.in_office", :desc], ["member.lastname", :asc], ["member.firstname", :asc]]).limit(10).all 22 | 23 | erb :index, locals: { 24 | committees: @committees, 25 | offices: @offices, 26 | members: members 27 | } 28 | end 29 | 30 | get '/faq' do 31 | erb :faq 32 | end 33 | 34 | get '/positions' do 35 | search = {} 36 | 37 | if params[:quarter].present? 38 | search[:quarter] = params[:quarter] 39 | end 40 | 41 | if params[:first_name].present? and params[:first_name].size >= 2 42 | search["staffer.first_name"] = regex_for params[:first_name] 43 | end 44 | 45 | if params[:last_name].present? and params[:last_name].size >= 2 46 | search["staffer.last_name"] = regex_for params[:last_name] 47 | end 48 | 49 | if params[:title].present? and params[:title].size >= 2 50 | search["title.name"] = regex_for params[:title] 51 | end 52 | 53 | if params[:state].present? 54 | search["office.member.state"] = params[:state] 55 | end 56 | 57 | if params[:party].present? 58 | search["office.member.party"] = params[:party] 59 | end 60 | 61 | if params[:office].present? 62 | search["office.slug"] = params[:office] 63 | end 64 | 65 | if params[:committee].present? 66 | search["office.slug"] = params[:committee] 67 | end 68 | 69 | if search.keys.empty? 70 | positions = nil 71 | else 72 | positions = Position.where(search).desc(:quarter) 73 | # positions = positions.limit(50) 74 | end 75 | 76 | if csv? 77 | positions_to_csv positions 78 | else 79 | erb :positions, locals: {positions: positions} 80 | end 81 | end 82 | 83 | get '/staffer/:slug' do 84 | staffer = Staffer.where(:slug => params[:slug]).first 85 | positions = Position.where("staffer.slug" => params[:slug]).all 86 | 87 | if csv? 88 | staffer_to_csv staffer, positions 89 | else 90 | erb :staffer, :locals => {:staffer => staffer, :positions => positions} 91 | end 92 | end 93 | 94 | 95 | # office URLs 96 | get '/office/:slug' do 97 | office = Office.where(:slug => params[:slug]).first 98 | positions = Position.where("office.slug" => params[:slug]).all 99 | 100 | if csv? 101 | office_to_csv office, positions 102 | else 103 | erb :office, :locals => {:office => office, :positions => positions} 104 | end 105 | end 106 | 107 | # support legacy /legislator/ URL 108 | get %r{/(?:member|legislator)/(\w\d+)} do 109 | bioguide_id = params[:captures].first 110 | office = Office.where("member.bioguide_id" => bioguide_id).first 111 | positions = Position.where("office.member.bioguide_id" => bioguide_id).order_by([["staffer.last_name", :asc], ["staffer.first_name", :asc]]).all 112 | 113 | if csv? 114 | office_to_csv office, positions 115 | else 116 | erb :office, :locals => {:office => office, :positions => positions} 117 | end 118 | end 119 | 120 | get '/committee/:committee_id' do 121 | office = Office.where("committee.id" => params[:committee_id]).first 122 | 123 | positions = Position.where("office.committee.id" => params[:committee_id]).order_by([["staffer.last_name", :asc], ["staffer.first_name", :asc]]).all 124 | 125 | if csv? 126 | office_to_csv office, positions 127 | else 128 | erb :office, :locals => {:office => office, :positions => positions} 129 | end 130 | end 131 | 132 | 133 | get '/members' do 134 | conditions = {} 135 | 136 | [:state, :district, :title].each do |key| 137 | if params[key] 138 | conditions["member.#{key}"] = params[key] 139 | end 140 | end 141 | 142 | offices = Office.members.house.where(conditions).order_by([["member.in_office", :desc], ["member.lastname", :asc], ["member.firstname", :asc]]).all 143 | 144 | if csv? 145 | members_to_csv offices 146 | else 147 | erb :offices, :locals => {:offices => offices, :type => 'members'} 148 | end 149 | end 150 | 151 | get '/committees' do 152 | offices = Office.committees.order_by([[:name, :asc]]).all 153 | 154 | if csv? 155 | committees_to_csv offices 156 | else 157 | erb :offices, :locals => {:offices => offices, :type => 'committees'} 158 | end 159 | end 160 | 161 | get '/offices' do 162 | offices = Office.others.order_by([[:name, :asc]]).all 163 | 164 | if csv? 165 | offices_to_csv offices 166 | else 167 | erb :offices, :locals => {:offices => offices, :type => 'offices'} 168 | end 169 | end 170 | 171 | 172 | def regex_for(value) 173 | regex_value = value.dup 174 | %w{+ ? . * ^ $ ( ) [ ] { } | \ }.each {|char| regex_value.gsub! char, "\\#{char}"} 175 | /^#{regex_value}$/i 176 | end 177 | -------------------------------------------------------------------------------- /views/offices.erb: -------------------------------------------------------------------------------- 1 | <% content_for :footer_id do %>footer-person<% end %> 2 | 3 | 31 | 32 |
33 | 34 |
35 |

36 | <% if type == 'committees' %> 37 | House Committees 38 | <% elsif type == 'offices' %> 39 | House Offices 40 | <% elsif type == 'members' %> 41 | <% if params[:state].present? and params[:district].present? %> 42 | House Members from <%= state_codes[params[:state]] %>, District <%= district_for params[:district] %> 43 | <% elsif params[:state].present? and params[:title].present? %> 44 | <%= plural_title_for params[:title] %> from <%= state_codes[params[:state]] %> 45 | <% elsif params[:state].present? %> 46 | House Members from <%= state_codes[params[:state]] %> 47 | <% else %> 48 | House Members 49 | <% end %> 50 | <% end %> 51 |

52 | 53 |
54 | Download As CSV 55 | " role="main"> 56 | 57 | 58 | 59 | <% if type == 'members' %> 60 | 61 | <% end %> 62 | 63 | 64 | <% if type == 'members' %> 65 | 66 | 67 | 68 | 69 | 70 | 71 | <% else %> 72 | 73 | 74 | 75 | 76 | 77 | <% end %> 78 | 79 | 80 | 81 | <% offices.each do |office| %> 82 | 83 | <% if type == 'members' %> 84 | 89 | 92 | 95 | 103 | <% else %> 104 | 109 | 114 | 119 | <% end %> 120 | 121 | <% end %> 122 | 123 | 124 |
NamePartyStateIn Office
OfficesContact NumberOffice/Building
85 | 86 | <%= member_name office %> 87 | 88 | 90 | <%= party_names[office['member']['party']] %> 91 | 93 | <%= office['member']['state'] %><%= office['member']['district'].to_i != 0 ? "-#{office['member']['district']}" : "" %> 94 | 96 | <% if office['member']['in_office'] %> 97 | Currently in office 98 | Yes 99 | <% else %> 100 | No 101 | <% end %> 102 | 105 | 106 | <%= office['name'] %> 107 | 108 | 110 | <% unless out?(office) %> 111 | <%= office['phone'] %> 112 | <% end %> 113 | 115 | <% unless out?(office) %> 116 | <%= room_for office['building'], office['room'] %> 117 | <% end %> 118 |
125 |
126 | 127 |
128 |
129 | 134 |
135 | 136 |
137 |
138 | 139 |
140 |
-------------------------------------------------------------------------------- /helpers.rb: -------------------------------------------------------------------------------- 1 | helpers do 2 | 3 | def out?(office) 4 | office.office_type == 'member' and office['member']['in_office'] == false 5 | end 6 | 7 | def office_path(office) 8 | if office['office_type'] == "member" 9 | "/member/#{office['member']['bioguide_id']}" 10 | elsif office['office_type'] == "committee" 11 | "/committee/#{office['committee']['id']}" 12 | else 13 | "/office/#{office['slug']}" 14 | end 15 | end 16 | 17 | def member_name(office) 18 | member = office['member'] 19 | "#{member['title']}. #{member['nickname'].present? ? member['nickname'] : member['firstname']} #{member['lastname']} #{member['name_suffix']}".strip 20 | end 21 | 22 | def staffer_path(staffer) 23 | "/staffer/#{staffer['slug']}" 24 | end 25 | 26 | def csv_path 27 | "#{request.fullpath}#{request.fullpath['?'] ? "&" : "?"}format=csv" 28 | end 29 | 30 | def capitalize(words) 31 | first_pass = words.split(' ').map {|word| word.capitalize}.join ' ' 32 | first_pass.gsub(/\/(\w)/) {" / #{$1.upcase}"} 33 | end 34 | 35 | def title_listing(position) 36 | name = capitalize position['title']['name'] 37 | # original_names = title['original_names'].join("
").gsub("\"", "\\\"") 38 | original_name = position['original_title'] 39 | 40 | "#{name}
#{original_name}\">i
" 41 | end 42 | 43 | def display_name(staffer) 44 | "#{staffer['first_name']} #{staffer['last_name']}".strip 45 | end 46 | 47 | def list_name(staffer) 48 | "#{staffer['last_name']}, #{staffer['first_name']}".strip 49 | end 50 | 51 | def format_quarter(quarter) 52 | pieces = quarter.match /^(\d+)Q(\d)/ 53 | year = pieces[1] 54 | quarter = pieces[2] 55 | ordinal = { 56 | "1" => "st", 57 | "2" => "nd", 58 | "3" => "rd", 59 | "4" => "th" 60 | }[quarter] 61 | 62 | "#{year} #{quarter}#{ordinal} Quarter" 63 | end 64 | 65 | def room_for(building, room, long = false) 66 | if building and room 67 | "#{room} #{building.split(' ').first}#{" House Office Building" if long}" 68 | else 69 | nil 70 | end 71 | end 72 | 73 | def district_for(district) 74 | if district.to_i == 0 75 | "At-Large" 76 | else 77 | district 78 | end 79 | end 80 | 81 | def plural_title_for(title) 82 | { 83 | "Del" => "Delegates", 84 | "Sen" => "Senators", 85 | "Com" => "Commissioners", 86 | "Rep" => "Representatives" 87 | }[title] 88 | end 89 | 90 | def state_codes 91 | { 92 | "AL" => "Alabama", 93 | "AK" => "Alaska", 94 | "AZ" => "Arizona", 95 | "AR" => "Arkansas", 96 | "CA" => "California", 97 | "CO" => "Colorado", 98 | "CT" => "Connecticut", 99 | "DE" => "Delaware", 100 | "DC" => "District of Columbia", 101 | "FL" => "Florida", 102 | "GA" => "Georgia", 103 | "HI" => "Hawaii", 104 | "ID" => "Idaho", 105 | "IL" => "Illinois", 106 | "IN" => "Indiana", 107 | "IA" => "Iowa", 108 | "KS" => "Kansas", 109 | "KY" => "Kentucky", 110 | "LA" => "Louisiana", 111 | "ME" => "Maine", 112 | "MD" => "Maryland", 113 | "MA" => "Massachusetts", 114 | "MI" => "Michigan", 115 | "MN" => "Minnesota", 116 | "MS" => "Mississippi", 117 | "MO" => "Missouri", 118 | "MT" => "Montana", 119 | "NE" => "Nebraska", 120 | "NV" => "Nevada", 121 | "NH" => "New Hampshire", 122 | "NJ" => "New Jersey", 123 | "NM" => "New Mexico", 124 | "NY" => "New York", 125 | "NC" => "North Carolina", 126 | "ND" => "North Dakota", 127 | "OH" => "Ohio", 128 | "OK" => "Oklahoma", 129 | "OR" => "Oregon", 130 | "PA" => "Pennsylvania", 131 | "PR" => "Puerto Rico", 132 | "RI" => "Rhode Island", 133 | "SC" => "South Carolina", 134 | "SD" => "South Dakota", 135 | "TN" => "Tennessee", 136 | "TX" => "Texas", 137 | "UT" => "Utah", 138 | "VT" => "Vermont", 139 | "VA" => "Virginia", 140 | "WA" => "Washington", 141 | "WV" => "West Virginia", 142 | "WI" => "Wisconsin", 143 | "WY" => "Wyoming" 144 | } 145 | end 146 | 147 | def party_names 148 | { 149 | "R" => "Republican", 150 | "D" => "Democrat", 151 | "I" => "Independent" 152 | } 153 | end 154 | 155 | def search_titles 156 | [ 157 | "Case Worker", 158 | "Chief of Staff", 159 | "Clerk", 160 | "Constituent Liaison", 161 | "Counsel", 162 | "District Director", 163 | "Executive Assistant", 164 | "Legislative Assistant", 165 | "Legislative Correspondent", 166 | "Legislative Director", 167 | "Office Manager", 168 | "Press Secretary", 169 | "Professional Staff", 170 | "Research Assistant", 171 | "Scheduler", 172 | "Staff Assistant", 173 | "System Administrator" 174 | ] 175 | end 176 | 177 | end 178 | 179 | # stolen from http://github.com/cschneid/irclogger/blob/master/lib/partials.rb 180 | # and made a lot more robust by me 181 | # this implementation uses erb by default. if you want to use any other template mechanism 182 | # then replace `erb` on line 13 and line 17 with `haml` or whatever 183 | module Sinatra::Partials 184 | def partial(template, *args) 185 | template_array = template.to_s.split('/') 186 | template = template_array[0..-2].join('/') + "/_#{template_array[-1]}" 187 | options = args.last.is_a?(Hash) ? args.pop : {} 188 | options.merge!(:layout => false) 189 | if collection = options.delete(:collection) then 190 | collection.inject([]) do |buffer, member| 191 | buffer << erb(:"#{template}", options.merge(:layout => 192 | false, :locals => {template_array[-1].to_sym => member})) 193 | end.join("\n") 194 | else 195 | erb(:"#{template}", options) 196 | end 197 | end 198 | end 199 | 200 | helpers Sinatra::Partials -------------------------------------------------------------------------------- /views/layout.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | House Staff Directory 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 65 | 66 | 67 | 74 |
75 | 76 |
77 |

78 | House Staff Directory 79 |

80 | 81 | <% yield_content :header %> 82 | 83 |
84 |
85 | 86 | <%= yield %> 87 | 88 | 142 | 143 | 158 | 159 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /public/css/donorbanner.css: -------------------------------------------------------------------------------- 1 | /* Donor Banner */ 2 | #donor_banner { 3 | display:block; 4 | font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif; 5 | position: fixed; 6 | z-index: 10000000; 7 | width: 100%; 8 | min-height: 50px; 9 | margin-top: -20px; 10 | padding-top: 20px; 11 | background: #f3ce51 url(http://assets.sunlightfoundation.com.s3.amazonaws.com/donorbanner/donorbanner_border.png) center bottom repeat-x; 12 | top: 0; 13 | left: 0; 14 | transition: all .8s ease-in-out; 15 | -webkit-transition: all .8s ease-in-out; /** Chrome & Safari **/ 16 | -moz-transition: all .8s ease-in-out; /** Firefox **/ 17 | -o-transition: all .8s ease-in-out; /** Opera **/ 18 | -moz-transform: scale(1) rotate(1deg) translate(0px, 288px) skew(0deg, 0deg); 19 | -webkit-transform: scale(1) rotate(1deg) translate(0px, -288px) skew(0deg, 0deg); 20 | -o-transform: scale(1) rotate(1deg) translate(0px, -288px) skew(0deg, 0deg); 21 | -ms-transform: scale(1) rotate(1deg) translate(0px, -288px) skew(0deg, 0deg); 22 | transform: scale(1) rotate(1deg) translate(0px, -288px) skew(0deg, 0deg); 23 | -webkit-box-shadow: 3px 3px 5px #bfb78f; 24 | box-shadow: 3px 3px 5px #bfb78f; 25 | } 26 | .no-csstransforms #donor_banner { 27 | margin-top:-304px; 28 | } 29 | 30 | html.revealbanner { 31 | transition: all .8s ease-in-out; 32 | -webkit-transition: all .8s ease-in-out; /** Chrome & Safari **/ 33 | -moz-transition: all .8s ease-in-out; /** Firefox **/ 34 | -o-transition: all .8s ease-in-out; /** Opera **/ 35 | -moz-transform: translate(0px, 55px); 36 | -webkit-transform: translate(0px, 55px); 37 | -o-transform: translate(0px, 55px); 38 | -ms-transform: translate(0px, 55px); 39 | transform: translate(0px, 55px); 40 | } 41 | html.no-csstransforms.revealbanner { 42 | margin-top: 55px; 43 | } 44 | 45 | html.revealbanner.collapsed { 46 | -moz-transform: translate(0px, 0px); 47 | -webkit-transform: translate(0px, 0px); 48 | -o-transform: translate(0px, 0px); 49 | -ms-transform: translate(0px, 0px); 50 | transform: translate(0px, 0px); 51 | } 52 | html.no-csstransforms.revealbanner.collapsed { 53 | margin-top: 0; 54 | } 55 | 56 | .revealbanner #donor_banner { 57 | margin-top: -20px; 58 | -moz-transform: scale(1) rotate(1deg) translate(0px, -55px) skew(0deg, 0deg); 59 | -webkit-transform: scale(1) rotate(1deg) translate(0px, -55px) skew(0deg, 0deg); 60 | -o-transform: scale(1) rotate(1deg) translate(0px, -55px) skew(0deg, 0deg); 61 | transform: scale(1) rotate(1deg) translate(0px, -55px) skew(0deg, 0deg); /* This order is wrong and important and should stay this way */ 62 | -ms-transform: scale(1) rotate(1deg) translate(0px, 0) skew(0deg, 0deg); 63 | -webkit-box-shadow: 3px 3px 5px #bfb78f; 64 | box-shadow: 3px 3px 5px #bfb78f; 65 | } 66 | .revealbanner.collapsed #donor_banner { 67 | -moz-transform: scale(1) rotate(1deg) translate(0px, -200px) skew(0deg, 0deg); 68 | -webkit-transform: scale(1) rotate(1deg) translate(0px, -200px) skew(0deg, 0deg); 69 | -o-transform: scale(1) rotate(1deg) translate(0px, -200px) skew(0deg, 0deg); 70 | -ms-transform: scale(1) rotate(1deg) translate(0px, -200px) skew(0deg, 0deg); 71 | } 72 | .no-csstransforms.revealbanner.collapsed #donor_banner { 73 | margin-top: -194px; 74 | } 75 | 76 | #donor_banner .donorcontainer { 77 | width: 960px; 78 | margin: 0 auto; 79 | -moz-transform: scale(1) rotate(-1deg) translate(0px, 0px) skew(0deg, 0deg); 80 | -webkit-transform: scale(1) rotate(-1deg) translate(0px, 0px) skew(0deg, 0deg); 81 | -o-transform: scale(1) rotate(-1deg) translate(0px, 0px) skew(0deg, 0deg); 82 | -ms-transform: scale(1) rotate(-1deg) translate(0px, 0px) skew(0deg, 0deg); 83 | transform: scale(1) rotate(-1deg) translate(0px, 0px) skew(0deg, 0deg); 84 | } 85 | #donor_banner .sf_logo { 86 | background: url(http://assets.sunlightfoundation.com.s3.amazonaws.com/donorbanner/smalldonor_sf_logo.png) center top no-repeat; 87 | text-indent: -9999em; 88 | display: block; 89 | width: 119px; 90 | height: 54px; 91 | float: left; 92 | margin-left: -10px; 93 | } 94 | #donor_banner .because { 95 | float: left; 96 | width: 487px; 97 | color: #4d5b5c; 98 | font-size: 13px; 99 | text-align: right; 100 | padding: 0 20px 0 5px; 101 | border-right: 1px solid #ffffff; 102 | margin: 10px 0; 103 | line-height: 1.3; 104 | min-height: 72px; 105 | } 106 | #donor_banner .because.quote { 107 | font-style: italic; 108 | } 109 | #donor_banner .attribution { 110 | display: block; 111 | text-align: right; 112 | } 113 | #donor_banner .attribution em{ 114 | font-weight: bold; 115 | } 116 | #donor_banner .bubble { 117 | background: url(http://assets.sunlightfoundation.com.s3.amazonaws.com/donorbanner/donorbanner_bubble.png) center bottom no-repeat; 118 | min-height: 330px; 119 | width: 392px; 120 | position: absolute; 121 | right: -55px; 122 | bottom: -288px; 123 | } 124 | .no-csstransforms #donor_banner .bubble { 125 | bottom: -192px; 126 | right: 155px; 127 | } 128 | 129 | #donor_banner .donationplea { 130 | position: absolute; 131 | top: 8px; 132 | right: 0; 133 | width: 287px; 134 | } 135 | .no-csstransforms #donor_banner .donationplea { 136 | top: 18px; 137 | right: 210px; 138 | } 139 | 140 | #donor_banner .donationplea span { 141 | display: block; 142 | } 143 | #donor_banner .donationplea span.line2 { 144 | margin-left: 60px; 145 | } 146 | #donor_banner .donationplea p { 147 | font-size: 16px; 148 | margin-left: 45px; 149 | color: #4d5b5c; 150 | line-height: 1.2; 151 | margin-bottom: 25px; 152 | } 153 | #donor_banner .donationplea p em { 154 | font-weight: bold; 155 | } 156 | #donor_banner .donationplea .btn { 157 | margin-left: 74px; 158 | } 159 | #donor_banner h2 { 160 | font-family: "Georgia", "Times New Roman", "Times", serif; 161 | color: #4d5b5c; 162 | font-style: italic; 163 | text-transform: none; 164 | font-size: 24px; 165 | font-weight: bold; 166 | line-height: 1.3em; 167 | margin-bottom: 25px; 168 | margin-top: 0; 169 | } 170 | #donor_banner .notnow { 171 | background: url(http://assets.sunlightfoundation.com.s3.amazonaws.com/donorbanner/donorbanner_close.png) center top no-repeat; 172 | display: block; 173 | position: absolute; 174 | width: 126px; 175 | height: 36px; 176 | bottom: 4px; 177 | left: 121px; 178 | opacity: 1; 179 | text-align: center; 180 | padding: 9px 0 0 0; 181 | color: #ffffff; 182 | text-shadow: none; 183 | font-size: 14px; 184 | text-decoration: underline; 185 | cursor: pointer; 186 | border: none; 187 | padding-top: 0; 188 | } 189 | #donor_banner .notnow:focus { 190 | outline: none; 191 | } 192 | 193 | #donor_banner .close:hover, #donor_banner .notnow:hover { 194 | text-decoration: none; 195 | } 196 | #donor_banner .closebanner { 197 | opacity: 0; 198 | -webkit-transition: all .3s ease-in-out; /** Chrome & Safari **/ 199 | -moz-transition: all .3s ease-in-out; /** Firefox **/ 200 | -o-transition: all .3s ease-in-out; /** Opera **/ 201 | transition: all .3s ease-in-out; /** Opera **/ 202 | } 203 | #donor_banner .btn { 204 | border: 3px solid #dad8d1; 205 | border: 3px solid rgba(218, 216, 209, 0.8); 206 | background: #cb610b; 207 | background-image: -webkit-linear-gradient(#cb610b 0%, #bc5b0a 55%, #cb610b 55%); 208 | background-image: -moz-linear-gradient(#cb610b 0%, #bc5b0a 55%, #cb610b 55%); 209 | background-image: -ms-linear-gradient(#cb610b 0%, #bc5b0a 55%, #cb610b 55%); 210 | background-image: -o-linear-gradient(#cb610b 0%, #bc5b0a 55%, #cb610b 55%); 211 | background-image: linear-gradient(#cb610b 0%, #bc5b0a 55%, #cb610b 55%); 212 | color: #eee; 213 | font-family: "Georgia", "Times New Roman", "Times", serif; 214 | font-size: 17px; 215 | font-weight: normal; 216 | padding: 0.45em 1.8em; 217 | -webkit-background-clip: padding; 218 | -moz-background-clip: padding; 219 | background-clip: padding-box; 220 | -webkit-border-radius: 6px; 221 | -moz-border-radius: 6px; 222 | -ms-border-radius: 6px; 223 | -o-border-radius: 6px; 224 | border-radius: 6px; 225 | text-shadow: 0 0 0; 226 | cursor: pointer; 227 | } 228 | #donor_banner .btn:hover { 229 | background: #b85301; 230 | background-image: -webkit-linear-gradient(#b85301 0%, #a54e01 55%, #b85301 55%); 231 | background-image: -moz-linear-gradient(#b85301 0%, #a54e01 55%, #b85301 55%); 232 | background-image: -ms-linear-gradient(#b85301 0%, #a54e01 55%, #b85301 55%); 233 | background-image: -o-linear-gradient(#b85301 0%, #a54e01 55%, #b85301 55%); 234 | background-image: linear-gradient(#b85301 0%, #a54e01 55%, #b85301 55%); 235 | color: #fff; 236 | -webkit-background-clip: padding; 237 | -moz-background-clip: padding; 238 | background-clip: padding-box; 239 | } 240 | 241 | #donor_banner.donorbanner_2 .bubble { 242 | bottom: -297px; 243 | } 244 | #donor_banner.donorbanner_2 .donationplea p, .openstates_1#donor_banner .donationplea p{ 245 | margin-top: -15px; 246 | } -------------------------------------------------------------------------------- /views/faq.erb: -------------------------------------------------------------------------------- 1 | <% content_for(:footer_id) do %>footer-person<% end %> 2 | 3 |
4 |
5 |

Frequently Asked Questions

6 |
7 |
8 |

What is the House Staff Directory?

9 |

The House Staff Directory makes it easy to search publicly available information about the House of Representatives to allow Americans to better communicate with their elected representatives. It is no secret that congressional staff are the lifeblood of Congress, but identifying the best staffer to speak with about a particular issue can be a daunting challenge. 10 |

11 |
12 |
13 |

What makes the House Staff Directory unique?

14 | Four things combine to make the House Staff Directory unique: 15 |
    16 |
  • It's free. 17 |
  • 18 |
  • It's a directory of all House employees, including those who provide legislative support to the functioning of Congress, with data no more than six months old. 19 |
  • 20 |
  • You can perform complex searches by staff title, political party, fiscal quarter or state and download that information into a spreadsheet. 21 |
  • 22 |
  • You can search over multiple quarters to see the congressional employment history for a particular staffer. 23 |
  • 24 |
25 |
26 |
27 |

What can I do with this information?

28 | The better question is: What do you want to do? 29 |
    30 |
  • If you want a list of all of the press secretaries whose bosses serve on a particular committee, or a myriad of other combinations of information, you can get it here. 31 |
  • 32 |
  • If you met a staffer but lost his or her contact information, you can look that up here. 33 |
  • 34 |
  • If you're trying to find the phone number for a legislative support office, it's available here. 35 |
  • 36 |
  • If you want the room numbers for certain offices, you can get them here. 37 |
  • 38 |
  • You can see what other offices a staffer has worked in, or his or her title. 39 |
  • 40 |
41 | Found another use? Tell us about it. 42 |
43 |
44 |

Where does your data come from? How recent is it?

45 | We draw all of our data from three public sources. 46 |
    47 |
  • A list of addresses and phone numbers for each personal and committee office is published online by the House Clerk's office. This information is updated after each election and as rooms change. 48 |
  • 49 |
  • Addresses and phone numbers for many miscellaneous House offices are made available in a PDF from the House Clerk. This information is updated at least once every two years. 50 |
  • 51 |
  • Our list of House staff comes from the the House Statement of Disbursements, a quarterly document published by the Clerk that lists every penny spent in the House. Although the report is published in print every quarter, it takes some time (but no more than 3 months) for the Clerk to publish this information online in PDF format. We scrape the data and publish it online on our House Expenditure Reports database in a more usable format. Then, we go through this data and identify each staffer, their title and where they work. 52 |
  • 53 |
54 | At most, our list of staff in a particular office is six months out of date. 55 |
56 |
57 |

Where is the direct contact information?

58 |

Please note that we only use publicly available information. We do not provide direct phone numbers for particular staff, but rather the number for the main office. For some staff, we do not know if they are based in a district office or Capitol Hill, so we just included the main office phone number in Washington, D.C. 59 |

60 |
61 |
62 |

Why is a staffer listed in more than one office?

63 | There are several possible reasons. 64 |
    65 |
  • The data we are drawing from is a snapshot of a quarter of the year. Staff move around a lot, including serving in two offices during a particular quarter. Thus, they will show up twice. 66 |
  • 67 |
  • Some staff are shared staff, meaning they have responsibilities in multiple offices. This happens most often with caucus staff and committee staff. 68 |
  • 69 |
  • Two different staffers may have identical names. We have no way of knowing that they are two entirely different people. 70 |
  • 71 |
72 |
73 |
74 |

Where are the email addresses?

75 |

The House of Representatives does not make publicly available email addresses for staff. Some private companies do gather this information and sell it for a fee. If you wish for a particular staffer's email address, it is best to ask for it. To communicate with a congressional office electronically, the House recommends that you use their WriteRep system. 76 |

77 |
78 |
79 |

Where is staff salary information?

80 |

Legistorm publishes House staff salary information. Although we do have some salary data in the raw data, our purpose was to build a directory. 81 |

82 |
83 |
84 |

How do I know who works on a particular issue?

85 |

Unfortunately, there is no public source that identifies the issue areas on which particular staffers work. Your best bet is to contact the office that you're interested in. If you wish to get a list of all staffers who work in your issue area, you will need to contact a commercial vendor that compiles this information. 86 |

87 |
88 |
89 |

I found a mistake.

90 |

We're sorry to hear that, and hope you'll tell us about it. Please keep in mind some kinds of mistakes we cannot fix because we're not the source of the error. But do let us know. 91 |

92 |
93 |
94 |

I have an idea to make this better.

95 |

Great! We'd love to hear from you. Drop us a line. 96 |

97 |
98 |
99 |

I want to play with the data.

100 |

101 | You can download the expenditure data upon which our staffer data is premised, in bulk, from Sunlight's website. There's no copyright on the data, so have fun. 102 | 103 | The code we wrote to generate the data is available and dedicated to the worldwide public domain. You can also check out our various data APIs. If you build something with our data, we'd love it if you let us know. 104 | 105 | Finally, you can get the source code for this website, licensed under the General Public License v3. 106 |

107 |
108 |
109 |

Who is responsible for the House Staff Directory?

110 |

The Sunlight Foundation, a non-profit organization that works to make the government more transparent by encouraging public information to be published online, in real time, and in machine readable formats. 111 |

112 |
113 |
114 | 115 |
116 |
117 | 122 |
123 | 124 |
125 |
126 | 127 |
128 |
-------------------------------------------------------------------------------- /data/load.rake: -------------------------------------------------------------------------------- 1 | task loading_environment: :environment do 2 | require 'csv' 3 | require 'congress' 4 | 5 | Congress.key = config['sunlight_api_key'] 6 | end 7 | 8 | namespace :load do 9 | 10 | desc "Loads titles from titles.csv" 11 | task titles: :loading_environment do 12 | start = Time.now 13 | debug = ENV['debug'].present? 14 | 15 | # blow away and start from scratch 16 | Title.delete_all 17 | 18 | i = 0 19 | CSV.foreach("data/csv/titles.csv") do |row| 20 | i += 1 21 | next if i == 1 # header row 22 | 23 | title_name_original = row[0].strip 24 | title_name = row[1].blank? ? title_name_original : row[1].strip 25 | 26 | if title = Title.where(:name => title_name).first 27 | title.original_names << title_name_original 28 | puts "Updated title: #{title_name} with original title #{title_name_original}" if debug 29 | else 30 | title = Title.new :name => title_name 31 | title.original_names = [title_name_original] 32 | puts "New title: #{title_name} with original title #{title_name_original}" if debug 33 | end 34 | 35 | title.save! 36 | end 37 | 38 | puts "Loaded #{Title.count} titles." 39 | puts "\nFinished in #{Time.now - start} seconds." 40 | end 41 | 42 | 43 | desc "Loads offices from offices.csv" 44 | task offices: :loading_environment do 45 | start = Time.now 46 | 47 | offline = ENV['offline'].present? 48 | 49 | # don't empty out offices; we don't keep past committees anywhere, so they must be preserved 50 | committees = offline ? {} : committee_cache 51 | 52 | i = 0 53 | CSV.foreach("data/csv/offices.csv") do |row| 54 | i += 1 55 | next if i == 1 # header row 56 | 57 | office_from_row row, i, committees 58 | end 59 | 60 | unless offline 61 | Congress.legislators(per_page: "all")['results'].each do |legislator| 62 | office_from_legislator legislator 63 | end 64 | end 65 | 66 | puts "Loaded #{Office.count} offices." 67 | puts "\t#{Office.members.count} members" 68 | puts "\t#{Office.committees.count} committees" 69 | puts "\t#{Office.others.count} other offices" 70 | puts "\nFinished in #{Time.now - start} seconds." 71 | end 72 | 73 | 74 | desc "Loads staffers from staffers.csv" 75 | task staffers: :loading_environment do 76 | start = Time.now 77 | 78 | Staffer.delete_all 79 | 80 | i = 0 81 | CSV.foreach("data/csv/staffers.csv") do |row| 82 | i += 1 83 | next if i == 1 # header row 84 | 85 | staffer_from_row row, i 86 | end 87 | 88 | puts "Loaded #{Staffer.count} staffers." 89 | puts "\nFinished in #{Time.now - start} seconds." 90 | end 91 | 92 | 93 | desc "Loads database (from scratch) from staffers.csv and titles.csv" 94 | task positions: :loading_environment do 95 | start = Time.now 96 | 97 | debug = ENV['debug'].present? 98 | limit = ENV['limit'].present? ? ENV['limit'].to_i : nil 99 | 100 | Position.delete_all 101 | 102 | i = 0 103 | CSV.foreach("data/csv/positions.csv") do |row| 104 | i += 1 105 | next if i == 1 # header row 106 | 107 | # for debugging usage 108 | break if limit and i > limit 109 | 110 | staffer_name_original = strip row[0] 111 | if staffer_name_original.blank? 112 | puts "WARNING: No staffer name given, skipping row #{i}" 113 | next 114 | end 115 | 116 | title_original = strip row[1] 117 | quarter = strip row[2] 118 | bioguide_id = strip row[3] 119 | office_name_original = strip row[4] 120 | 121 | # also strip off title addendums, we're ignoring these and collapsing them programmatically 122 | ["(OTHER COMPENSATION)", "(OVERTIME)"].each do |addendum| 123 | title_original.sub! addendum, '' 124 | end 125 | title_original.strip! 126 | 127 | 128 | unless staffer = Staffer.where(:original_names => staffer_name_original).first 129 | puts "Couldn't locate staffer by name #{staffer_name_original} in row #{i}, skipping" 130 | next 131 | end 132 | 133 | unless title = Title.where(:original_names => title_original).first 134 | puts "Couldn't locate title by name #{title_original} in row #{i}, skipping" 135 | next 136 | end 137 | 138 | office = nil 139 | if bioguide_id.present? 140 | unless office = Office.where("member.bioguide_id" => bioguide_id).first 141 | puts "Couldn't locate legislator by bioguide_id #{bioguide_id} in row #{i}, skipping" 142 | next 143 | end 144 | 145 | else 146 | unless office = Office.where(original_names: office_name_original).first 147 | puts "Couldn't locate office by name #{office_name_original} in row #{i}, skipping" 148 | next 149 | end 150 | end 151 | 152 | position = Position.where( 153 | :quarter => quarter, 154 | "title.name" => title['name'], 155 | "staffer.slug" => staffer['slug'], 156 | "office.slug" => office['slug'] 157 | ).first 158 | 159 | if position.nil? 160 | position = Position.new( 161 | quarter: quarter, 162 | title: title.attributes, 163 | staffer: staffer.attributes, 164 | office: office.attributes, 165 | original_title: title_original 166 | ) 167 | 168 | position.save! 169 | end 170 | 171 | puts "[#{i}][#{quarter}] #{staffer.name} works as #{title.name} for #{office.name}" if debug 172 | end 173 | 174 | puts "\nLoaded in #{Position.count} staffer positions." 175 | puts "\nFinished in #{Time.now - start} seconds." 176 | end 177 | 178 | desc "Load in quarters from positions" 179 | task quarters: :loading_environment do 180 | Quarter.delete_all 181 | 182 | Position.all.distinct(:quarter).each do |quarter| 183 | Quarter.create! name: quarter 184 | end 185 | 186 | puts "\nLoaded in #{Quarter.count} quarters: #{Quarter.all.map(&:name).join ', '}" 187 | end 188 | 189 | desc "Run all loading tasks in sequence" 190 | task all: [:loading_environment, "load:titles", "load:offices", "load:staffers", "load:positions", "load:quarters"] do 191 | end 192 | 193 | end 194 | 195 | # format name from Sunlight Congress API 196 | def titled_name(legislator) 197 | "#{legislator.title}. #{legislator.nickname.present? ? legislator.nickname : legislator.firstname} #{legislator.lastname} #{legislator.name_suffix}".strip 198 | end 199 | 200 | # split congress_office out into room and building 201 | def split_office(congress_office) 202 | words = congress_office.split ' ' 203 | [words[0], words[1]] 204 | end 205 | 206 | def strip(str) 207 | str.present? ? str.strip : nil 208 | end 209 | 210 | # office from a row in offices.csv 211 | def office_from_row(row, i, committees) 212 | office_name_original = strip row[0] 213 | office_name = strip(row[1]) || office_name_original 214 | 215 | committee_id = strip row[2] 216 | phone = strip row[3] 217 | building = strip row[4] 218 | room = strip row[5] 219 | 220 | debug = ENV['debug'].present? 221 | 222 | if committee_id.present? 223 | office = Office.where("committee.id" => committee_id).first 224 | 225 | # there may be multiple spellings of a given committee that cause it to show up in duplicate rows in committees.csv 226 | if office 227 | 228 | if !office.original_names.include?(office_name_original) 229 | office.original_names << office_name_original 230 | puts "Updated committee office: #{office.name} with new original name #{office_name_original}" if debug 231 | else 232 | puts "Found old committee office with this name, not touching" if debug 233 | end 234 | 235 | # in case remote committee data changed 236 | committee = committees[committee_id] 237 | if committee 238 | office.attributes = { 239 | :name => committee.name, 240 | :committee => { 241 | :id => committee_id, 242 | :name => committee.name, 243 | :chamber => committee.chamber 244 | } 245 | } 246 | office.save! 247 | end 248 | 249 | else 250 | committee = committees[committee_id] 251 | 252 | if committee 253 | office = Office.new name: committee.name 254 | office.attributes = { 255 | :original_names => [office_name_original], 256 | :office_type => "committee", 257 | :phone => phone, 258 | :room => room, 259 | :building => building, 260 | :chamber => 'house', 261 | :committee => { 262 | :id => committee_id, 263 | :name => committee.name, 264 | :chamber => committee.chamber 265 | } 266 | } 267 | else 268 | puts "BAD OR OLD COMMITTEE_ID: #{committee_id}, row #{i}" 269 | return 270 | end 271 | 272 | puts "New committee office: #{office.name} with original name #{office_name_original}" if debug 273 | end 274 | 275 | office.save! 276 | 277 | else 278 | office = Office.where(name: office_name).first 279 | 280 | if office 281 | 282 | if !office.original_names.include?(office_name_original) 283 | office.original_names << office_name_original 284 | puts "Updated other office: #{office.name} with new original name #{office_name_original}" if debug 285 | end 286 | 287 | else 288 | office = Office.new :name => office_name 289 | office.attributes = { 290 | :original_names => [office_name_original], 291 | :office_type => "other", 292 | :phone => phone, 293 | :room => room, 294 | :building => building, 295 | :chamber => 'house' 296 | } 297 | 298 | puts "New other office: #{office.name} with original name #{office_name_original}" if debug 299 | end 300 | 301 | office.save! 302 | end 303 | end 304 | 305 | def office_from_legislator(legislator) 306 | 307 | room, building = split_office legislator.office 308 | 309 | unless office = Office.where("member.bioguide_id" => legislator.bioguide_id).first 310 | office = Office.new name: titled_name(legislator) 311 | puts "[#{legislator.bioguide_id}] not found, making new record" 312 | else 313 | puts "[#{legislator.bioguide_id}] found, updating existing record" 314 | end 315 | 316 | office.attributes = { 317 | original_names: [], 318 | office_type: "member", 319 | phone: legislator.phone, 320 | room: room, 321 | building: building, 322 | chamber: legislator.chamber, 323 | member: { 324 | bioguide_id: legislator.bioguide_id, 325 | firstname: legislator.first_name, 326 | lastname: legislator.last_name, 327 | nickname: legislator.nickname, 328 | party: legislator.party, 329 | name_suffix: legislator.name_suffix, 330 | title: legislator.title, 331 | chamber: legislator.chamber, 332 | congress_office: legislator.office, 333 | phone: legislator.phone, 334 | state: legislator.state, 335 | district: legislator.district, 336 | in_office: legislator.in_office 337 | } 338 | } 339 | 340 | puts "[#{legislator.bioguide_id}] New or updated member office: #{office.name}" 341 | office.save! 342 | end 343 | 344 | def staffer_from_row(row, i) 345 | debug = ENV['debug'].present? 346 | 347 | staffer_name_original = strip row[0] 348 | staffer_name = strip(row[1]) || staffer_name_original 349 | 350 | if staffer_name_original.blank? 351 | puts "WARNING: no staffer original name provided for row #{i}, skipping" 352 | return 353 | end 354 | 355 | 356 | if staffer = Staffer.where(name: staffer_name).first 357 | staffer.original_names << staffer_name_original 358 | puts "[#{i}] Updated staffer: #{staffer_name} with original name #{staffer_name_original}" if debug 359 | 360 | else 361 | # standardize fields 362 | last_name, first_name = staffer_name.split(/,\s?/) 363 | 364 | if last_name 365 | last_name = last_name.split(/\s+/).map {|n| n.capitalize}.join " " 366 | end 367 | 368 | if first_name 369 | first_name = first_name.split(/\s+/).map {|n| n.capitalize}.join " " 370 | end 371 | 372 | staffer = Staffer.new 373 | staffer.attributes = { 374 | name: [first_name, last_name].join(" "), 375 | original_names: [staffer_name_original], 376 | first_name: first_name, 377 | last_name: last_name, 378 | } 379 | 380 | puts "[#{i}] New staffer: #{staffer_name} with original name #{staffer_name_original}" if debug 381 | end 382 | 383 | staffer.save! 384 | end 385 | 386 | def committee_cache 387 | # all parent committees 388 | cache = {} 389 | committees = Congress.committees(subcommittee: false, per_page: 'all')['results'] 390 | committees.each do |committee| 391 | cache[committee.id] = committee 392 | end 393 | 394 | cache 395 | end -------------------------------------------------------------------------------- /public/javascripts/jquery.tablesorter.min.js: -------------------------------------------------------------------------------- 1 | 2 | (function($){$.extend({tablesorter:new 3 | function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((ab)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((ba)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i',a,""].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},x=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=C(e[d],"function"),C(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),y={}.hasOwnProperty,z;!C(y,"undefined")&&!C(y.call,"undefined")?z=function(a,b){return y.call(a,b)}:z=function(a,b){return b in a&&C(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e}),q.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},q.canvastext=function(){return!!e.canvas&&!!C(b.createElement("canvas").getContext("2d").fillText,"function")},q.csstransforms=function(){return!!G("transform")},q.csstransforms3d=function(){var a=!!G("perspective");return a&&"webkitPerspective"in g.style&&w("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},q.csstransitions=function(){return G("transition")};for(var H in q)z(q,H)&&(v=H.toLowerCase(),e[v]=q[H](),t.push((e[v]?"":"no-")+v));return e.addTest=function(a,b){if(typeof a=="object")for(var d in a)z(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},A(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.hasEvent=x,e.testProp=function(a){return E([a])},e.testAllProps=G,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;fSeth Flaxman, TurboVote", 155 | urls: { 156 | sf: "ES4WiEhrlKfiBxc3Kpg8cYRz2%2bRR%2f1fzLYu6pb7u65ZqNZbZieRkFg%3d%3d", 157 | ie: "ES4WiEhrlKfiBxc3Kpg8cfHj7Aebd6qH9LNcUSj633k83FgeurrtcQ%3d%3d", 158 | staffers: "ES4WiEhrlKfiBxc3Kpg8cWDlfqDH1uxY5GzS5yL%2bUQ1NCDg8WJMkzw%3d%3d", 159 | capitol_words: "ES4WiEhrlKfiBxc3Kpg8ce6YeCbWSMeQaQE3hAEXcsYfk%2fIGzT5K1g%3d%3d", 160 | politwoops: "ES4WiEhrlKfiBxc3Kpg8cWec3Th0pAGlo%2fQVClQngfMsYk%2bKWxuq0w%3d%3d", 161 | party_time: "ES4WiEhrlKfiBxc3Kpg8caGUCsN%2bZHf6YEjh93orBwwHoGnkTtSnkg%3d%3d", 162 | open_states: "ES4WiEhrlKfiBxc3Kpg8cSoqdq3HMdqJCp1bFf10GIejjsNc9W6rcQ%3d%3d", 163 | docket_wrench: "ES4WiEhrlKfiBxc3Kpg8cSeVmriKsVfjkNbyzSZnHB%2bi%2fEDOfmO2FQ%3d%3d" 164 | } 165 | }, 166 | 167 | // F 168 | { 169 | quote: true, 170 | bottom: -288, 171 | html: "“I came to Sunlight because I believe technology is incredibly powerful and government is incredibly important. I don't think there's any better place for working to bring the two together.” Tom Lee, Director of Sunlight Labs", 172 | urls: { 173 | sf: "ES4WiEhrlKdDXjRIAQuDVNDReMFLMcETTNSqPh2aPnJJfwJooSR4oQ%3d%3d", 174 | ie: "ES4WiEhrlKdDXjRIAQuDVMJKNCEZQctzkjD5GBZJ7VfCiWJS8KbKFQ%3d%3d", 175 | staffers: "ES4WiEhrlKdDXjRIAQuDVMuhVE%2f0VX25PmDKP4qWmwPhPUnnPC6r5w%3d%3d", 176 | capitol_words: "ES4WiEhrlKdDXjRIAQuDVLmIAHZlEp16be5Qmh5Hggspi8RZIjuzLg%3d%3d", 177 | politwoops: "ES4WiEhrlKdDXjRIAQuDVCfxOZR0J0x3nF%2fBimKx4D2gKuWLu%2bT%2bTQ%3d%3d", 178 | party_time: "ES4WiEhrlKdDXjRIAQuDVPoBVZzDImnRdKYZWksI0pDcke46DDHpNg%3d%3d", 179 | open_states: "ES4WiEhrlKdDXjRIAQuDVFRJyBUQ36TPvO6zVffwBy8XoaAY7ICiUA%3d%3d", 180 | docket_wrench: "ES4WiEhrlKdDXjRIAQuDVDDbD3qYmbsmplBi%2fBJTF5X3a68ujD8F0w%3d%3d" 181 | } 182 | }, 183 | 184 | // G 185 | { 186 | quote: true, 187 | bottom: -296, 188 | html: "“Sunlight was one of the first organizations to really engage the technology community around improving government, and the organization has continued to inspire and leverage people with these critical skills in this important task. They are the leaders in a movement that matters.” Jen Pahlka, Founder of Code for America", 189 | urls: { 190 | sf: "ES4WiEhrlKfGvWU2dfGp3eWO79ir6PwlHnibLmScIoFaYIOveDlleA%3d%3d", 191 | ie: "ES4WiEhrlKfGvWU2dfGp3Zbzsqph%2bGUtjX3zjy2%2bILW2PHhJUKUC0g%3d%3d", 192 | staffers: "ES4WiEhrlKfGvWU2dfGp3fLoCreho%2f7dwq1OECAps8HQqsz%2f9m5DRA%3d%3d", 193 | capitol_words: "ES4WiEhrlKfGvWU2dfGp3Y%2frdMS0Qx6UqbbQn1Svr3LllHNEGrCTuw%3d%3d", 194 | politwoops: "ES4WiEhrlKfGvWU2dfGp3R7JJHsqOyOeP5JQj%2fxiRMMsjikamB4p2Q%3d%3d", 195 | party_time: "ES4WiEhrlKfGvWU2dfGp3V3b6yfnZ4UlFeMxRQmtbWDedwubWbR%2fHA%3d%3d", 196 | open_states: "ES4WiEhrlKfGvWU2dfGp3YvzZnTOP6hbAlgKiBcRQWZ50WkjVeIpuw%3d%3d", 197 | docket_wrench: "ES4WiEhrlKfGvWU2dfGp3ZEkRrkhFGTTmNTBVOedFQAYOJKJNuBxRg%3d%3d" 198 | } 199 | }, 200 | 201 | // H 202 | { 203 | quote: true, 204 | bottom: -296, 205 | html: "“Since I began working at Sunlight six years ago, I've seen us evolve from a small non-profit working to expose money in politics to a global leader in the transparency movement. I'm thrilled to be working everyday to help define accountability and create a more open, inclusive democracy.” John Wonderlich, Sunlight Foundation Policy Director", 206 | urls: { 207 | sf: "ES4WiEhrlKfJPbNqTm4u93n2EKLmptx%2bXuXhGUhShTkMQxiJ934IDg%3d%3d", 208 | ie: "ES4WiEhrlKfJPbNqTm4u99TMzAAb%2fw22gmRZo%2btPIP1F5bfvt8SeEg%3d%3d", 209 | staffers: "ES4WiEhrlKfJPbNqTm4u9%2fRdaO2x4KdNAAioCGZChq%2bCU5oj%2f7Balw%3d%3d", 210 | capitol_words: "ES4WiEhrlKfJPbNqTm4u9%2fNo%2feShXRMNT658paou%2bCg1cud1cy%2frXg%3d%3d", 211 | politwoops: "ES4WiEhrlKfJPbNqTm4u92wcYDlYpBhwEkV7hT4QKDxgiCMsH2hW4w%3d%3d", 212 | party_time: "ES4WiEhrlKfJPbNqTm4u93LXjTO8fi7nYCtxHhbSdf5nQlWCkA5kLw%3d%3d", 213 | open_states: "ES4WiEhrlKfJPbNqTm4u9%2bOpUJXwEn9FaNSf3AeopvapdHZsh0K%2brg%3d%3d", 214 | docket_wrench: "ES4WiEhrlKfJPbNqTm4u9%2f%2b6uk2bgIknIr7m77ZLNvdxSWKbsy6Syg%3d%3d" 215 | } 216 | }, 217 | 218 | // I 219 | { 220 | quote: true, 221 | bottom: -288, 222 | html: "“The Sunlight Foundation exists to seize the potential that technology offers to hold government accountable. Open and accessible data in the hands of citizens are key to that.” Ellen Miller, Co-Founder and Executive Director of the Sunlight Foundation", 223 | urls: { 224 | sf: "ES4WiEhrlKcrHsC%2bG4xM%2fIpHYocnOuJGPqa%2fNUMT2%2fmkDvGXBgk28g%3d%3d", 225 | ie: "ES4WiEhrlKcrHsC%2bG4xM%2fP7qWUrWPfFnMgShofpe4jDVGot57NPwNA%3d%3d", 226 | staffers: "ES4WiEhrlKcrHsC%2bG4xM%2fJLtbIXcrupQgQhaW1WxAtG6uXfv2e1JPA%3d%3d", 227 | capitol_words: "ES4WiEhrlKcrHsC%2bG4xM%2fObwYT85cAhLI4hxs1wVSUDF8Ha1w26B4Q%3d%3d", 228 | politwoops: "ES4WiEhrlKcrHsC%2bG4xM%2fFS5KOb8TrRQNgySODlnPMc4YXyYBjCeFQ%3d%3d", 229 | party_time: "ES4WiEhrlKcrHsC%2bG4xM%2fHr6sONflyVB5uYb5v4HZ3m9NoZ0RZPqCw%3d%3d", 230 | open_states: "ES4WiEhrlKcrHsC%2bG4xM%2fKytDEZ3It4B%2fY7l9Qy6SPAxrAHsNsJScg%3d%3d", 231 | docket_wrench: "ES4WiEhrlKcrHsC%2bG4xM%2fFqbmuVWq8PXgPkp0ms1cLF5muRhipIMtA%3d%3d" 232 | } 233 | } 234 | ]; 235 | 236 | function abSwap(site) { 237 | var test = Banner.ab[Math.floor(Math.random() * Banner.ab.length)]; 238 | var $p = $("#donor_banner p.because"); 239 | 240 | if (test.quote) 241 | $p.addClass("quote") 242 | else 243 | $p.removeClass("quote"); 244 | 245 | $p.html(test.html); 246 | 247 | var $bubble = $("#donor_banner .bubble"); 248 | $bubble.css("bottom", "" + test.bottom + "px"); 249 | 250 | var url = test.urls[site]; 251 | var base = "https://services.myngp.com/ngponlineservices/contribution.aspx?X="; 252 | $("#donor_banner a.btn").attr("href", base + url); 253 | } 254 | 255 | $(document).ready(function() { 256 | var banner = new Banner($('html')); 257 | 258 | // HEY: change this to your site code, as seen in the above object 259 | abSwap('staffers'); 260 | }); 261 | 262 | })(jQuery); -------------------------------------------------------------------------------- /public/css/style.css: -------------------------------------------------------------------------------- 1 | /* HTML5 ✰ Boilerplate */ 2 | 3 | html, body, div, span, object, iframe, 4 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 5 | abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, 6 | small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, 7 | fieldset, form, label, legend, 8 | table, caption, tbody, tfoot, thead, tr, th, td, 9 | article, aside, canvas, details, figcaption, figure, 10 | footer, header, hgroup, menu, nav, section, summary, 11 | time, mark, audio, video { 12 | margin: 0; 13 | padding: 0; 14 | border: 0; 15 | font-size: 100%; 16 | font: inherit; 17 | vertical-align: baseline; 18 | } 19 | 20 | article, aside, details, figcaption, figure, 21 | footer, header, hgroup, menu, nav, section { 22 | display: block; 23 | } 24 | 25 | blockquote, q { quotes: none; } 26 | blockquote:before, blockquote:after, 27 | q:before, q:after { content: ""; content: none; } 28 | ins { background-color: #ff9; color: #000; text-decoration: none; } 29 | mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; } 30 | del { text-decoration: line-through; } 31 | abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; } 32 | table { border-collapse: collapse; border-spacing: 0; } 33 | hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; } 34 | input, select { vertical-align: middle; } 35 | 36 | body { font:13px/1.231 sans-serif; *font-size:small; } 37 | pre, code, kbd, samp { font-family: monospace, sans-serif; } 38 | 39 | html { overflow-y: scroll; } 40 | a:hover, a:active { outline: none; } 41 | ul, ol { margin-left: 2em; } 42 | ol { list-style-type: decimal; } 43 | nav ul, nav li { margin: 0; list-style:none; list-style-image: none; } 44 | small { font-size: 85%; } 45 | strong, th { font-weight: bold; } 46 | td { vertical-align: top; } 47 | sub, sup { font-size: 75%; line-height: 0; position: relative; } 48 | sup { top: -0.5em; } 49 | sub { bottom: -0.25em; } 50 | 51 | pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; } 52 | textarea { overflow: auto; } 53 | .ie6 legend, .ie7 legend { margin-left: -7px; } 54 | input[type="radio"] { vertical-align: text-bottom; } 55 | input[type="checkbox"] { vertical-align: bottom; } 56 | .ie7 input[type="checkbox"] { vertical-align: baseline; } 57 | .ie6 input { vertical-align: text-bottom; } 58 | label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; } 59 | button, input, select, textarea { margin: 0; } 60 | input:valid, textarea:valid { } 61 | input:invalid, textarea:invalid { border-radius: 1px; -moz-box-shadow: 0px 0px 5px red; -webkit-box-shadow: 0px 0px 5px red; box-shadow: 0px 0px 5px red; } 62 | .no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; } 63 | 64 | 65 | ::-moz-selection{ background: #43706d; color:#fff; text-shadow: none; } 66 | ::selection { background:#43706d; color:#fff; text-shadow: none; } 67 | a:link { -webkit-tap-highlight-color: #43706d; } 68 | button { width: auto; overflow: visible; } 69 | .ie7 img { -ms-interpolation-mode: bicubic; } 70 | 71 | 72 | .main a:link, #feature a:link, footer a:link {outline: none; color: #085069;} 73 | .main a:hover, #feature a:hover, footer a:hover {outline: none; color: #ffffff; background-color: #046380;} 74 | 75 | .clear { 76 | clear:both; 77 | } 78 | 79 | .replace { 80 | text-indent: -9999em; 81 | display: block; 82 | } 83 | 84 | ol, ul { 85 | list-style-type: none; 86 | margin-left: 0; 87 | } 88 | 89 | body { 90 | color: #5e5f5f; 91 | background: #f8f6ec url('/images/bg_tile.jpg') top left repeat-x; 92 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 93 | font-size: 13px; 94 | line-height: 16px; 95 | } 96 | 97 | .sf_mini_header { 98 | background: url('/images/sfapp_bg_header.jpg') center bottom repeat-x; 99 | background-color: rgb(231, 240, 233); 100 | height: 53px; 101 | width: 100%; 102 | margin-bottom: 4px; 103 | border-bottom: 1px dashed #fff; 104 | } 105 | 106 | .sf_mini_header_nav_wrapper{ 107 | width: 960px; 108 | padding: 0 10px; 109 | margin: 0 auto; 110 | } 111 | 112 | .sf_mini_header .sf-logo{ 113 | background: url('/images/sfapp_logo.png') top right no-repeat; 114 | float: right; 115 | height: 40px; 116 | width: 160px; 117 | margin-top: 5px; 118 | margin-right: 10px; 119 | text-indent: -9999px; 120 | color: transparent; 121 | } 122 | 123 | .sf_mini_header_nav_wrapper a{ 124 | text-decoration: none; 125 | color: #494442; 126 | } 127 | 128 | .sf_mini_header_nav_wrapper a:hover{ 129 | font-weight: bold; 130 | color: #2F4867; 131 | } 132 | 133 | .sf_mini_header_nav_wrapper .about{ 134 | width: 40px; 135 | } 136 | 137 | .sf_mini_header_nav_wrapper .blog{ 138 | width: 40px; 139 | } 140 | 141 | .sf_mini_header_nav_wrapper .projects{ 142 | width: 65px; 143 | } 144 | 145 | .sf_mini_header_nav_wrapper .contact{ 146 | width: 40px; 147 | } 148 | 149 | header { 150 | width: 950px; 151 | margin: 0 auto; 152 | padding: 0 5px 0 5px; 153 | } 154 | 155 | .header-internal { 156 | margin: 0 0 0 5%; 157 | } 158 | 159 | h1 { 160 | padding-top: 17px; 161 | float: left; 162 | } 163 | 164 | h1 a { 165 | background: url('/images/hsd_logo.png') top left no-repeat; 166 | width: 227px; 167 | height: 63px; 168 | } 169 | 170 | h2 { 171 | color: #426e6b; 172 | font-size: 22px; 173 | font-weight: bold; 174 | margin: 10px 0 20px 10px; 175 | } 176 | 177 | #feature { 178 | background: url('/images/featurebar.png') top left no-repeat; 179 | height: 177px; 180 | width: 613px; 181 | margin-top: 43px; 182 | margin-left: 17px; 183 | padding-top: 15px; 184 | padding-left: 45px; 185 | float: left; 186 | } 187 | 188 | #feature h2 { 189 | background: url('/images/findingstaffer.png') top left no-repeat; 190 | width: 355px; 191 | height: 75px; 192 | margin: 0px; 193 | } 194 | 195 | #feature p { 196 | width: 488px; 197 | font-size: 12px; 198 | line-height: 18px; 199 | margin: 10px 5px; 200 | } 201 | 202 | #feature p a { 203 | text-decoration: none; 204 | } 205 | 206 | #action { 207 | font-weight: bold; 208 | font-style: italic; 209 | } 210 | 211 | .main { 212 | background: #f4f1e7 url('/images/main_bg.jpg') left top repeat-y; 213 | width: 950px; 214 | } 215 | 216 | .main label { 217 | padding-bottom: 3px; 218 | display: block; 219 | } 220 | 221 | #main-home { 222 | margin: -70px auto 0; 223 | padding: 27px 5px 40px 5px; 224 | } 225 | 226 | #main-internal { 227 | width: 95%; 228 | min-width: 910px; 229 | margin: 6% 0 0 2%; 230 | padding: 27px 1% 40px 1%; 231 | } 232 | 233 | #main-person { 234 | width: 950px; 235 | margin: 80px auto 0; 236 | padding: 27px 5px 40px 5px; 237 | } 238 | 239 | #main-basic { 240 | width: 950px; 241 | margin: 30px auto 0; 242 | padding: 27px 5px 40px 5px; 243 | } 244 | 245 | #directory-search-area { 246 | margin-top: -40px; 247 | margin-left: -20px; 248 | float: left; 249 | } 250 | 251 | #directory-search-corner { 252 | background: url('/images/cornerpiece.png') left top no-repeat; 253 | width: 16px; 254 | height: 13px; 255 | } 256 | 257 | #directory-search { 258 | background: url('/images/searchbar_tile.jpg') top left repeat; 259 | width: 208px; 260 | color: #ffffff; 261 | padding: 15px 0 25px 25px; 262 | border-top: 1px #006666 solid; 263 | border-right: 1px #006666 solid; 264 | border-bottom: 1px #006666 solid; 265 | -moz-border-radius-topright: 25px; 266 | -webkit-border-top-right-radius: 25px; 267 | border-top-right-radius: 25px; 268 | -moz-border-radius-bottomright: 25px; 269 | -webkit-border-bottom-right-radius: 25px; 270 | border-bottom-right-radius: 25px; 271 | -moz-box-shadow: #8b8981 4px 4px 10px; 272 | -webkit-box-shadow: #8b8981 4px 4px 10px; 273 | box-shadow: #8b8981 4px 4px 10px; 274 | } 275 | 276 | #directory-search h2 { 277 | background: url('/images/searchtitle.png') left top no-repeat; 278 | width: 230px; 279 | height: 82px; 280 | margin-left: -7px; 281 | margin-bottom: 11px; 282 | } 283 | 284 | #directory-search-internal { 285 | background: url('/images/searchbar_tile.jpg') top left repeat; 286 | width: 850px; 287 | color: #ffffff; 288 | margin: -55px 0 0 27px; 289 | padding: 15px 0 15px 20px; 290 | border-top: 1px #006666 solid; 291 | border-right: 1px #006666 solid; 292 | border-left: 1px #006666 solid; 293 | -moz-border-radius-topright: 25px; 294 | -webkit-border-top-right-radius: 25px; 295 | border-top-right-radius: 25px; 296 | -moz-box-shadow: #8b8981 2px 2px 5px; 297 | -webkit-box-shadow: #8b8981 2px 2px 5px; 298 | box-shadow: #8b8981 2px 2px 5px; 299 | } 300 | 301 | #directory-search-internal.min { 302 | background: url('/images/searchbar_tile.jpg') top left repeat; 303 | width: 850px; 304 | color: #ffffff; 305 | margin: -55px 0 0 27px; 306 | padding: 10px 15px 40px 13px; 307 | border-top: 1px #006666 solid; 308 | border-right: 1px #006666 solid; 309 | border-left: 1px #006666 solid; 310 | -moz-border-radius-topright: 25px; 311 | -webkit-border-top-right-radius: 25px; 312 | border-top-right-radius: 25px; 313 | -moz-box-shadow: #8b8981 2px 2px 5px; 314 | -webkit-box-shadow: #8b8981 2px 2px 5px; 315 | box-shadow: #8b8981 2px 2px 5px; 316 | } 317 | 318 | #directory-search-internal h2 { 319 | background: url('/images/resultstitle.png') left top no-repeat; 320 | width: 253px; 321 | height: 48px; 322 | margin: -21px 10px 0 -30px; 323 | float: left; 324 | } 325 | 326 | #directory-search-internal.min h2 { 327 | background: url('/images/resultstitle.png') left top no-repeat; 328 | width: 253px; 329 | height: 48px; 330 | float: left; 331 | } 332 | 333 | #directory-search-internal.min h2 { 334 | margin: -16px 10px 0 -30px; 335 | } 336 | 337 | #directory-search-internal.min { 338 | padding: 10px 0 40px 20px; 339 | } 340 | 341 | 342 | a.toggle { 343 | width: 15px; 344 | height: 15px; 345 | margin: 0 0 0 -13px; 346 | text-indent: -9999em; 347 | display: block; 348 | float: left; 349 | position: absolute; 350 | } 351 | 352 | #directory-search-internal.min .toggle { 353 | margin: 0 0 0 -10px; 354 | } 355 | 356 | #directory-search-internal.min span.tip, #directory-search-internal.min form { 357 | display: none; 358 | } 359 | 360 | a.toggle.on { 361 | background: url('/images/toggle-on.png') left top no-repeat; 362 | } 363 | 364 | a.toggle.off { 365 | background: url('/images/toggle-off.png') left top no-repeat; 366 | } 367 | 368 | .tip { 369 | font-family: Georgia, "Times New Roman", Times, serif; 370 | font-style: italic; 371 | line-height: 17px; 372 | display: block; 373 | } 374 | 375 | #search-tip { 376 | width: 150px; 377 | } 378 | 379 | #results-tip { 380 | margin-bottom: 20px; 381 | } 382 | 383 | .info-icon { 384 | background-color: #e4e9e9; 385 | font-family: Georgia, serif; 386 | font-size: 10px; 387 | font-weight: bold; 388 | font-style: italic; 389 | margin: 0 0 0 2px; 390 | padding: 0 3px; 391 | border: 1px solid #e4e9e9; 392 | -moz-border-radius: 10px; 393 | -webkit-border-radius: 10px; 394 | border-radius: 10px; 395 | } 396 | 397 | .basic-form { 398 | margin-top: 17px; 399 | } 400 | 401 | .search-list li{ 402 | margin: 20px 0 0 0; 403 | display: inline-block; 404 | } 405 | 406 | #directory-search-form select { 407 | width: 190px; 408 | } 409 | 410 | #directory-search-form input { 411 | width: 183px; 412 | } 413 | 414 | #directory-search-form #search-button { 415 | background: url('/images/search_button.png') center top no-repeat; 416 | width: 141px; 417 | height: 33px; 418 | margin: 5px 0 0 25px; 419 | border: none; 420 | cursor: pointer; 421 | } 422 | 423 | #directory-search-form-internal li { 424 | margin: 5px 10px 0 0; 425 | } 426 | 427 | #directory-search-form-internal #list-year { 428 | margin: 5px 50px 0 0; 429 | } 430 | 431 | #directory-search-form-internal #list-button { 432 | vertical-align: bottom; 433 | } 434 | 435 | #directory-search-form-internal select { 436 | width: 150px; 437 | } 438 | 439 | #directory-search-form-internal input { 440 | width: 143px; 441 | } 442 | 443 | #directory-search-form-internal #search-button { 444 | background: url('/images/newsearch_button.png') center top no-repeat; 445 | width: 129px; 446 | height: 28px; 447 | margin: 0 0 0 8px; 448 | border: none; 449 | cursor: pointer; 450 | } 451 | 452 | .directory-search-area-right { 453 | width: 166px; 454 | margin: 22px -12px 0 0; 455 | float: left; 456 | } 457 | 458 | #right-of-text { 459 | margin: -10px -12px 0 0; 460 | } 461 | 462 | #directory-search-right-corner { 463 | background: url('/images/cornerpiece2.png') left top no-repeat; 464 | width: 29px; 465 | height: 13px; 466 | margin-left: 159px; 467 | } 468 | 469 | #directory-search-right { 470 | background: url('/images/searchbar_tile.jpg') top left repeat; 471 | width: 166px; 472 | color: #ffffff; 473 | padding: 15px 0 25px 20px; 474 | border-top: 1px #006666 solid; 475 | border-left: 1px #006666 solid; 476 | border-bottom: 1px #006666 solid; 477 | -moz-border-radius-topleft: 25px; 478 | -webkit-border-top-left-radius: 25px; 479 | border-top-left-radius: 25px; 480 | -moz-border-radius-bottomleft: 25px; 481 | -webkit-border-bottom-left-radius: 25px; 482 | border-bottom-left-radius: 25px; 483 | -moz-box-shadow: #8b8981 -4px 4px 10px; 484 | -webkit-box-shadow: #8b8981 -4px 4px 10px; 485 | box-shadow: #8b8981 -4px 4px 10px; 486 | } 487 | 488 | #new-search { 489 | background: url('/images/newsearch.png') left top no-repeat; 490 | width: 176px; 491 | height: 40px; 492 | margin-bottom: 10px; 493 | } 494 | 495 | #directory-search-form-right select { 496 | width: 150px; 497 | } 498 | 499 | #directory-search-form-right input { 500 | width: 143px; 501 | } 502 | 503 | #directory-search-form-right #search-button { 504 | background: url('/images/newsearch_button.png') center top no-repeat; 505 | width: 129px; 506 | height: 28px; 507 | margin: 5px 0 0 13px; 508 | border: none; 509 | cursor: pointer; 510 | } 511 | 512 | #person-info { 513 | background: url('/images/personbar.jpg') top left repeat; 514 | width: 615px; 515 | margin: -55px 0 0 27px; 516 | padding: 15px 5px 10px 18px; 517 | border-top: 1px #b7b5aa solid; 518 | border-right: 1px #b7b5aa solid; 519 | border-left: 1px #b7b5aa solid; 520 | -moz-border-radius-topright: 25px; 521 | -webkit-border-top-right-radius: 25px; 522 | border-top-right-radius: 25px; 523 | -moz-border-radius-topleft: 25px; 524 | -webkit-border-top-left-radius: 25px; 525 | border-top-left-radius: 25px; 526 | -moz-box-shadow: #8b8981 2px 2px 5px; 527 | -webkit-box-shadow: #8b8981 2px 2px 5px; 528 | box-shadow: #8b8981 2px 2px 5px; 529 | } 530 | 531 | #id_portrait { 532 | width: 88px; 533 | height: 110px; 534 | margin-right: 20px; 535 | border: 3px #828176 solid; 536 | -moz-border-radius: 5px; 537 | -webkit-border-radius: 5px; 538 | border-radius: 5px; 539 | float: left; 540 | } 541 | 542 | #vcard-wrapper { 543 | float: left; 544 | } 545 | 546 | #id_person-name { 547 | font-size: 23px; 548 | font-weight: bold; 549 | margin: 0px; 550 | padding: 5px 0; 551 | } 552 | 553 | #person-main h2 { 554 | color: #57564e; 555 | } 556 | 557 | #id_party-district { 558 | color: #57564e; 559 | font-size: 16px; 560 | font-weight: 600; 561 | margin-top: 7px; 562 | display: block; 563 | } 564 | 565 | .vcard { 566 | color: #828176; 567 | font-size: 16px; 568 | line-height: 22px; 569 | margin-top: 3px; 570 | } 571 | 572 | .type { 573 | display: none; 574 | } 575 | 576 | .center { 577 | background-color: #f8f7f1; 578 | border: 1px #eeeeee solid; 579 | -moz-box-shadow: #8b8981 0px 1px 5px; 580 | -webkit-box-shadow: #8b8981 0px 1px 5px; 581 | box-shadow: #8b8981 0px 1px 5px; 582 | } 583 | 584 | #directory-browse { 585 | width: 670px; 586 | margin-top: 55px; 587 | margin-left: 160px; 588 | padding: 18px 15px 44px 90px; 589 | } 590 | 591 | #directory-browse h4 { 592 | font-size: 18px; 593 | font-weight: bold; 594 | margin-bottom: 12px; 595 | } 596 | 597 | #search-results { 598 | margin-left: 17px; 599 | padding: 15px 15px 20px 15px; 600 | } 601 | 602 | #basic-content { 603 | width: 880px; 604 | margin-left: 17px; 605 | padding: 15px 0 20px 15px; 606 | } 607 | 608 | #directory-browse h3 { 609 | background: url('/images/browsetitle.png') left top no-repeat; 610 | width: 488px; 611 | height: 25px; 612 | margin: 0 0 20px 70px; 613 | } 614 | 615 | #browse-tip { 616 | color: #005454; 617 | width: 650px; 618 | text-align: center; 619 | font-size: 12px; 620 | font-family: Georgia, "Times New Roman", Times, serif; 621 | font-weight: bold; 622 | font-style: italic; 623 | display: block; 624 | } 625 | 626 | #download-csv { 627 | background: url('/images/download_button.png') center top no-repeat; 628 | width: 115px; 629 | height: 26px; 630 | margin: 0 0 13px 8px; 631 | } 632 | 633 | #three-columns { 634 | width: 700px; 635 | margin: 10px 22px 0 0; 636 | float: left; 637 | } 638 | 639 | .browse-column { 640 | width: 195px; 641 | margin-top: 30px; 642 | float: left; 643 | } 644 | 645 | .browse-column a.view:hover { 646 | color: #ffffff; 647 | } 648 | 649 | .basic-text { 650 | width: 630px; 651 | padding: 0 82px 0 10px; 652 | float: left; 653 | } 654 | 655 | .text-content { 656 | font-size: 15px; 657 | line-height: 20px; 658 | margin-bottom: 20px; 659 | } 660 | 661 | .text-content ul { 662 | margin: 5px 0; 663 | list-style-position: inside; 664 | list-style-type: disc; 665 | } 666 | 667 | .text-content li { 668 | margin: 0 0 8px 12px; 669 | } 670 | 671 | .text-content li:last-child { 672 | margin: 0 0 0 12px; 673 | } 674 | 675 | h4 a:link, h4 a:visited, h4 a:active { 676 | color: #005454; 677 | text-decoration: none; 678 | } 679 | 680 | h3 { 681 | font-size: 19px; 682 | font-weight: bold; 683 | margin-top: 3px; 684 | margin-bottom: 6px; 685 | } 686 | 687 | .person-page-staffers { 688 | margin-left: 10px; 689 | } 690 | 691 | .text-content h4 { 692 | margin-bottom: 10px; 693 | } 694 | 695 | #house-members { 696 | margin-left: 10px; 697 | } 698 | 699 | #house-offices { 700 | margin-left: 5px; 701 | } 702 | 703 | #house-comm { 704 | padding-right: 25px; 705 | } 706 | 707 | #house-offices, #house-comm { 708 | padding-left: 15px; 709 | border-left: #e4e9e9 2px solid; 710 | } 711 | 712 | .browse-column li a { 713 | color: #006666; 714 | font-size: 12px; 715 | line-height: 16px; 716 | text-decoration: none; 717 | display: block; 718 | } 719 | 720 | .browse-column li { 721 | margin-bottom: 10px; 722 | } 723 | 724 | .member-parties { 725 | color: #979797; 726 | font-size: 12px; 727 | } 728 | 729 | .browse-column a.view { 730 | color: #085069; 731 | font-size: 12px; 732 | font-family: Georgia, "Times New Roman", Times, serif; 733 | font-style: italic; 734 | } 735 | 736 | .basic-table { 737 | width: 100%; 738 | text-align: left; 739 | border-collapse: separate; 740 | border-spacing: 10px 0; 741 | } 742 | 743 | .basic-table a:link { 744 | color: #006666; 745 | } 746 | 747 | .basic-table th { 748 | color: #006666; 749 | background-color: #e4e9e9; 750 | padding: 5px; 751 | font-size: 14px; 752 | } 753 | 754 | /** This class is added dynamically by tablesorter to headers that are sortable **/ 755 | .basic-table th.header { 756 | cursor: pointer; 757 | } 758 | 759 | #results-table { 760 | min-width: 855px; 761 | } 762 | 763 | #person-results-table .column-sort, #results-table .column-sort, #three-col-results-table .column-sort { 764 | background: #e4e9e9 url('/images/sort_arrows.png') 5px 8px no-repeat; 765 | padding: 5px 5px 5px 20px; 766 | } 767 | 768 | .basic-table td { 769 | font-size: 12px; 770 | padding: 7px 0 7px 7px; 771 | border-bottom: 1px #e4e9e9 dashed; 772 | } 773 | 774 | #results-table td { 775 | width: 14%; 776 | } 777 | 778 | #results-table .wide-col { 779 | width: 30%; 780 | } 781 | 782 | #person-results-table .wide-col, #three-col-results-table .wide-col { 783 | width: 45%; 784 | } 785 | 786 | #person-results-table .short-col { 787 | width: 20%; 788 | } 789 | 790 | #three-col-results-table .short-col { 791 | width: 15%; 792 | } 793 | 794 | #three-col-results-table .short-col { 795 | width: 15%; 796 | } 797 | 798 | #three-col-results-table .no-col { 799 | padding-left:27px; 800 | } 801 | 802 | #results-table th:nth-child(5) { 803 | padding: 5px 0 5px 3px; 804 | } 805 | 806 | .basic-table a:link, .basic-table a:active, .basic-table a:visited { 807 | color: #005454; 808 | text-decoration: none; 809 | } 810 | 811 | .basic-table a:hover { 812 | color: #ffffff; 813 | background-color: #046380; 814 | } 815 | 816 | footer { 817 | width: 950px; 818 | margin: -30px auto 50px; 819 | padding: 0 5px 0 5px; 820 | } 821 | 822 | .footer-internal { 823 | margin: -30px 0 50px 225px; 824 | } 825 | 826 | .footer-content { 827 | background: url('/images/footerbar.png') top left no-repeat; 828 | height: 150px; 829 | width: 575px; 830 | color: #7b7d81; 831 | padding: 25px 40px 0 40px; 832 | } 833 | 834 | #footer-home { 835 | margin-left: 240px; 836 | } 837 | 838 | #footer-person { 839 | margin-left: 170px; 840 | } 841 | 842 | #subnav li { 843 | float:left; 844 | } 845 | 846 | #faqs { 847 | margin-left: 125px; 848 | } 849 | 850 | #feedback { 851 | margin-left: 75px; 852 | } 853 | 854 | #opensource { 855 | margin-left: 70px; 856 | } 857 | 858 | #subnav a { 859 | font-size: 14px; 860 | font-family: Georgia, "Times New Roman", Times, serif; 861 | font-weight: bold; 862 | font-style: italic; 863 | } 864 | 865 | #subnav a:link, #subnav a:visited, #subnav a:active { 866 | color: #006666; 867 | } 868 | 869 | #subnav a:hover, #about a:hover { 870 | color: #ffffff; 871 | } 872 | 873 | #join { 874 | line-height: 17px; 875 | text-align: center; 876 | margin-top: 10px; 877 | display: block; 878 | } 879 | 880 | #join strong { 881 | display: block; 882 | } 883 | 884 | #emailmediarow { 885 | margin-top: 13px; 886 | padding-left: 94px; 887 | } 888 | 889 | #mailinglistform li { 890 | margin-right: 13px; 891 | float: left; 892 | } 893 | 894 | #mailinglistform li label { 895 | display: none; 896 | } 897 | 898 | #mailinglistform input { 899 | width: 103px; 900 | color: #44726f; 901 | font-family: Georgia, "Times New Roman",Times,serif; 902 | font-style: italic; 903 | } 904 | 905 | #join-button { 906 | background: url('/images/join_button.png') center top no-repeat; 907 | width: 66px; 908 | height: 24px; 909 | border: none; 910 | text-indent: -9999em; 911 | display: block; 912 | cursor: pointer; 913 | } 914 | 915 | #facebook { 916 | background: url('/images/facebook_icon.png') left top no-repeat; 917 | width: 22px; 918 | height: 24px; 919 | text-indent: -9999em; 920 | margin-right: 13px; 921 | display: block; 922 | float: left; 923 | cursor: pointer; 924 | } 925 | 926 | #twitter { 927 | background: url('/images/twitter_icon.png') left top no-repeat; 928 | width: 24px; 929 | height: 24px; 930 | text-indent: -9999em; 931 | display: block; 932 | float: left; 933 | cursor: pointer; 934 | } 935 | 936 | #about { 937 | text-align: center; 938 | margin-top: 7px; 939 | display: block; 940 | } 941 | 942 | #about a { 943 | color: #7b7d81; 944 | } -------------------------------------------------------------------------------- /public/javascripts/jquery.qtip-1.0.0-rc3.min.js: -------------------------------------------------------------------------------- 1 | /* 2 | * jquery.qtip. The jQuery tooltip plugin 3 | * 4 | * Copyright (c) 2009 Craig Thompson 5 | * http://craigsworks.com 6 | * 7 | * Licensed under MIT 8 | * http://www.opensource.org/licenses/mit-license.php 9 | * 10 | * Launch : February 2009 11 | * Version : 1.0.0-rc3 12 | * Released: Tuesday 12th May, 2009 - 00:00 13 | * Debug: jquery.qtip.debug.js 14 | */ 15 | (function(f){f.fn.qtip=function(B,u){var y,t,A,s,x,w,v,z;if(typeof B=="string"){if(typeof f(this).data("qtip")!=="object"){f.fn.qtip.log.error.call(self,1,f.fn.qtip.constants.NO_TOOLTIP_PRESENT,false)}if(B=="api"){return f(this).data("qtip").interfaces[f(this).data("qtip").current]}else{if(B=="interfaces"){return f(this).data("qtip").interfaces}}}else{if(!B){B={}}if(typeof B.content!=="object"||(B.content.jquery&&B.content.length>0)){B.content={text:B.content}}if(typeof B.content.title!=="object"){B.content.title={text:B.content.title}}if(typeof B.position!=="object"){B.position={corner:B.position}}if(typeof B.position.corner!=="object"){B.position.corner={target:B.position.corner,tooltip:B.position.corner}}if(typeof B.show!=="object"){B.show={when:B.show}}if(typeof B.show.when!=="object"){B.show.when={event:B.show.when}}if(typeof B.show.effect!=="object"){B.show.effect={type:B.show.effect}}if(typeof B.hide!=="object"){B.hide={when:B.hide}}if(typeof B.hide.when!=="object"){B.hide.when={event:B.hide.when}}if(typeof B.hide.effect!=="object"){B.hide.effect={type:B.hide.effect}}if(typeof B.style!=="object"){B.style={name:B.style}}B.style=c(B.style);s=f.extend(true,{},f.fn.qtip.defaults,B);s.style=a.call({options:s},s.style);s.user=f.extend(true,{},B)}return f(this).each(function(){if(typeof B=="string"){w=B.toLowerCase();A=f(this).qtip("interfaces");if(typeof A=="object"){if(u===true&&w=="destroy"){while(A.length>0){A[A.length-1].destroy()}}else{if(u!==true){A=[f(this).qtip("api")]}for(y=0;y0))}if(typeof s.options.show.solo=="object"){z=f(s.options.show.solo)}else{if(s.options.show.solo===true){z=f("div.qtip").not(s.elements.tooltip)}}if(z){z.each(function(){if(f(this).qtip("api").status.rendered===true){f(this).qtip("api").hide()}})}if(typeof s.options.show.effect.type=="function"){s.options.show.effect.type.call(s.elements.tooltip,s.options.show.effect.length);s.elements.tooltip.queue(function(){w();f(this).dequeue()})}else{switch(s.options.show.effect.type.toLowerCase()){case"fade":s.elements.tooltip.fadeIn(s.options.show.effect.length,w);break;case"slide":s.elements.tooltip.slideDown(s.options.show.effect.length,function(){w();if(s.options.position.type!=="static"){s.updatePosition(y,true)}});break;case"grow":s.elements.tooltip.show(s.options.show.effect.length,w);break;default:s.elements.tooltip.show(null,w);break}s.elements.tooltip.addClass(s.options.style.classes.active)}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_SHOWN,"show")},hide:function(y){var x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"hide")}else{if(s.elements.tooltip.css("display")==="none"){return s}}clearTimeout(s.timers.show);s.elements.tooltip.stop(true,false);x=s.beforeHide.call(s,y);if(x===false){return s}function w(){s.onHide.call(s,y)}s.cache.toggle=0;if(typeof s.options.hide.effect.type=="function"){s.options.hide.effect.type.call(s.elements.tooltip,s.options.hide.effect.length);s.elements.tooltip.queue(function(){w();f(this).dequeue()})}else{switch(s.options.hide.effect.type.toLowerCase()){case"fade":s.elements.tooltip.fadeOut(s.options.hide.effect.length,w);break;case"slide":s.elements.tooltip.slideUp(s.options.hide.effect.length,w);break;case"grow":s.elements.tooltip.hide(s.options.hide.effect.length,w);break;default:s.elements.tooltip.hide(null,w);break}s.elements.tooltip.removeClass(s.options.style.classes.active)}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_HIDDEN,"hide")},updatePosition:function(w,x){var C,G,L,J,H,E,y,I,B,D,K,A,F,z;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updatePosition")}else{if(s.options.position.type=="static"){return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.CANNOT_POSITION_STATIC,"updatePosition")}}G={position:{left:0,top:0},dimensions:{height:0,width:0},corner:s.options.position.corner.target};L={position:s.getPosition(),dimensions:s.getDimensions(),corner:s.options.position.corner.tooltip};if(s.options.position.target!=="mouse"){if(s.options.position.target.get(0).nodeName.toLowerCase()=="area"){J=s.options.position.target.attr("coords").split(",");for(C=0;CG.dimensions.width){G.dimensions.width=J[C]}if(J[C]G.dimensions.height){G.dimensions.height=J[C]}if(J[C]0){if(L.corner.search(/Left/)!==-1){y.left-=s.options.style.border.radius}else{if(L.corner.search(/Right/)!==-1){y.left+=s.options.style.border.radius}}if(L.corner.search(/Top/)!==-1){y.top-=s.options.style.border.radius}else{if(L.corner.search(/Bottom/)!==-1){y.top+=s.options.style.border.radius}}}if(I){if(L.corner.search(/top/)!==-1){y.top-=I}else{if(L.corner.search(/bottom/)!==-1){y.top+=I}}if(L.corner.search(/left/)!==-1){y.left-=I}else{if(L.corner.search(/right/)!==-1){y.left+=I}}if(L.corner.search(/leftMiddle|rightMiddle/)!==-1){y.top-=1}}if(s.options.position.adjust.screen===true){y=o.call(s,y,G,L)}if(s.options.position.target==="mouse"&&s.options.position.adjust.mouse===true){if(s.options.position.adjust.screen===true&&s.elements.tip){K=s.elements.tip.attr("rel")}else{K=s.options.position.corner.tooltip}y.left+=(K.search(/right/i)!==-1)?-6:6;y.top+=(K.search(/bottom/i)!==-1)?-6:6}if(!s.elements.bgiframe&&f.browser.msie&&parseInt(f.browser.version.charAt(0))==6){f("select, object").each(function(){A=f(this).offset();A.bottom=A.top+f(this).height();A.right=A.left+f(this).width();if(y.top+L.dimensions.height>=A.top&&y.left+L.dimensions.width>=A.left){k.call(s)}})}y.left+=s.options.position.adjust.x;y.top+=s.options.position.adjust.y;F=s.getPosition();if(y.left!=F.left||y.top!=F.top){z=s.beforePositionUpdate.call(s,w);if(z===false){return s}s.cache.position=y;if(x===true){s.status.animated=true;s.elements.tooltip.animate(y,200,"swing",function(){s.status.animated=false})}else{s.elements.tooltip.css(y)}s.onPositionUpdate.call(s,w);if(typeof w!=="undefined"&&w.type&&w.type!=="mousemove"){f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_POSITION_UPDATED,"updatePosition")}}return s},updateWidth:function(w){var x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateWidth")}else{if(w&&typeof w!=="number"){return f.fn.qtip.log.error.call(s,2,"newWidth must be of type number","updateWidth")}}x=s.elements.contentWrapper.siblings().add(s.elements.tip).add(s.elements.button);if(!w){if(typeof s.options.style.width.value=="number"){w=s.options.style.width.value}else{s.elements.tooltip.css({width:"auto"});x.hide();if(f.browser.msie){s.elements.wrapper.add(s.elements.contentWrapper.children()).css({zoom:"normal"})}w=s.getDimensions().width+1;if(!s.options.style.width.value){if(w>s.options.style.width.max){w=s.options.style.width.max}if(w").get(0).getContext){z=s.elements.tooltip.find(".qtip-tip canvas:first");x=z.get(0).getContext("2d");x.clearRect(0,0,300,300);y=z.parent("div[rel]:first").attr("rel");B=b(y,s.options.style.tip.size.width,s.options.style.tip.size.height);h.call(s,z,B,s.options.style.tip.color||s.options.style.border.color)}else{if(f.browser.msie){z=s.elements.tooltip.find('.qtip-tip [nodeName="shape"]');z.attr("fillcolor",s.options.style.tip.color||s.options.style.border.color)}}}if(s.options.style.border.radius>0){s.elements.tooltip.find(".qtip-betweenCorners").css({backgroundColor:s.options.style.border.color});if(f("").get(0).getContext){A=g(s.options.style.border.radius);s.elements.tooltip.find(".qtip-wrapper canvas").each(function(){x=f(this).get(0).getContext("2d");x.clearRect(0,0,300,300);y=f(this).parent("div[rel]:first").attr("rel");r.call(s,f(this),A[y],s.options.style.border.radius,s.options.style.border.color)})}else{if(f.browser.msie){s.elements.tooltip.find('.qtip-wrapper [nodeName="arc"]').each(function(){f(this).attr("fillcolor",s.options.style.border.color)})}}}return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_STYLE_UPDATED,"updateStyle")},updateContent:function(A,y){var z,x,w;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateContent")}else{if(!A){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.NO_CONTENT_PROVIDED,"updateContent")}}z=s.beforeContentUpdate.call(s,A);if(typeof z=="string"){A=z}else{if(z===false){return}}if(f.browser.msie){s.elements.contentWrapper.children().css({zoom:"normal"})}if(A.jquery&&A.length>0){A.clone(true).appendTo(s.elements.content).show()}else{s.elements.content.html(A)}x=s.elements.content.find("img[complete=false]");if(x.length>0){w=0;x.each(function(C){f('').load(function(){if(++w==x.length){B()}})})}else{B()}function B(){s.updateWidth();if(y!==false){if(s.options.position.type!=="static"){s.updatePosition(s.elements.tooltip.is(":visible"),true)}if(s.options.style.tip.corner!==false){n.call(s)}}}s.onContentUpdate.call(s);return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_CONTENT_UPDATED,"loadContent")},loadContent:function(w,z,A){var y;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"loadContent")}y=s.beforeContentLoad.call(s);if(y===false){return s}if(A=="post"){f.post(w,z,x)}else{f.get(w,z,x)}function x(B){s.onContentLoad.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_CONTENT_LOADED,"loadContent");s.updateContent(B)}return s},updateTitle:function(w){if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"updateTitle")}else{if(!w){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.NO_CONTENT_PROVIDED,"updateTitle")}}returned=s.beforeTitleUpdate.call(s);if(returned===false){return s}if(s.elements.button){s.elements.button=s.elements.button.clone(true)}s.elements.title.html(w);if(s.elements.button){s.elements.title.prepend(s.elements.button)}s.onTitleUpdate.call(s);return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_TITLE_UPDATED,"updateTitle")},focus:function(A){var y,x,w,z;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"focus")}else{if(s.options.position.type=="static"){return f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.CANNOT_FOCUS_STATIC,"focus")}}y=parseInt(s.elements.tooltip.css("z-index"));x=6000+f("div.qtip[qtip]").length-1;if(!s.status.focused&&y!==x){z=s.beforeFocus.call(s,A);if(z===false){return s}f("div.qtip[qtip]").not(s.elements.tooltip).each(function(){if(f(this).qtip("api").status.rendered===true){w=parseInt(f(this).css("z-index"));if(typeof w=="number"&&w>-1){f(this).css({zIndex:parseInt(f(this).css("z-index"))-1})}f(this).qtip("api").status.focused=false}});s.elements.tooltip.css({zIndex:x});s.status.focused=true;s.onFocus.call(s,A);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_FOCUSED,"focus")}return s},disable:function(w){if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"disable")}if(w){if(!s.status.disabled){s.status.disabled=true;f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_DISABLED,"disable")}else{f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.TOOLTIP_ALREADY_DISABLED,"disable")}}else{if(s.status.disabled){s.status.disabled=false;f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_ENABLED,"disable")}else{f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.TOOLTIP_ALREADY_ENABLED,"disable")}}return s},destroy:function(){var w,x,y;x=s.beforeDestroy.call(s);if(x===false){return s}if(s.status.rendered){s.options.show.when.target.unbind("mousemove.qtip",s.updatePosition);s.options.show.when.target.unbind("mouseout.qtip",s.hide);s.options.show.when.target.unbind(s.options.show.when.event+".qtip");s.options.hide.when.target.unbind(s.options.hide.when.event+".qtip");s.elements.tooltip.unbind(s.options.hide.when.event+".qtip");s.elements.tooltip.unbind("mouseover.qtip",s.focus);s.elements.tooltip.remove()}else{s.options.show.when.target.unbind(s.options.show.when.event+".qtip-create")}if(typeof s.elements.target.data("qtip")=="object"){y=s.elements.target.data("qtip").interfaces;if(typeof y=="object"&&y.length>0){for(w=0;w0){s.elements.target.data("qtip").current=y.length-1}else{s.elements.target.removeData("qtip")}s.onDestroy.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_DESTROYED,"destroy");return s.elements.target},getPosition:function(){var w,x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"getPosition")}w=(s.elements.tooltip.css("display")!=="none")?false:true;if(w){s.elements.tooltip.css({visiblity:"hidden"}).show()}x=s.elements.tooltip.offset();if(w){s.elements.tooltip.css({visiblity:"visible"}).hide()}return x},getDimensions:function(){var w,x;if(!s.status.rendered){return f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.TOOLTIP_NOT_RENDERED,"getDimensions")}w=(!s.elements.tooltip.is(":visible"))?true:false;if(w){s.elements.tooltip.css({visiblity:"hidden"}).show()}x={height:s.elements.tooltip.outerHeight(),width:s.elements.tooltip.outerWidth()};if(w){s.elements.tooltip.css({visiblity:"visible"}).hide()}return x}})}function p(){var s,w,u,t,v,y,x;s=this;s.beforeRender.call(s);s.status.rendered=true;s.elements.tooltip='';s.elements.tooltip=f(s.elements.tooltip);s.elements.tooltip.appendTo(s.options.position.container);s.elements.tooltip.data("qtip",{current:0,interfaces:[s]});s.elements.wrapper=s.elements.tooltip.children("div:first");s.elements.contentWrapper=s.elements.wrapper.children("div:first").css({background:s.options.style.background});s.elements.content=s.elements.contentWrapper.children("div:first").css(q(s.options.style));if(f.browser.msie){s.elements.wrapper.add(s.elements.content).css({zoom:1})}if(s.options.hide.when.event=="unfocus"){s.elements.tooltip.attr("unfocus",true)}if(typeof s.options.style.width.value=="number"){s.updateWidth()}if(f("").get(0).getContext||f.browser.msie){if(s.options.style.border.radius>0){m.call(s)}else{s.elements.contentWrapper.css({border:s.options.style.border.width+"px solid "+s.options.style.border.color})}if(s.options.style.tip.corner!==false){e.call(s)}}else{s.elements.contentWrapper.css({border:s.options.style.border.width+"px solid "+s.options.style.border.color});s.options.style.border.radius=0;s.options.style.tip.corner=false;f.fn.qtip.log.error.call(s,2,f.fn.qtip.constants.CANVAS_VML_NOT_SUPPORTED,"render")}if((typeof s.options.content.text=="string"&&s.options.content.text.length>0)||(s.options.content.text.jquery&&s.options.content.text.length>0)){u=s.options.content.text}else{if(typeof s.elements.target.attr("title")=="string"&&s.elements.target.attr("title").length>0){u=s.elements.target.attr("title").replace("\\n","
");s.elements.target.attr("title","")}else{if(typeof s.elements.target.attr("alt")=="string"&&s.elements.target.attr("alt").length>0){u=s.elements.target.attr("alt").replace("\\n","
");s.elements.target.attr("alt","")}else{u=" ";f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.NO_VALID_CONTENT,"render")}}}if(s.options.content.title.text!==false){j.call(s)}s.updateContent(u);l.call(s);if(s.options.show.ready===true){s.show()}if(s.options.content.url!==false){t=s.options.content.url;v=s.options.content.data;y=s.options.content.method||"get";s.loadContent(t,v,y)}s.onRender.call(s);f.fn.qtip.log.error.call(s,1,f.fn.qtip.constants.EVENT_RENDERED,"render")}function m(){var F,z,t,B,x,E,u,G,D,y,w,C,A,s,v;F=this;F.elements.wrapper.find(".qtip-borderBottom, .qtip-borderTop").remove();t=F.options.style.border.width;B=F.options.style.border.radius;x=F.options.style.border.color||F.options.style.tip.color;E=g(B);u={};for(z in E){u[z]='
';if(f("").get(0).getContext){u[z]+=''}else{if(f.browser.msie){G=B*2+3;u[z]+=''}}u[z]+="
"}D=F.getDimensions().width-(Math.max(t,B)*2);y='
';w='
'+u.topLeft+u.topRight+y;F.elements.wrapper.prepend(w);C='
'+u.bottomLeft+u.bottomRight+y;F.elements.wrapper.append(C);if(f("").get(0).getContext){F.elements.wrapper.find("canvas").each(function(){A=E[f(this).parent("[rel]:first").attr("rel")];r.call(F,f(this),A,B,x)})}else{if(f.browser.msie){F.elements.tooltip.append('')}}s=Math.max(B,(B+(t-B)));v=Math.max(t-B,0);F.elements.contentWrapper.css({border:"0px solid "+x,borderWidth:v+"px "+s+"px"})}function r(u,w,s,t){var v=u.get(0).getContext("2d");v.fillStyle=t;v.beginPath();v.arc(w[0],w[1],s,0,Math.PI*2,false);v.fill()}function e(v){var t,s,x,u,w;t=this;if(t.elements.tip!==null){t.elements.tip.remove()}s=t.options.style.tip.color||t.options.style.border.color;if(t.options.style.tip.corner===false){return}else{if(!v){v=t.options.style.tip.corner}}x=b(v,t.options.style.tip.size.width,t.options.style.tip.size.height);t.elements.tip='
';if(f("").get(0).getContext){t.elements.tip+=''}else{if(f.browser.msie){u=t.options.style.tip.size.width+","+t.options.style.tip.size.height;w="m"+x[0][0]+","+x[0][1];w+=" l"+x[1][0]+","+x[1][1];w+=" "+x[2][0]+","+x[2][1];w+=" xe";t.elements.tip+='';t.elements.tip+='';t.elements.contentWrapper.css("position","relative")}}t.elements.tooltip.prepend(t.elements.tip+"
");t.elements.tip=t.elements.tooltip.find("."+t.options.style.classes.tip).eq(0);if(f("").get(0).getContext){h.call(t,t.elements.tip.find("canvas:first"),x,s)}if(v.search(/top/)!==-1&&f.browser.msie&&parseInt(f.browser.version.charAt(0))===6){t.elements.tip.css({marginTop:-4})}n.call(t,v)}function h(t,v,s){var u=t.get(0).getContext("2d");u.fillStyle=s;u.beginPath();u.moveTo(v[0][0],v[0][1]);u.lineTo(v[1][0],v[1][1]);u.lineTo(v[2][0],v[2][1]);u.fill()}function n(u){var t,w,s,x,v;t=this;if(t.options.style.tip.corner===false||!t.elements.tip){return}if(!u){u=t.elements.tip.attr("rel")}w=positionAdjust=(f.browser.msie)?1:0;t.elements.tip.css(u.match(/left|right|top|bottom/)[0],0);if(u.search(/top|bottom/)!==-1){if(f.browser.msie){if(parseInt(f.browser.version.charAt(0))===6){positionAdjust=(u.search(/top/)!==-1)?-3:1}else{positionAdjust=(u.search(/top/)!==-1)?1:2}}if(u.search(/Middle/)!==-1){t.elements.tip.css({left:"50%",marginLeft:-(t.options.style.tip.size.width/2)})}else{if(u.search(/Left/)!==-1){t.elements.tip.css({left:t.options.style.border.radius-w})}else{if(u.search(/Right/)!==-1){t.elements.tip.css({right:t.options.style.border.radius+w})}}}if(u.search(/top/)!==-1){t.elements.tip.css({top:-positionAdjust})}else{t.elements.tip.css({bottom:positionAdjust})}}else{if(u.search(/left|right/)!==-1){if(f.browser.msie){positionAdjust=(parseInt(f.browser.version.charAt(0))===6)?1:((u.search(/left/)!==-1)?1:2)}if(u.search(/Middle/)!==-1){t.elements.tip.css({top:"50%",marginTop:-(t.options.style.tip.size.height/2)})}else{if(u.search(/Top/)!==-1){t.elements.tip.css({top:t.options.style.border.radius-w})}else{if(u.search(/Bottom/)!==-1){t.elements.tip.css({bottom:t.options.style.border.radius+w})}}}if(u.search(/left/)!==-1){t.elements.tip.css({left:-positionAdjust})}else{t.elements.tip.css({right:positionAdjust})}}}s="padding-"+u.match(/left|right|top|bottom/)[0];x=t.options.style.tip.size[(s.search(/left|right/)!==-1)?"width":"height"];t.elements.tooltip.css("padding",0);t.elements.tooltip.css(s,x);if(f.browser.msie&&parseInt(f.browser.version.charAt(0))==6){v=parseInt(t.elements.tip.css("margin-top"))||0;v+=parseInt(t.elements.content.css("margin-top"))||0;t.elements.tip.css({marginTop:v})}}function j(){var s=this;if(s.elements.title!==null){s.elements.title.remove()}s.elements.title=f('
').css(q(s.options.style.title,true)).css({zoom:(f.browser.msie)?1:0}).prependTo(s.elements.contentWrapper);if(s.options.content.title.text){s.updateTitle.call(s,s.options.content.title.text)}if(s.options.content.title.button!==false&&typeof s.options.content.title.button=="string"){s.elements.button=f('').css(q(s.options.style.button,true)).html(s.options.content.title.button).prependTo(s.elements.title).click(function(t){if(!s.status.disabled){s.hide(t)}})}}function l(){var t,v,u,s;t=this;v=t.options.show.when.target;u=t.options.hide.when.target;if(t.options.hide.fixed){u=u.add(t.elements.tooltip)}if(t.options.hide.when.event=="inactive"){s=["click","dblclick","mousedown","mouseup","mousemove","mouseout","mouseenter","mouseleave","mouseover"];function y(z){if(t.status.disabled===true){return}clearTimeout(t.timers.inactive);t.timers.inactive=setTimeout(function(){f(s).each(function(){u.unbind(this+".qtip-inactive");t.elements.content.unbind(this+".qtip-inactive")});t.hide(z)},t.options.hide.delay)}}else{if(t.options.hide.fixed===true){t.elements.tooltip.bind("mouseover.qtip",function(){if(t.status.disabled===true){return}clearTimeout(t.timers.hide)})}}function x(z){if(t.status.disabled===true){return}if(t.options.hide.when.event=="inactive"){f(s).each(function(){u.bind(this+".qtip-inactive",y);t.elements.content.bind(this+".qtip-inactive",y)});y()}clearTimeout(t.timers.show);clearTimeout(t.timers.hide);t.timers.show=setTimeout(function(){t.show(z)},t.options.show.delay)}function w(z){if(t.status.disabled===true){return}if(t.options.hide.fixed===true&&t.options.hide.when.event.search(/mouse(out|leave)/i)!==-1&&f(z.relatedTarget).parents("div.qtip[qtip]").length>0){z.stopPropagation();z.preventDefault();clearTimeout(t.timers.hide);return false}clearTimeout(t.timers.show);clearTimeout(t.timers.hide);t.elements.tooltip.stop(true,true);t.timers.hide=setTimeout(function(){t.hide(z)},t.options.hide.delay)}if((t.options.show.when.target.add(t.options.hide.when.target).length===1&&t.options.show.when.event==t.options.hide.when.event&&t.options.hide.when.event!=="inactive")||t.options.hide.when.event=="unfocus"){t.cache.toggle=0;v.bind(t.options.show.when.event+".qtip",function(z){if(t.cache.toggle==0){x(z)}else{w(z)}})}else{v.bind(t.options.show.when.event+".qtip",x);if(t.options.hide.when.event!=="inactive"){u.bind(t.options.hide.when.event+".qtip",w)}}if(t.options.position.type.search(/(fixed|absolute)/)!==-1){t.elements.tooltip.bind("mouseover.qtip",t.focus)}if(t.options.position.target==="mouse"&&t.options.position.type!=="static"){v.bind("mousemove.qtip",function(z){t.cache.mouse={x:z.pageX,y:z.pageY};if(t.status.disabled===false&&t.options.position.adjust.mouse===true&&t.options.position.type!=="static"&&t.elements.tooltip.css("display")!=="none"){t.updatePosition(z)}})}}function o(u,v,A){var z,s,x,y,t,w;z=this;if(A.corner=="center"){return v.position}s=f.extend({},u);y={x:false,y:false};t={left:(s.left=f.fn.qtip.cache.screen.width+f.fn.qtip.cache.screen.scroll.left),top:(s.top=f.fn.qtip.cache.screen.height+f.fn.qtip.cache.screen.scroll.top)};x={left:(t.left&&(A.corner.search(/right/i)!=-1||(A.corner.search(/right/i)==-1&&!t.right))),right:(t.right&&(A.corner.search(/left/i)!=-1||(A.corner.search(/left/i)==-1&&!t.left))),top:(t.top&&A.corner.search(/top/i)==-1),bottom:(t.bottom&&A.corner.search(/bottom/i)==-1)};if(x.left){if(z.options.position.target!=="mouse"){s.left=v.position.left+v.dimensions.width}else{s.left=z.cache.mouse.x}y.x="Left"}else{if(x.right){if(z.options.position.target!=="mouse"){s.left=v.position.left-A.dimensions.width}else{s.left=z.cache.mouse.x-A.dimensions.width}y.x="Right"}}if(x.top){if(z.options.position.target!=="mouse"){s.top=v.position.top+v.dimensions.height}else{s.top=z.cache.mouse.y}y.y="top"}else{if(x.bottom){if(z.options.position.target!=="mouse"){s.top=v.position.top-A.dimensions.height}else{s.top=z.cache.mouse.y-A.dimensions.height}y.y="bottom"}}if(s.left<0){s.left=u.left;y.x=false}if(s.top<0){s.top=u.top;y.y=false}if(z.options.style.tip.corner!==false){s.corner=new String(A.corner);if(y.x!==false){s.corner=s.corner.replace(/Left|Right|Middle/,y.x)}if(y.y!==false){s.corner=s.corner.replace(/top|bottom/,y.y)}if(s.corner!==z.elements.tip.attr("rel")){e.call(z,s.corner)}}return s}function q(u,t){var v,s;v=f.extend(true,{},u);for(s in v){if(t===true&&s.search(/(tip|classes)/i)!==-1){delete v[s]}else{if(!t&&s.search(/(width|border|tip|title|classes|user)/i)!==-1){delete v[s]}}}return v}function c(s){if(typeof s.tip!=="object"){s.tip={corner:s.tip}}if(typeof s.tip.size!=="object"){s.tip.size={width:s.tip.size,height:s.tip.size}}if(typeof s.border!=="object"){s.border={width:s.border}}if(typeof s.width!=="object"){s.width={value:s.width}}if(typeof s.width.max=="string"){s.width.max=parseInt(s.width.max.replace(/([0-9]+)/i,"$1"))}if(typeof s.width.min=="string"){s.width.min=parseInt(s.width.min.replace(/([0-9]+)/i,"$1"))}if(typeof s.tip.size.x=="number"){s.tip.size.width=s.tip.size.x;delete s.tip.size.x}if(typeof s.tip.size.y=="number"){s.tip.size.height=s.tip.size.y;delete s.tip.size.y}return s}function a(){var s,t,u,x,v,w;s=this;u=[true,{}];for(t=0;t0){v.tip.size.width+=1}if(v.tip.size.height%2>0){v.tip.size.height+=1}if(v.tip.corner===true){v.tip.corner=(s.options.position.corner.tooltip==="center")?false:s.options.position.corner.tooltip}return v}function b(v,u,t){var s={bottomRight:[[0,0],[u,t],[u,0]],bottomLeft:[[0,0],[u,0],[0,t]],topRight:[[0,t],[u,0],[u,t]],topLeft:[[0,0],[0,t],[u,t]],topMiddle:[[0,t],[u/2,0],[u,t]],bottomMiddle:[[0,0],[u,0],[u/2,t]],rightMiddle:[[0,0],[u,t/2],[0,t]],leftMiddle:[[u,0],[u,t],[0,t/2]]};s.leftTop=s.bottomRight;s.rightTop=s.bottomLeft;s.leftBottom=s.topRight;s.rightBottom=s.topLeft;return s[v]}function g(s){var t;if(f("").get(0).getContext){t={topLeft:[s,s],topRight:[0,s],bottomLeft:[s,0],bottomRight:[0,0]}}else{if(f.browser.msie){t={topLeft:[-90,90,0],topRight:[-90,90,-s],bottomLeft:[90,270,0],bottomRight:[90,270,-s]}}}return t}function k(){var s,t,u;s=this;u=s.getDimensions();t='