├── .gitignore
├── .travis.yml
├── Appraisals
├── Gemfile
├── LICENSE
├── README.md
├── Rakefile
├── gemfiles
├── rack_1.gemfile
└── rack_2.gemfile
├── lib
├── rack-pjax.rb
└── rack
│ ├── pjax.rb
│ └── pjax
│ └── version.rb
├── rack-pjax.gemspec
└── spec
├── rack
└── pjax_spec.rb
├── spec.opts
└── spec_helper.rb
/.gitignore:
--------------------------------------------------------------------------------
1 | *.gem
2 | .bundle
3 | Gemfile.lock
4 | pkg/*
5 | .rvmrc
6 | .rbenv-version
7 | gemfiles/*.lock
8 |
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | rvm:
2 | - 1.9.3
3 | - 2.1.10
4 | - 2.2.7
5 | - 2.3.4
6 | - 2.4.1
7 | - jruby
8 | gemfile:
9 | - gemfiles/rack_1.gemfile
10 | - gemfiles/rack_2.gemfile
11 | matrix:
12 | exclude:
13 | - rvm: 1.9.3
14 | gemfile: gemfiles/rack_2.gemfile
15 | - rvm: 2.1.10
16 | gemfile: gemfiles/rack_2.gemfile
17 | - rvm: jruby
18 | gemfile: gemfiles/rack_2.gemfile
19 | cache: bundler
20 | sudo: false
21 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise "rack-1" do
2 | gem 'rack', '~> 1.0'
3 | gem 'nokogiri', '~> 1.6.8'
4 | end
5 |
6 | appraise "rack-2" do
7 | gem 'rack', '~> 2.0'
8 | end
9 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | gem 'appraisal'
4 |
5 | gemspec
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2011 Gert Goet, ThinkCreate
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining
4 | a copy of this software and associated documentation files (the
5 | "Software"), to deal in the Software without restriction, including
6 | without limitation the rights to use, copy, modify, merge, publish,
7 | distribute, sublicense, and/or sell copies of the Software, and to
8 | permit persons to whom the Software is furnished to do so, subject to
9 | the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be
12 | included in all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Rack-pjax [](https://secure.travis-ci.org/#!/eval/rack-pjax)
2 | ========
3 |
4 | Rack-pjax is middleware that lets you serve 'chrome-less' pages in respond to [pjax-requests](https://github.com/defunkt/jquery-pjax).
5 |
6 | It does this by stripping the generated body; only the title and inner-html of the pjax-container are sent to the client.
7 |
8 | While this won't save you any time rendering the page, it gives you more flexibility where and how to define the pjax-container.
9 | Ryan Bates featured [rack-pjax on Railscasts](http://railscasts.com/episodes/294-playing-with-pjax) and explains how this gem compares to [pjax_rails](https://github.com/rails/pjax_rails).
10 |
11 | [](http://railscasts.com/)
12 |
13 | Installation
14 | ------------
15 |
16 | Check out the [Railscasts' notes](http://railscasts.com/episodes/294-playing-with-pjax) how to integrate rack-pjax in your Rails 3.1 application.
17 |
18 | You can find the source from the screencast over [here](https://github.com/ryanb/railscasts-episodes/tree/master/episode-294).
19 |
20 | Another sample-app: the original [pjax-demo](http://pjax.herokuapp.com/) but with rack-pjax onboard can be found in the [sample-app](https://github.com/eval/rack-pjax/tree/sample-app) branch.
21 |
22 | The more generic installation comes down to:
23 |
24 | I. Add the gem to your Gemfile
25 |
26 | ```ruby
27 | # Gemfile
28 | gem "rack-pjax"
29 | ```
30 |
31 | II. Include **rack-pjax** as middleware to your application(-stack)
32 |
33 | ```ruby
34 | # config.ru
35 | require ::File.expand_path('../config/environment', __FILE__)
36 | use Rack::Pjax
37 | run RackApp::Application
38 | ```
39 |
40 | III. Install [jquery-pjax](https://github.com/defunkt/jquery-pjax). Make sure to add the 'data-pjax-container'-attribute to the container.
41 |
42 | ```html
43 |
44 | ...
45 |
46 |
47 |
52 | ...
53 |
54 |
55 |
56 | ...
57 |
58 |
59 | ```
60 |
61 | (For more see [the docs of jquery-pjax](https://github.com/defunkt/jquery-pjax#usage).)
62 |
63 | IV. Fire up your [pushState-enabled browser](http://caniuse.com/#search=pushstate) and enjoy!
64 |
65 |
66 | Requirements
67 | ------------
68 |
69 | - Nokogiri
70 |
71 |
72 | Contributors
73 | ------
74 |
75 | [The contributors](https://github.com/eval/rack-pjax/graphs/contributors).
76 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler/gem_tasks'
2 | require "rspec/core/rake_task"
3 |
4 | RSpec::Core::RakeTask.new do |t|
5 | t.rspec_opts = ['--options', "\"#{File.dirname(__FILE__)}/spec/spec.opts\""]
6 | end
7 |
8 | desc "Run the specs"
9 | task :default => :spec
10 |
11 | desc 'Removes trailing whitespace'
12 | task :whitespace do
13 | sh %{find . -name '*.rb' -exec sed -i '' 's/ *$//g' {} \\;}
14 | end
15 |
--------------------------------------------------------------------------------
/gemfiles/rack_1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "rack", "~> 1.0"
7 | gem "nokogiri", "~> 1.6.8"
8 |
9 | gemspec :path => "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/rack_2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "http://rubygems.org"
4 |
5 | gem "appraisal"
6 | gem "rack", "~> 2.0"
7 |
8 | gemspec :path => "../"
9 |
--------------------------------------------------------------------------------
/lib/rack-pjax.rb:
--------------------------------------------------------------------------------
1 | require 'rack'
2 | require "rack/pjax"
3 |
--------------------------------------------------------------------------------
/lib/rack/pjax.rb:
--------------------------------------------------------------------------------
1 | require 'nokogiri'
2 |
3 | module Rack
4 | class Pjax
5 | include Rack::Utils
6 |
7 | def initialize(app)
8 | @app = app
9 | end
10 |
11 | def call(env)
12 | status, headers, body = @app.call(env)
13 | return [status, headers, body] unless pjax?(env)
14 |
15 | headers = HeaderHash.new(headers)
16 |
17 | new_body = ""
18 | body.each do |b|
19 | b = b.dup.force_encoding('UTF-8') if RUBY_VERSION > '1.9.0'
20 |
21 | parsed_body = Nokogiri::HTML(b)
22 | container = parsed_body.at(container_selector(env))
23 |
24 | new_body << begin
25 | if container
26 | title = parsed_body.at("title")
27 |
28 | "%s%s" % [title, container.inner_html]
29 | else
30 | b
31 | end
32 | end
33 | end
34 |
35 | body.close if body.respond_to?(:close)
36 |
37 | headers['Content-Length'] &&= new_body.bytesize.to_s
38 | headers['X-PJAX-URL'] ||= Rack::Request.new(env).fullpath
39 |
40 | [status, headers, [new_body]]
41 | end
42 |
43 | protected
44 | def pjax?(env)
45 | env['HTTP_X_PJAX']
46 | end
47 |
48 | def container_selector(env)
49 | env['HTTP_X_PJAX_CONTAINER'] || "[@data-pjax-container]"
50 | end
51 | end
52 | end
53 |
--------------------------------------------------------------------------------
/lib/rack/pjax/version.rb:
--------------------------------------------------------------------------------
1 | module Rack
2 | class Pjax
3 | VERSION = "1.1.0"
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/rack-pjax.gemspec:
--------------------------------------------------------------------------------
1 | # -*- encoding: utf-8 -*-
2 | $:.push File.expand_path("../lib", __FILE__)
3 | require "rack/pjax/version"
4 |
5 | Gem::Specification.new do |s|
6 | s.name = "rack-pjax"
7 | s.version = Rack::Pjax::VERSION
8 | s.authors = ["Gert Goet"]
9 | s.email = ["gert@thinkcreate.nl"]
10 | s.homepage = "https://github.com/eval/rack-pjax"
11 | s.license = "MIT"
12 | s.summary = %q{Serve pjax responses through rack middleware}
13 | s.description = %q{Serve pjax responses through rack middleware}
14 |
15 | s.rubyforge_project = "rack-pjax"
16 |
17 | s.files = `git ls-files`.split("\n")
18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20 | s.require_paths = ["lib"]
21 |
22 | s.add_dependency('rack', '>= 1.1')
23 | s.add_dependency('nokogiri', '~> 1.5')
24 |
25 | s.add_development_dependency "rake"
26 | s.add_development_dependency "rspec"
27 | s.add_development_dependency "rack-test"
28 | end
29 |
--------------------------------------------------------------------------------
/spec/rack/pjax_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2 |
3 | describe Rack::Pjax do
4 | include Rack::Test::Methods # can be moved to config
5 |
6 | def generate_app(options={})
7 | body = options[:body]
8 |
9 | Rack::Lint.new(
10 | Rack::Pjax.new(
11 | lambda do |env|
12 | [
13 | 200,
14 | {'Content-Type' => 'text/plain', 'Content-Length' => body.bytesize.to_s},
15 | [body]
16 | ]
17 | end
18 | )
19 | )
20 | end
21 |
22 | context "a pjaxified app, upon receiving a pjax-request" do
23 | before do
24 | self.class.app = generate_app(:body => 'HelloWorld!
')
25 | end
26 |
27 | it "should return the title-tag in the body" do
28 | get "/", {}, {"HTTP_X_PJAX" => "true"}
29 | expect(body).to eq("HelloWorld!")
30 | end
31 |
32 | it "should return the inner-html of the pjax-container in the body" do
33 | self.class.app = generate_app(:body => 'World!
')
34 |
35 | get "/", {}, {"HTTP_X_PJAX" => "true"}
36 | expect(body).to eq("World!")
37 | end
38 |
39 | it "should return the inner-html of the custom pjax-container in the body" do
40 | self.class.app = generate_app(:body => 'World!
')
41 |
42 | get "/", {}, {"HTTP_X_PJAX" => "true", "HTTP_X_PJAX_CONTAINER" => "#container"}
43 | expect(body).to eq("World!")
44 | end
45 |
46 | it "should handle self closing tags with HTML5 elements" do
47 | self.class.app = generate_app(:body => 'World!
')
48 |
49 | get "/", {}, {"HTTP_X_PJAX" => "true"}
50 |
51 | expect(body).to eq('World!
')
52 | end
53 |
54 | it "should handle nesting of elements inside anchor tags" do
55 | self.class.app = generate_app(:body => '')
56 |
57 | get "/", {}, {"HTTP_X_PJAX" => "true"}
58 |
59 | expect(body).to eq('World!
')
60 | end
61 |
62 | it "should handle html5 br tags correctly" do
63 | self.class.app = generate_app(:body => '')
64 |
65 | get "/", {}, {"HTTP_X_PJAX" => "true"}
66 |
67 | expect(body).to eq('foo
bar
')
68 | end
69 |
70 | it "should handle frozen body string correctly" do
71 | self.class.app = generate_app(:body => ''.freeze)
72 |
73 | get "/", {}, {"HTTP_X_PJAX" => "true"}
74 |
75 | expect(body).to eq('foo
bar
')
76 | end
77 |
78 | it "should return the correct Content Length" do
79 | get "/", {}, {"HTTP_X_PJAX" => "true"}
80 | expect(headers['Content-Length']).to eq(body.bytesize.to_s)
81 | end
82 |
83 | it "should return the original body when there's no pjax-container" do
84 | self.class.app = generate_app(:body => 'Has no pjax-container')
85 |
86 | get "/", {}, {"HTTP_X_PJAX" => "true"}
87 | expect(body).to eq("Has no pjax-container")
88 | end
89 |
90 | it "should preserve whitespaces of the original body" do
91 | container = "\n \nfirst paragraph
Second paragraph
\n"
92 | self.class.app = generate_app(:body =><<-BODY)
93 |
94 | #{container}
95 |
96 | BODY
97 |
98 | get "/", {}, {"HTTP_X_PJAX" => "true"}
99 | expect(body).to eq(container)
100 | end
101 | end
102 |
103 | context "a pjaxified app, upon receiving a non-pjax request" do
104 | before do
105 | self.class.app = generate_app(:body => 'HelloWorld!
')
106 | end
107 |
108 | it "should return the original body" do
109 | get "/"
110 | expect(body).to eq('HelloWorld!
')
111 | end
112 |
113 | it "should return the correct Content Length" do
114 | get "/"
115 | expect(headers['Content-Length']).to eq(body.bytesize.to_s)
116 | end
117 | end
118 | end
119 |
--------------------------------------------------------------------------------
/spec/spec.opts:
--------------------------------------------------------------------------------
1 | --color
2 | --format d
3 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require "rubygems"
2 | require "bundler"
3 | Bundler.setup
4 |
5 | $:.unshift File.expand_path("../../lib", __FILE__)
6 | require "rack-pjax"
7 | require 'rack/test'
8 |
9 | Bundler.require(:test)
10 |
11 | # helpers ripped from wycat's Rack::Offline
12 | # (https://github.com/wycats/rack-offline/blob/master/spec/spec_helper.rb)
13 | module Rack::Test::Methods
14 | def self.included(klass)
15 | class << klass
16 | attr_accessor :app
17 | end
18 | end
19 |
20 | def body
21 | last_response.body
22 | end
23 |
24 | def status
25 | last_response.status
26 | end
27 |
28 | def headers
29 | last_response.headers
30 | end
31 |
32 | def app
33 | self.class.app
34 | end
35 | end
36 |
--------------------------------------------------------------------------------