├── log └── .gitkeep ├── lib └── tasks │ └── .gitkeep ├── public ├── favicon.ico ├── robots.txt ├── 422.html ├── 404.html └── 500.html ├── test ├── unit │ ├── .gitkeep │ ├── helpers │ │ └── pages_helper_test.rb │ └── page_test.rb ├── fixtures │ ├── .gitkeep │ └── pages.yml ├── functional │ ├── .gitkeep │ └── pages_controller_test.rb ├── integration │ └── .gitkeep ├── performance │ └── browsing_test.rb └── test_helper.rb ├── app ├── mailers │ └── .gitkeep ├── models │ ├── .gitkeep │ └── page.rb ├── helpers │ └── application_helper.rb ├── assets │ ├── javascripts │ │ ├── app │ │ │ ├── views │ │ │ │ └── pages │ │ │ │ │ ├── item.jst.eco │ │ │ │ │ └── list.jst.eco │ │ │ ├── index.coffee │ │ │ ├── models │ │ │ │ └── page.coffee │ │ │ ├── controllers │ │ │ │ └── pages.coffee │ │ │ └── lib │ │ │ │ └── jquery.waypoints.js │ │ └── application.js │ ├── images │ │ └── rails.png │ └── stylesheets │ │ ├── pages.css │ │ └── application.css ├── controllers │ ├── application_controller.rb │ └── pages_controller.rb └── views │ └── pages │ └── index.html.erb ├── vendor ├── plugins │ └── .gitkeep └── assets │ └── stylesheets │ └── .gitkeep ├── Procfile ├── .gitignore ├── config.ru ├── config ├── environment.rb ├── boot.rb ├── initializers │ ├── mime_types.rb │ ├── stitch.rb │ ├── inflections.rb │ ├── backtrace_silencers.rb │ ├── session_store.rb │ ├── secret_token.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── database.yml ├── environments │ ├── development.rb │ ├── test.rb │ └── production.rb ├── routes.rb └── application.rb ├── doc └── README_FOR_APP ├── db ├── migrate │ └── 20110616103504_create_pages.rb ├── seeds.rb └── schema.rb ├── Rakefile ├── script └── rails ├── Gemfile ├── README.md └── Gemfile.lock /log/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/unit/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/plugins/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/functional/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec rails server thin -p $PORT 2 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/views/pages/item.jst.eco: -------------------------------------------------------------------------------- 1 |
  • <%= @name %>
  • -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | db/*.sqlite3 3 | log/*.log 4 | tmp/ 5 | .sass-cache/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /app/models/page.rb: -------------------------------------------------------------------------------- 1 | class Page < ActiveRecord::Base 2 | validates_presence_of :name 3 | end -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | //= require jquery 2 | //= require jquery_ujs 3 | //= require app/index -------------------------------------------------------------------------------- /app/assets/images/rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/maccman/spine.infinite/HEAD/app/assets/images/rails.png -------------------------------------------------------------------------------- /test/unit/helpers/pages_helper_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PagesHelperTest < ActionView::TestCase 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery 3 | end 4 | -------------------------------------------------------------------------------- /test/unit/page_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PageTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/views/pages/list.jst.eco: -------------------------------------------------------------------------------- 1 |

    Pages

    2 | 3 | 4 | 5 |
    Loading...
    6 | 7 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run SpineRails3::Application 5 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the rails application 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the rails application 5 | SpineRails3::Application.initialize! 6 | -------------------------------------------------------------------------------- /test/functional/pages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class PagesControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | require 'rubygems' 2 | 3 | # Set up gems listed in the Gemfile. 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 5 | 6 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 7 | -------------------------------------------------------------------------------- /doc/README_FOR_APP: -------------------------------------------------------------------------------- 1 | Use this README file to introduce your application and point to useful places in the API for learning more. 2 | Run "rake doc:app" to generate API documentation for your models, controllers, helpers, and libraries. 3 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/wc/norobots.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-Agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/index.coffee: -------------------------------------------------------------------------------- 1 | #= require json2 2 | #= require jquery 3 | #= require spine 4 | #= require spine/ajax 5 | # 6 | #= require_tree ./lib 7 | #= require_tree ./models 8 | #= require_tree ./controllers 9 | #= require_tree ./views -------------------------------------------------------------------------------- /app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | respond_to :html, :json 3 | 4 | def index 5 | @pages = Page.where("id > ?", params[:index] || 0).limit(60) 6 | respond_with(@pages) 7 | end 8 | end -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Sample localization file for English. Add more files in this directory for other locales. 2 | # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. 3 | 4 | en: 5 | hello: "Hello world" 6 | -------------------------------------------------------------------------------- /test/fixtures/pages.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/Fixtures.html 2 | 3 | one: 4 | name: MyString 5 | slug: MyString 6 | body: MyText 7 | 8 | two: 9 | name: MyString 10 | slug: MyString 11 | body: MyText 12 | -------------------------------------------------------------------------------- /db/migrate/20110616103504_create_pages.rb: -------------------------------------------------------------------------------- 1 | class CreatePages < ActiveRecord::Migration 2 | def change 3 | create_table :pages do |t| 4 | t.string :name 5 | t.string :slug 6 | t.text :body 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env rake 2 | # Add your own tasks in files placed in lib/tasks ending in .rake, 3 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 4 | 5 | require File.expand_path('../config/application', __FILE__) 6 | 7 | SpineRails3::Application.load_tasks 8 | -------------------------------------------------------------------------------- /config/initializers/stitch.rb: -------------------------------------------------------------------------------- 1 | class StitchApplication 2 | def initialize 3 | @package = Stitch::Package.new(:paths => ["app/assets/javascripts/app", "app/assets/javascripts/lib"]) 4 | end 5 | 6 | def call(env) 7 | [200, {"Content-Type" => "text/javascript"}, [Uglifier.compile(@package.compile)]] 8 | end 9 | end -------------------------------------------------------------------------------- /script/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. 3 | 4 | APP_PATH = File.expand_path('../../config/application', __FILE__) 5 | require File.expand_path('../../config/boot', __FILE__) 6 | require 'rails/commands' 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '3.1.0' 4 | 5 | group :assets do 6 | gem 'coffee-rails' 7 | gem 'uglifier' 8 | gem 'sass' 9 | end 10 | 11 | gem 'jquery-rails' 12 | gem 'eco' 13 | gem 'spine-rails' 14 | gem 'thin' 15 | 16 | group :development do 17 | gem 'sqlite3' 18 | gem 'ruby-debug19', :require => 'ruby-debug' 19 | end 20 | 21 | group :production do 22 | gem 'pg' 23 | end -------------------------------------------------------------------------------- /app/assets/javascripts/app/models/page.coffee: -------------------------------------------------------------------------------- 1 | class Page extends Spine.Model 2 | @configure "Page", "name" 3 | @extend Spine.Model.Ajax.Methods 4 | 5 | @fetch (params) -> 6 | index = @last()?.id or 0 7 | return false if index is @index 8 | @index = index 9 | 10 | params or= 11 | data: {index: index} 12 | processData: true 13 | 14 | @ajax().fetch(params) 15 | 16 | window.Page = Page -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format 4 | # (all these examples are active by default): 5 | # ActiveSupport::Inflector.inflections do |inflect| 6 | # inflect.plural /^(ox)$/i, '\1en' 7 | # inflect.singular /^(ox)en/i, '\1' 8 | # inflect.irregular 'person', 'people' 9 | # inflect.uncountable %w( fish sheep ) 10 | # end 11 | -------------------------------------------------------------------------------- /test/performance/browsing_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | require 'rails/performance_test_help' 3 | 4 | class BrowsingTest < ActionDispatch::PerformanceTest 5 | # Refer to the documentation for all available options 6 | # self.profile_options = { :runs => 5, :metrics => [:wall_time, :memory] 7 | # :output => 'tmp/performance', :formats => [:flat] } 8 | 9 | def test_homepage 10 | get '/' 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/views/pages/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Infinite 5 | <%= stylesheet_link_tag "application" %> 6 | <%= javascript_include_tag "application" %> 7 | <%= csrf_meta_tags %> 8 | 9 | 14 | 15 | 16 |
    17 | 18 | 19 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | SpineRails3::Application.config.session_store :cookie_store, key: '_spine.rails3_session' 4 | 5 | # Use the database for sessions instead of the cookie-based default, 6 | # which shouldn't be used to store highly confidential information 7 | # (create the session table with "rails generate session_migration") 8 | # SpineRails3::Application.config.session_store :active_record_store 9 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order. 7 | # 8 | # Note: You'll currently still have to declare fixtures explicitly in integration tests 9 | # -- they do not yet inherit this setting 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | 9 | 1000.times do |i| 10 | page = Page.new 11 | page.name = "A Page" 12 | page.save! 13 | page.name = "A Page ##{page.id}" 14 | page.save! 15 | end -------------------------------------------------------------------------------- /config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | # Make sure the secret is at least 30 characters and all random, 6 | # no regular words or you'll be exposed to dictionary attacks. 7 | SpineRails3::Application.config.secret_token = '877d90109fe035a16ac02b3e7ff4969bb5d53b8f58ca5e1b7ad0ce630b225318f0bf647e6fd8681274df726e1fae55815b815194f8bc93d873357d37dae4ac77' 8 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains the settings for ActionController::ParametersWrapper 4 | # which will be enabled by default in the upcoming version of Ruby on Rails. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActionController::Base.wrap_parameters format: [:json] 8 | 9 | # Disable root element in JSON by default. 10 | if defined?(ActiveRecord) 11 | ActiveRecord::Base.include_root_in_json = false 12 | end 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##Introduction 2 | 3 | This is an example of infinite scrolling using [Spine](http://spinejs.com) and Rails. 4 | 5 | You can find a live demo [here](http://spine-infinite.herokuapp.com/). 6 | 7 | ##Source 8 | 9 | The files you need to inspect are: 10 | 11 | * `app/assets/javascripts/app/models/page.coffee` 12 | * `app/assets/javascripts/app/controllers/pages.coffee` 13 | * `app/controllers/pages_controller.rb` 14 | 15 | ##Usage 16 | 17 | bundle install 18 | rake db:setup 19 | rake db:seed 20 | 21 | rails server thin 22 | 23 | open http://localhost:3000 -------------------------------------------------------------------------------- /app/assets/stylesheets/pages.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | overflow: auto; 3 | } 4 | 5 | #pages { 6 | overflow: none; 7 | margin: 20px; 8 | 9 | padding: 20px; 10 | background: #FFF; 11 | -webkit-box-shadow: rgba(100, 100, 100, 0.3) 0 2px 6px; 12 | -moz-box-shadow: rgba(100, 100, 100, 0.3) 0 2px 6px; 13 | box-shadow: rgba(100, 100, 100, 0.3) 0 2px 6px; 14 | } 15 | 16 | #pages .loading { 17 | position: fixed; 18 | right: 20px; 19 | bottom: 10px; 20 | padding: 5px; 21 | background: #ffffcc; 22 | opacity: 0; 23 | -webkit-transition: opacity 0.5s linear; 24 | } 25 | 26 | #pages.loading .loading { 27 | opacity: 1; 28 | } 29 | 30 | #pages h1 { 31 | margin: 0 0 8px 0; 32 | } -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | 21 | production: 22 | adapter: sqlite3 23 | database: db/production.sqlite3 24 | pool: 5 25 | timeout: 5000 26 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The change you wanted was rejected.

    23 |

    Maybe you tried to change something you didn't have access to.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/controllers/pages.coffee: -------------------------------------------------------------------------------- 1 | $ = jQuery 2 | 3 | class Pages extends Spine.Controller 4 | className: 'list' 5 | 6 | elements: 7 | '.items': 'items' 8 | 'footer': 'footer' 9 | 10 | constructor: -> 11 | super 12 | 13 | @html JST['app/views/pages/list']() 14 | 15 | Page.bind 'fetch', => 16 | @el.addClass('loading') 17 | 18 | Page.bind 'refresh', => 19 | @el.removeClass('loading') 20 | @footer.waypoint(@scroll, offset: '80%') 21 | @render(arguments...) 22 | 23 | Page.fetch() 24 | 25 | render: (items = []) => 26 | for item in items 27 | @items.append JST['app/views/pages/item'](item) 28 | 29 | scroll: (e, direction) => 30 | @footer.waypoint('destroy') 31 | Page.fetch() 32 | 33 | window.Pages = Pages 34 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    The page you were looking for doesn't exist.

    23 |

    You may have mistyped the address or the page may have moved.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 17 | 18 | 19 | 20 | 21 |
    22 |

    We're sorry, but something went wrong.

    23 |

    We've been notified about this issue and we'll take a look at it shortly.

    24 |
    25 | 26 | 27 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended to check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(:version => 20110616103504) do 14 | 15 | create_table "pages", :force => true do |t| 16 | t.string "name" 17 | t.string "slug" 18 | t.text "body" 19 | t.datetime "created_at" 20 | t.datetime "updated_at" 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | SpineRails3::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Log error messages when you accidentally call methods on nil. 10 | config.whiny_nils = true 11 | 12 | # Show full error reports and disable caching 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger 20 | config.active_support.deprecation = :log 21 | 22 | # Only use best-standards-support built into browsers 23 | config.action_dispatch.best_standards_support = :builtin 24 | end 25 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | SpineRails3::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Configure static asset server for tests with Cache-Control for performance 11 | config.serve_static_assets = true 12 | config.static_cache_control = "public, max-age=3600" 13 | 14 | # Log error messages when you accidentally call methods on nil 15 | config.whiny_nils = true 16 | 17 | # Show full error reports and disable caching 18 | config.consider_all_requests_local = true 19 | config.action_controller.perform_caching = false 20 | 21 | # Raise exceptions instead of rendering exception templates 22 | config.action_dispatch.show_exceptions = false 23 | 24 | # Disable request forgery protection in test environment 25 | config.action_controller.allow_forgery_protection = false 26 | 27 | # Tell Action Mailer not to deliver emails to the real world. 28 | # The :test delivery method accumulates sent emails in the 29 | # ActionMailer::Base.deliveries array. 30 | config.action_mailer.delivery_method = :test 31 | 32 | # Use SQL instead of Active Record's schema dumper when creating the test database. 33 | # This is necessary if your schema can't be completely dumped by the schema dumper, 34 | # like if you have constraints or database-specific column types 35 | # config.active_record.schema_format = :sql 36 | 37 | # Print deprecation notices to the stderr 38 | config.active_support.deprecation = :stderr 39 | end 40 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | SpineRails3::Application.routes.draw do 2 | # The priority is based upon order of creation: 3 | # first created -> highest priority. 4 | 5 | # Sample of regular route: 6 | # match 'products/:id' => 'catalog#view' 7 | # Keep in mind you can assign values other than :controller and :action 8 | 9 | # Sample of named route: 10 | # match 'products/:id/purchase' => 'catalog#purchase', :as => :purchase 11 | # This route can be invoked with purchase_url(:id => product.id) 12 | 13 | # Sample resource route (maps HTTP verbs to controller actions automatically): 14 | # resources :products 15 | 16 | # Sample resource route with options: 17 | # resources :products do 18 | # member do 19 | # get 'short' 20 | # post 'toggle' 21 | # end 22 | # 23 | # collection do 24 | # get 'sold' 25 | # end 26 | # end 27 | 28 | # Sample resource route with sub-resources: 29 | # resources :products do 30 | # resources :comments, :sales 31 | # resource :seller 32 | # end 33 | 34 | # Sample resource route with more complex sub-resources 35 | # resources :products do 36 | # resources :comments 37 | # resources :sales do 38 | # get 'recent', :on => :collection 39 | # end 40 | # end 41 | 42 | # Sample resource route within a namespace: 43 | # namespace :admin do 44 | # # Directs /admin/products/* to Admin::ProductsController 45 | # # (app/controllers/admin/products_controller.rb) 46 | # resources :products 47 | # end 48 | 49 | # You can have the root of your site routed with "root" 50 | # just remember to delete public/index.html. 51 | # root :to => 'welcome#index' 52 | 53 | # See how all your routes lay out with "rake routes" 54 | 55 | resources :pages 56 | 57 | root :to => 'pages#index' 58 | 59 | # This is a legacy wild controller route that's not recommended for RESTful applications. 60 | # Note: This route will make all actions in every controller accessible via GET requests. 61 | # match ':controller(/:action(/:id(.:format)))' 62 | end 63 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | SpineRails3::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb 3 | 4 | # Code is not reloaded between requests 5 | config.cache_classes = true 6 | 7 | # Full error reports are disabled and caching is turned on 8 | config.consider_all_requests_local = false 9 | config.action_controller.perform_caching = true 10 | 11 | # Disable Rails's static asset server (Apache or nginx will already do this) 12 | config.serve_static_assets = false 13 | 14 | # Compress both stylesheets and JavaScripts 15 | config.assets.js_compressor = :uglifier 16 | config.assets.css_compressor = :scss 17 | 18 | # Specifies the header that your server uses for sending files 19 | # (comment out if your front-end server doesn't support this) 20 | config.action_dispatch.x_sendfile_header = "X-Sendfile" # Use 'X-Accel-Redirect' for nginx 21 | 22 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 23 | # config.force_ssl = true 24 | 25 | # See everything in the log (default is :info) 26 | # config.log_level = :debug 27 | 28 | # Use a different logger for distributed setups 29 | # config.logger = SyslogLogger.new 30 | 31 | # Use a different cache store in production 32 | # config.cache_store = :mem_cache_store 33 | 34 | # Enable serving of images, stylesheets, and JavaScripts from an asset server 35 | # config.action_controller.asset_host = "http://assets.example.com" 36 | 37 | # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) 38 | # config.assets.precompile += %w( search.js ) 39 | 40 | # Disable delivery errors, bad email addresses will be ignored 41 | # config.action_mailer.raise_delivery_errors = false 42 | 43 | # Enable threaded mode 44 | # config.threadsafe! 45 | 46 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 47 | # the I18n.default_locale when a translation can not be found) 48 | config.i18n.fallbacks = true 49 | 50 | # Send deprecation notices to registered listeners 51 | config.active_support.deprecation = :notify 52 | end 53 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # If you have a Gemfile, require the gems listed there, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(:default, Rails.env) if defined?(Bundler) 8 | 9 | module SpineRails3 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Custom directories with classes and modules you want to be autoloadable. 16 | # config.autoload_paths += %W(#{config.root}/extras) 17 | 18 | # Only load the plugins named here, in the order given (default is alphabetical). 19 | # :all can be used as a placeholder for all plugins not explicitly named. 20 | # config.plugins = [ :exception_notification, :ssl_requirement, :all ] 21 | 22 | # Activate observers that should always be running. 23 | # config.active_record.observers = :my_observer 24 | 25 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 26 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 27 | # config.time_zone = 'Central Time (US & Canada)' 28 | 29 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 30 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 31 | # config.i18n.default_locale = :de 32 | 33 | # Please note that JavaScript expansions are *ignored altogether* if the asset 34 | # pipeline is enabled (see config.assets.enabled below). Put your defaults in 35 | # app/assets/javascripts/application.js in that case. 36 | # 37 | # JavaScript files you want as :defaults (application.js is always included). 38 | # config.action_view.javascript_expansions[:defaults] = %w(prototype prototype_ujs) 39 | 40 | # Configure the default encoding used in templates for Ruby 1.9. 41 | config.encoding = "utf-8" 42 | 43 | # Configure sensitive parameters which will be filtered from the log file. 44 | config.filter_parameters += [:password] 45 | 46 | # Enable the asset pipeline 47 | config.assets.enabled = true 48 | end 49 | end -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll automatically include all the stylesheets available in this directory 3 | * and any sub-directories. You're free to add application-wide styles to this file and they'll appear at 4 | * the top of the compiled file, but it's generally better to create a new file per style scope. 5 | *= require_self 6 | *= require_tree . 7 | */ 8 | 9 | body { 10 | font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, FreeSerif, serif; 11 | color: #252519; 12 | font-size: 15px; 13 | margin: 0; 14 | -webkit-font-smoothing: antialiased; 15 | background: #D0D0D0; 16 | } 17 | 18 | a, a:visited { 19 | color: #261a3b; 20 | text-decoration: underline; 21 | cursor: pointer; 22 | } 23 | 24 | hr { 25 | border: 0; 26 | height: 1px; 27 | background-color: #E2E2E2; 28 | margin: 1em 1em 1.5em; 29 | } 30 | 31 | abbr { 32 | cursor: help; 33 | border-bottom: 1px dashed #000; 34 | } 35 | 36 | p { 37 | margin: 15px 0; 38 | line-height: 1.7em; 39 | } 40 | 41 | h1, h2, h3, h4, h5, h6 { 42 | margin: 15px 0 15px 0; 43 | } 44 | 45 | h2 { 46 | font-size: 1.2em; 47 | } 48 | 49 | h1 { 50 | padding: 10px 0 8px 0; 51 | margin-top: 30px; 52 | border-bottom: 1px solid #e5e5ee; 53 | } 54 | 55 | h1 a { 56 | text-decoration: none; 57 | } 58 | 59 | pre, code { 60 | font-size: 12px; line-height: 18px; 61 | font-family: Monaco, Consolas, "Lucida Console", monospace; 62 | margin: 0; padding: 0; 63 | } 64 | 65 | pre { 66 | overflow: auto; 67 | border: 1px solid #e5e5ee; 68 | margin: 0 0 15px 0; 69 | background: ghostWhite; 70 | padding: 5px; 71 | } 72 | 73 | ul { 74 | padding: 0; 75 | margin: 10px 0; 76 | list-style-position: inside; 77 | } 78 | 79 | ul li { 80 | line-height: 1.3em; 81 | } 82 | 83 | label, label span { 84 | display: block; 85 | } 86 | 87 | label { 88 | margin: 10px 0; 89 | } 90 | 91 | label span { 92 | margin: 0 0 5px 0; 93 | } 94 | 95 | input[type=text], textarea { 96 | padding: 3px; 97 | margin: 0; 98 | border: 1px solid rgba(0, 0, 0, 0.25); 99 | 100 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); 101 | -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); 102 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2); 103 | } 104 | 105 | input[type=text]:focus, textarea:focus, select:focus { 106 | outline: none; 107 | border-color: rgba(104, 189, 244, 0.8); 108 | 109 | -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(104, 189, 244, 0.6); 110 | -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(104, 189, 244, 0.6); 111 | box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.2), 0 1px 5px 0 rgba(104, 189, 244, 0.6); 112 | } 113 | 114 | textarea { 115 | padding: 5px; 116 | height: 80px; 117 | } 118 | 119 | input[type=text], textarea { 120 | min-width: 200px; 121 | } -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | actionmailer (3.1.0) 5 | actionpack (= 3.1.0) 6 | mail (~> 2.3.0) 7 | actionpack (3.1.0) 8 | activemodel (= 3.1.0) 9 | activesupport (= 3.1.0) 10 | builder (~> 3.0.0) 11 | erubis (~> 2.7.0) 12 | i18n (~> 0.6) 13 | rack (~> 1.3.2) 14 | rack-cache (~> 1.0.3) 15 | rack-mount (~> 0.8.2) 16 | rack-test (~> 0.6.1) 17 | sprockets (~> 2.0.0) 18 | activemodel (3.1.0) 19 | activesupport (= 3.1.0) 20 | bcrypt-ruby (~> 3.0.0) 21 | builder (~> 3.0.0) 22 | i18n (~> 0.6) 23 | activerecord (3.1.0) 24 | activemodel (= 3.1.0) 25 | activesupport (= 3.1.0) 26 | arel (~> 2.2.1) 27 | tzinfo (~> 0.3.29) 28 | activeresource (3.1.0) 29 | activemodel (= 3.1.0) 30 | activesupport (= 3.1.0) 31 | activesupport (3.1.0) 32 | multi_json (~> 1.0) 33 | archive-tar-minitar (0.5.2) 34 | arel (2.2.1) 35 | bcrypt-ruby (3.0.1) 36 | builder (3.0.0) 37 | coffee-rails (3.1.1) 38 | coffee-script (>= 2.2.0) 39 | railties (~> 3.1.0) 40 | coffee-script (2.2.0) 41 | coffee-script-source 42 | execjs 43 | coffee-script-source (1.1.2) 44 | columnize (0.3.4) 45 | daemons (1.1.4) 46 | eco (1.0.0) 47 | coffee-script 48 | eco-source 49 | execjs 50 | eco-source (1.1.0.rc.1) 51 | erubis (2.7.0) 52 | eventmachine (0.12.10) 53 | execjs (1.2.9) 54 | multi_json (~> 1.0) 55 | hike (1.2.1) 56 | i18n (0.6.0) 57 | jquery-rails (1.0.16) 58 | railties (~> 3.0) 59 | thor (~> 0.14) 60 | json (1.6.1) 61 | linecache19 (0.5.12) 62 | ruby_core_source (>= 0.1.4) 63 | mail (2.3.0) 64 | i18n (>= 0.4.0) 65 | mime-types (~> 1.16) 66 | treetop (~> 1.4.8) 67 | mime-types (1.16) 68 | multi_json (1.0.3) 69 | pg (0.11.0) 70 | polyglot (0.3.2) 71 | rack (1.3.4) 72 | rack-cache (1.0.3) 73 | rack (>= 0.4) 74 | rack-mount (0.8.3) 75 | rack (>= 1.0.0) 76 | rack-ssl (1.3.2) 77 | rack 78 | rack-test (0.6.1) 79 | rack (>= 1.0) 80 | rails (3.1.0) 81 | actionmailer (= 3.1.0) 82 | actionpack (= 3.1.0) 83 | activerecord (= 3.1.0) 84 | activeresource (= 3.1.0) 85 | activesupport (= 3.1.0) 86 | bundler (~> 1.0) 87 | railties (= 3.1.0) 88 | railties (3.1.0) 89 | actionpack (= 3.1.0) 90 | activesupport (= 3.1.0) 91 | rack-ssl (~> 1.3.2) 92 | rake (>= 0.8.7) 93 | rdoc (~> 3.4) 94 | thor (~> 0.14.6) 95 | rake (0.9.2) 96 | rdoc (3.10) 97 | json (~> 1.4) 98 | ruby-debug-base19 (0.11.25) 99 | columnize (>= 0.3.1) 100 | linecache19 (>= 0.5.11) 101 | ruby_core_source (>= 0.1.4) 102 | ruby-debug19 (0.11.6) 103 | columnize (>= 0.3.1) 104 | linecache19 (>= 0.5.11) 105 | ruby-debug-base19 (>= 0.11.19) 106 | ruby_core_source (0.1.5) 107 | archive-tar-minitar (>= 0.5.2) 108 | sass (3.1.10) 109 | spine-rails (0.0.4) 110 | actionpack (~> 3.1.0) 111 | sprockets (2.0.3) 112 | hike (~> 1.2) 113 | rack (~> 1.0) 114 | tilt (~> 1.1, != 1.3.0) 115 | sqlite3 (1.3.4) 116 | thin (1.2.11) 117 | daemons (>= 1.0.9) 118 | eventmachine (>= 0.12.6) 119 | rack (>= 1.0.0) 120 | thor (0.14.6) 121 | tilt (1.3.3) 122 | treetop (1.4.10) 123 | polyglot 124 | polyglot (>= 0.3.1) 125 | tzinfo (0.3.30) 126 | uglifier (1.0.3) 127 | execjs (>= 0.3.0) 128 | multi_json (>= 1.0.2) 129 | 130 | PLATFORMS 131 | ruby 132 | 133 | DEPENDENCIES 134 | coffee-rails 135 | eco 136 | jquery-rails 137 | pg 138 | rails (= 3.1.0) 139 | ruby-debug19 140 | sass 141 | spine-rails 142 | sqlite3 143 | thin 144 | uglifier 145 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/lib/jquery.waypoints.js: -------------------------------------------------------------------------------- 1 | /*! 2 | jQuery Waypoints - v1.1.2 3 | Copyright (c) 2011 Caleb Troughton 4 | Dual licensed under the MIT license and GPL license. 5 | https://github.com/imakewebthings/jquery-waypoints/blob/master/MIT-license.txt 6 | https://github.com/imakewebthings/jquery-waypoints/blob/master/GPL-license.txt 7 | */ 8 | 9 | /* 10 | Waypoints is a small jQuery plugin that makes it easy to execute a function 11 | whenever you scroll to an element. 12 | 13 | GitHub Repository: https://github.com/imakewebthings/jquery-waypoints 14 | Documentation and Examples: http://imakewebthings.github.com/jquery-waypoints 15 | 16 | Changelog: 17 | v1.1.2 18 | - Fixed error thrown by waypoints with triggerOnce option that were 19 | triggered via resize refresh. 20 | v1.1.1 21 | - Fixed bug in initialization where all offsets were being calculated 22 | as if set to 0 initially, causing unwarranted triggers during the 23 | subsequent refresh. 24 | - Added onlyOnScroll, an option for individual waypoints that disables 25 | triggers due to an offset refresh that crosses the current scroll 26 | point. (All credit to @knuton on this one.) 27 | v1.1 28 | - Moved the continuous option out of global settings and into the options 29 | object for individual waypoints. 30 | - Added the context option, which allows for using waypoints within any 31 | scrollable element, not just the window. 32 | v1.0.2 33 | - Moved scroll and resize handler bindings out of load. Should play nicer 34 | with async loaders like Head JS and LABjs. 35 | - Fixed a 1px off error when using certain % offsets. 36 | - Added unit tests. 37 | v1.0.1 38 | - Added $.waypoints('viewportHeight'). 39 | - Fixed iOS bug (using the new viewportHeight method). 40 | - Added offset function alias: 'bottom-in-view'. 41 | v1.0 42 | - Initial release. 43 | 44 | Support: 45 | - jQuery versions 1.4.3+ 46 | - IE6+, FF3+, Chrome 6+, Safari 4+, Opera 11 47 | - Other versions and browsers may work, these are just the ones I've looked at. 48 | */ 49 | 50 | (function($, wp, wps, window, undefined){ 51 | '$:nomunge'; 52 | 53 | var $w = $(window), 54 | 55 | // Keeping common strings as variables = better minification 56 | eventName = 'waypoint.reached', 57 | 58 | /* 59 | For the waypoint and direction passed in, trigger the waypoint.reached 60 | event and deal with the triggerOnce option. 61 | */ 62 | triggerWaypoint = function(way, dir) { 63 | way.element.trigger(eventName, dir); 64 | if (way.options.triggerOnce) { 65 | way.element[wp]('destroy'); 66 | } 67 | }, 68 | 69 | /* 70 | Given a jQuery element and Context, returns the index of that element in the waypoints 71 | array. Returns the index, or -1 if the element is not a waypoint. 72 | */ 73 | waypointIndex = function(el, context) { 74 | var i = context.waypoints.length - 1; 75 | while (i >= 0 && context.waypoints[i].element[0] !== el[0]) { 76 | i -= 1; 77 | } 78 | return i; 79 | }, 80 | 81 | // Private list of all elements used as scrolling contexts for waypoints. 82 | contexts = [], 83 | 84 | /* 85 | Context Class - represents a scrolling context. Properties include: 86 | element: jQuery object containing a single HTML element. 87 | waypoints: Array of waypoints operating under this scroll context. 88 | oldScroll: Keeps the previous scroll position to determine scroll direction. 89 | didScroll: Flag used in scrolling the context's scroll event. 90 | didResize: Flag used in scrolling the context's resize event. 91 | doScroll: Function that checks for crossed waypoints. Called from throttler. 92 | */ 93 | Context = function(context) { 94 | $.extend(this, { 95 | 'element': $(context), 96 | 97 | /* 98 | Starting at a ridiculous negative number allows for a 'down' trigger of 0 or 99 | negative offset waypoints on load. Useful for setting initial states. 100 | */ 101 | 'oldScroll': -99999, 102 | 103 | /* 104 | List of all elements that have been registered as waypoints. 105 | Each object in the array contains: 106 | element: jQuery object containing a single HTML element. 107 | offset: The window scroll offset, in px, that triggers the waypoint event. 108 | options: Options object that was passed to the waypoint fn function. 109 | */ 110 | 'waypoints': [], 111 | 112 | didScroll: false, 113 | didResize: false, 114 | 115 | doScroll: $.proxy(function() { 116 | var newScroll = this.element.scrollTop(), 117 | 118 | // Are we scrolling up or down? Used for direction argument in callback. 119 | isDown = newScroll > this.oldScroll, 120 | that = this, 121 | 122 | // Get a list of all waypoints that were crossed since last scroll move. 123 | pointsHit = $.grep(this.waypoints, function(el, i) { 124 | return isDown ? 125 | (el.offset > that.oldScroll && el.offset <= newScroll) : 126 | (el.offset <= that.oldScroll && el.offset > newScroll); 127 | }), 128 | len = pointsHit.length; 129 | 130 | // iOS adjustment 131 | if (!this.oldScroll || !newScroll) { 132 | $[wps]('refresh'); 133 | } 134 | 135 | // Done with scroll comparisons, store new scroll before ejection 136 | this.oldScroll = newScroll; 137 | 138 | // No waypoints crossed? Eject. 139 | if (!len) return; 140 | 141 | // If several waypoints triggered, need to do so in reverse order going up 142 | if (!isDown) pointsHit.reverse(); 143 | 144 | /* 145 | One scroll move may cross several waypoints. If the waypoint's continuous 146 | option is true it should fire even if it isn't the last waypoint. If false, 147 | it will only fire if it's the last one. 148 | */ 149 | $.each(pointsHit, function(i, point) { 150 | if (point.options.continuous || i === len - 1) { 151 | triggerWaypoint(point, [isDown ? 'down' : 'up']); 152 | } 153 | }); 154 | }, this) 155 | }); 156 | 157 | // Setup scroll and resize handlers. Throttled at the settings-defined rate limits. 158 | $(context).scroll($.proxy(function() { 159 | if (!this.didScroll) { 160 | this.didScroll = true; 161 | window.setTimeout($.proxy(function() { 162 | this.doScroll(); 163 | this.didScroll = false; 164 | }, this), $[wps].settings.scrollThrottle); 165 | } 166 | }, this)).resize($.proxy(function() { 167 | if (!this.didResize) { 168 | this.didResize = true; 169 | window.setTimeout($.proxy(function() { 170 | $[wps]('refresh'); 171 | this.didResize = false; 172 | }, this), $[wps].settings.resizeThrottle); 173 | } 174 | }, this)); 175 | 176 | $w.load($.proxy(function() { 177 | /* 178 | Fire a scroll check, should the page be loaded at a non-zero scroll value, 179 | as with a fragment id link or a page refresh. 180 | */ 181 | this.doScroll(); 182 | }, this)); 183 | }, 184 | 185 | /* Returns a Context object from the contexts array, given the raw HTML element 186 | for that context. */ 187 | getContextByElement = function(element) { 188 | var found = null; 189 | 190 | $.each(contexts, function(i, c) { 191 | if (c.element[0] === element) { 192 | found = c; 193 | return false; 194 | } 195 | }); 196 | 197 | return found; 198 | }, 199 | 200 | // Methods exposed to the effin' object 201 | methods = { 202 | /* 203 | jQuery.fn.waypoint([handler], [options]) 204 | 205 | handler 206 | function, optional 207 | A callback function called when the user scrolls past the element. 208 | The function signature is function(event, direction) where event is 209 | a standard jQuery Event Object and direction is a string, either 'down' 210 | or 'up' indicating which direction the user is scrolling. 211 | 212 | options 213 | object, optional 214 | A map of options to apply to this set of waypoints, including where on 215 | the browser window the waypoint is triggered. For a full list of 216 | options and their defaults, see $.fn.waypoint.defaults. 217 | 218 | This is how you register an element as a waypoint. When the user scrolls past 219 | that element it triggers waypoint.reached, a custom event. Since the 220 | parameters for creating a waypoint are optional, we have a few different 221 | possible signatures. Let’s look at each of them. 222 | 223 | someElements.waypoint(); 224 | 225 | Calling .waypoint with no parameters will register the elements as waypoints 226 | using the default options. The elements will fire the waypoint.reached event, 227 | but calling it in this way does not bind any handler to the event. You can 228 | bind to the event yourself, as with any other event, like so: 229 | 230 | someElements.bind('waypoint.reached', function(event, direction) { 231 | // make it rain 232 | }); 233 | 234 | You will usually want to create a waypoint and immediately bind a function to 235 | waypoint.reached, and can do so by passing a handler as the first argument to 236 | .waypoint: 237 | 238 | someElements.waypoint(function(event, direction) { 239 | if (direction === 'down') { 240 | // do this on the way down 241 | } 242 | else { 243 | // do this on the way back up through the waypoint 244 | } 245 | }); 246 | 247 | This will still use the default options, which will trigger the waypoint when 248 | the top of the element hits the top of the window. We can pass .waypoint an 249 | options object to customize things: 250 | 251 | someElements.waypoint(function(event, direction) { 252 | // do something amazing 253 | }, { 254 | offset: '50%' // middle of the page 255 | }); 256 | 257 | You can also pass just an options object. 258 | 259 | someElements.waypoint({ 260 | offset: 100 // 100px from the top 261 | }); 262 | 263 | This behaves like .waypoint(), in that it registers the elements as waypoints 264 | but binds no event handlers. 265 | 266 | Calling .waypoint on an existing waypoint will extend the previous options. 267 | If the call includes a handler, it will be bound to waypoint.reached without 268 | unbinding any other handlers. 269 | */ 270 | init: function(f, options) { 271 | // Register each element as a waypoint, add to array. 272 | this.each(function() { 273 | var cElement = $.fn[wp].defaults.context, 274 | context, 275 | $this = $(this); 276 | 277 | // Default window context or a specific element? 278 | if (options && options.context) { 279 | cElement = options.context; 280 | } 281 | 282 | // Find the closest element that matches the context 283 | if (!$.isWindow(cElement)) { 284 | cElement = $this.closest(cElement)[0]; 285 | } 286 | context = getContextByElement(cElement); 287 | 288 | // Not a context yet? Create and push. 289 | if (!context) { 290 | context = new Context(cElement); 291 | contexts.push(context); 292 | } 293 | 294 | // Extend default and preexisting options 295 | var ndx = waypointIndex($this, context), 296 | base = ndx < 0 ? $.fn[wp].defaults : context.waypoints[ndx].options, 297 | opts = $.extend({}, base, options); 298 | 299 | // Offset aliases 300 | opts.offset = opts.offset === "bottom-in-view" ? 301 | function() { 302 | var cHeight = $.isWindow(cElement) ? $[wps]('viewportHeight') 303 | : $(cElement).height(); 304 | return cHeight - $(this).outerHeight(); 305 | } : opts.offset; 306 | 307 | // Update, or create new waypoint 308 | if (ndx < 0) { 309 | context.waypoints.push({ 310 | 'element': $this, 311 | 'offset': null, 312 | 'options': opts 313 | }); 314 | } 315 | else { 316 | context.waypoints[ndx].options = opts; 317 | } 318 | 319 | // Bind the function if it was passed in. 320 | if (f) { 321 | $this.bind(eventName, f); 322 | } 323 | }); 324 | 325 | // Need to re-sort+refresh the waypoints array after new elements are added. 326 | $[wps]('refresh'); 327 | 328 | return this; 329 | }, 330 | 331 | 332 | /* 333 | jQuery.fn.waypoint('remove') 334 | 335 | Passing the string 'remove' to .waypoint unregisters the elements as waypoints 336 | and wipes any custom options, but leaves the waypoint.reached events bound. 337 | Calling .waypoint again in the future would reregister the waypoint and the old 338 | handlers would continue to work. 339 | */ 340 | remove: function() { 341 | return this.each(function(i, el) { 342 | var $el = $(el); 343 | 344 | $.each(contexts, function(i, c) { 345 | var ndx = waypointIndex($el, c); 346 | 347 | if (ndx >= 0) { 348 | c.waypoints.splice(ndx, 1); 349 | } 350 | }); 351 | }); 352 | }, 353 | 354 | /* 355 | jQuery.fn.waypoint('destroy') 356 | 357 | Passing the string 'destroy' to .waypoint will unbind all waypoint.reached 358 | event handlers on those elements and unregisters them as waypoints. 359 | */ 360 | destroy: function() { 361 | return this.unbind(eventName)[wp]('remove'); 362 | } 363 | }, 364 | 365 | /* 366 | Methods used by the jQuery object extension. 367 | */ 368 | jQMethods = { 369 | 370 | /* 371 | jQuery.waypoints('refresh') 372 | 373 | This will force a recalculation of each waypoint’s trigger point based on 374 | its offset option and context. This is called automatically whenever the window 375 | (or other defined context) is resized, new waypoints are added, or a waypoint’s 376 | options are modified. If your project is changing the DOM or page layout without 377 | doing one of these things, you may want to manually call this refresh. 378 | */ 379 | refresh: function() { 380 | $.each(contexts, function(i, c) { 381 | var isWin = $.isWindow(c.element[0]), 382 | contextOffset = isWin ? 0 : c.element.offset().top, 383 | contextHeight = isWin ? $[wps]('viewportHeight') : c.element.height(), 384 | contextScroll = isWin ? 0 : c.element.scrollTop(); 385 | 386 | $.each(c.waypoints, function(j, o) { 387 | /* $.each isn't safe from element removal due to triggerOnce. 388 | Should rewrite the loop but this is way easier. */ 389 | if (!o) return; 390 | 391 | // Adjustment is just the offset if it's a px value 392 | var adjustment = o.options.offset, 393 | oldOffset = o.offset; 394 | 395 | // Set adjustment to the return value if offset is a function. 396 | if (typeof o.options.offset === "function") { 397 | adjustment = o.options.offset.apply(o.element); 398 | } 399 | // Calculate the adjustment if offset is a percentage. 400 | else if (typeof o.options.offset === "string") { 401 | var amount = parseFloat(o.options.offset); 402 | adjustment = o.options.offset.indexOf("%") ? 403 | Math.ceil(contextHeight * (amount / 100)) : amount; 404 | } 405 | 406 | /* 407 | Set the element offset to the window scroll offset, less 408 | all our adjustments. 409 | */ 410 | o.offset = o.element.offset().top - contextOffset 411 | + contextScroll - adjustment; 412 | 413 | /* 414 | An element offset change across the current scroll point triggers 415 | the event, just as if we scrolled past it unless prevented by an 416 | optional flag. 417 | */ 418 | if (o.options.onlyOnScroll) return; 419 | 420 | if (oldOffset !== null && c.oldScroll > oldOffset && c.oldScroll <= o.offset) { 421 | triggerWaypoint(o, ['up']); 422 | } 423 | else if (oldOffset !== null && c.oldScroll < oldOffset && c.oldScroll >= o.offset) { 424 | triggerWaypoint(o, ['down']); 425 | } 426 | }); 427 | 428 | // Keep waypoints sorted by offset value. 429 | c.waypoints.sort(function(a, b) { 430 | return a.offset - b.offset; 431 | }); 432 | }); 433 | }, 434 | 435 | 436 | /* 437 | jQuery.waypoints('viewportHeight') 438 | 439 | This will return the height of the viewport, adjusting for inconsistencies 440 | that come with calling $(window).height() in iOS. Recommended for use 441 | within any offset functions. 442 | */ 443 | viewportHeight: function() { 444 | return (window.innerHeight ? window.innerHeight : $w.height()); 445 | }, 446 | 447 | 448 | /* 449 | jQuery.waypoints() 450 | 451 | This will return a jQuery object with a collection of all registered waypoint 452 | elements. 453 | 454 | $('.post').waypoint(); 455 | $('.ad-unit').waypoint(function(event, direction) { 456 | // Passed an ad unit 457 | }); 458 | console.log($.waypoints()); 459 | 460 | The example above would log a jQuery object containing all .post and .ad-unit 461 | elements. 462 | */ 463 | aggregate: function() { 464 | var points = $(); 465 | $.each(contexts, function(i, c) { 466 | $.each(c.waypoints, function(i, e) { 467 | points = points.add(e.element); 468 | }); 469 | }); 470 | return points; 471 | } 472 | }; 473 | 474 | 475 | /* 476 | fn extension. Delegates to appropriate method. 477 | */ 478 | $.fn[wp] = function(method) { 479 | 480 | if (methods[method]) { 481 | return methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); 482 | } 483 | else if (typeof method === "function" || !method) { 484 | return methods.init.apply(this, arguments); 485 | } 486 | else if (typeof method === "object") { 487 | return methods.init.apply(this, [null, method]); 488 | } 489 | else { 490 | $.error( 'Method ' + method + ' does not exist on jQuery ' + wp ); 491 | } 492 | }; 493 | 494 | 495 | /* 496 | The default options object that is extended when calling .waypoint. It has the 497 | following properties: 498 | 499 | context 500 | string | element | jQuery* 501 | default: window 502 | The context defines which scrollable element the waypoint belongs to and acts 503 | within. The default, window, means the waypoint offset is calculated with relation 504 | to the whole viewport. You can set this to another element to use the waypoints 505 | within that element. Accepts a selector string, *but if you use jQuery 1.6+ it 506 | also accepts a raw HTML element or jQuery object. 507 | 508 | continuous 509 | boolean 510 | default: true 511 | If true, and multiple waypoints are triggered in one scroll, this waypoint will 512 | trigger even if it is not the last waypoint reached. If false, it will only 513 | trigger if it is the last waypoint. 514 | 515 | offset 516 | number | string | function 517 | default: 0 518 | Determines how far the top of the element must be from the top of the browser 519 | window to trigger a waypoint. It can be a number, which is taken as a number 520 | of pixels, a string representing a percentage of the viewport height, or a 521 | function that will return a number of pixels. 522 | 523 | onlyOnScroll 524 | boolean 525 | default: false 526 | If true, this waypoint will not trigger if an offset change during a refresh 527 | causes it to pass the current scroll point. 528 | 529 | triggerOnce 530 | boolean 531 | default: false 532 | If true, the waypoint will be destroyed when triggered. 533 | 534 | An offset of 250 would trigger the waypoint when the top of the element is 250px 535 | from the top of the viewport. Negative values for any offset work as you might 536 | expect. A value of -100 would trigger the waypoint when the element is 100px above 537 | the top of the window. 538 | 539 | offset: '100%' 540 | 541 | A string percentage will determine the pixel offset based on the height of the 542 | window. When resizing the window, this offset will automatically be recalculated 543 | without needing to call $.waypoints('refresh'). 544 | 545 | // The bottom of the element is in view 546 | offset: function() { 547 | return $.waypoints('viewportHeight') - $(this).outerHeight(); 548 | } 549 | 550 | Offset can take a function, which must return a number of pixels from the top of 551 | the window. The this value will always refer to the raw HTML element of the 552 | waypoint. As with % values, functions are recalculated automatically when the 553 | window resizes. For more on recalculating offsets, see $.waypoints('refresh'). 554 | 555 | An offset value of 'bottom-in-view' will act as an alias for the function in the 556 | example above, as this is a common usage. 557 | 558 | offset: 'bottom-in-view' 559 | 560 | You can see this alias in use on the Scroll Analytics example page. 561 | 562 | The triggerOnce flag, if true, will destroy the waypoint after the first trigger. 563 | This is just a shortcut for calling .waypoint('destroy') within the waypoint 564 | handler. This is useful in situations such as scroll analytics, where you only 565 | want to record an event once for each page visit. 566 | 567 | The context option lets you use Waypoints within an element other than the window. 568 | You can define the context with a selector string and the waypoint will act within 569 | the nearest ancestor that matches this selector. 570 | 571 | $('.something-scrollable .waypoint').waypoint({ 572 | context: '.something-scrollable' 573 | }); 574 | 575 | You can see this in action on the Dial Controls example. 576 | */ 577 | $.fn[wp].defaults = { 578 | continuous: true, 579 | offset: 0, 580 | triggerOnce: false, 581 | context: window 582 | }; 583 | 584 | 585 | 586 | 587 | 588 | /* 589 | jQuery object extension. Delegates to appropriate methods above. 590 | */ 591 | $[wps] = function(method) { 592 | if (jQMethods[method]) { 593 | return jQMethods[method].apply(this); 594 | } 595 | else { 596 | return jQMethods['aggregate'](); 597 | } 598 | }; 599 | 600 | 601 | /* 602 | $.waypoints.settings 603 | 604 | Settings object that determines some of the plugin’s behavior. 605 | 606 | resizeThrottle 607 | number 608 | default: 200 609 | For performance reasons, the refresh performed during resizes is 610 | throttled. This value is the rate-limit in milliseconds between resize 611 | refreshes. For more information on throttling, check out Ben Alman’s 612 | throttle / debounce plugin. 613 | http://benalman.com/projects/jquery-throttle-debounce-plugin/ 614 | 615 | scrollThrottle 616 | number 617 | default: 100 618 | For performance reasons, checking for any crossed waypoints during a 619 | scroll event is throttled. This value is the rate-limit in milliseconds 620 | between scroll checks. For more information on throttling, check out Ben 621 | Alman’s throttle / debounce plugin. 622 | http://benalman.com/projects/jquery-throttle-debounce-plugin/ 623 | */ 624 | $[wps].settings = { 625 | resizeThrottle: 200, 626 | scrollThrottle: 100 627 | }; 628 | 629 | $w.load(function() { 630 | // Calculate everything once on load. 631 | $[wps]('refresh'); 632 | }); 633 | })(jQuery, 'waypoint', 'waypoints', this); 634 | --------------------------------------------------------------------------------