├── .gitignore ├── .travis.yml ├── composer.json ├── phpunit.xml.dist ├── readme.md ├── src └── Turbo │ ├── Provider │ ├── Fuel │ │ └── Response.php │ └── Laravel │ │ └── TurboServiceProvider.php │ └── Turbo.php └── tests └── TurboTests └── TurboTest.php /.gitignore: -------------------------------------------------------------------------------- 1 | ## MAC OS 2 | .DS_Store 3 | 4 | ## TEXTMATE 5 | *.tmproj 6 | tmtags 7 | 8 | ## EMACS 9 | *~ 10 | \#* 11 | .\#* 12 | 13 | ## VIM 14 | *.swp 15 | 16 | ## PHPDoc files 17 | *.ini 18 | 19 | ## Composer 20 | composer.lock 21 | vendor 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - hhvm 8 | 9 | matrix: 10 | allow_failures: 11 | - php: hhvm 12 | 13 | before_script: 14 | - curl -s https://getcomposer.org/installer | php 15 | - php ./composer.phar install --dev --prefer-source 16 | 17 | script: phpunit --configuration phpunit.xml.dist 18 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rcrowe/turbo", 3 | "description": "Think turbolinks but for your PHP application. Powered by pjax.", 4 | "keywords": ["pjax", "turbolinks", "laravel", "fuelphp"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Rob Crowe", 9 | "email": "hello@vivalacrowe.com" 10 | } 11 | ], 12 | "require": { 13 | "php": ">=5.3.0" 14 | }, 15 | "autoload": { 16 | "psr-0": { 17 | "Turbo": "src/" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Turbo 2 | ===== 3 | 4 | Turbolinks but for your PHP application; powered by [PJAX](https://github.com/defunkt/jquery-pjax). 5 | 6 | [![Build Status](https://travis-ci.org/rcrowe/Turbo.png?branch=master)](https://travis-ci.org/rcrowe/Turbo) 7 | 8 | Installation 9 | ------------ 10 | 11 | Turbo has only been tested installing through [Composer](http://getcomposer.org/). 12 | 13 | Add `rcrowe\turbo` as a requirement to composer.json: 14 | 15 | ```javascript 16 | { 17 | "require": { 18 | "rcrowe/turbo": "0.2.*" 19 | } 20 | } 21 | ``` 22 | 23 | Update your packages with `composer update` or install with `composer install`. 24 | 25 | Providers 26 | --------- 27 | 28 | Providers enable instant usage of Turbo within different frameworks, we currently provide the following integrations: 29 | 30 | **Laravel** 31 | 32 | Add `Turbo\Provider\Laravel\TurboServiceProvider` to `app/config/app.php` and your good to go. 33 | 34 | The Laravel provider also registers a `turbo.pjax` event so that other parts of your app can listen for a pjax request. For example: 35 | 36 | ```php 37 | Event::listen('turbo.pjax', function($request, $response) { 38 | $response->header('X-App-Msg', 'Hello world'); 39 | }); 40 | ``` 41 | 42 | **Fuelphp** 43 | 44 | Add `class_alias('Turbo\\Provider\\Fuel\\Response', 'Response')` to the bottom of `fuel\app\bootstrap.php` and your good to go. 45 | 46 | Turbo also registers a `turbo.pjax` event that you can listen for. For example: 47 | 48 | ```php 49 | Event::register('turbo.pjax', function() { 50 | echo 'This is a pjax request'; 51 | }); 52 | ``` 53 | 54 | PJAX 55 | ---- 56 | 57 | To make this all work Turbo needs [PJAX](https://github.com/defunkt/jquery-pjax) to get and set the response. 58 | Just like Turbolinks we respond with the whole body, not just a section of it. In order to support this, you need 59 | to setup PJAX to use the `` tag. A simple example of this would be: 60 | 61 | ```js 62 | $(function() { 63 | $(document).pjax('.js-pjax', 'body'); 64 | }); 65 | ``` 66 | 67 | License 68 | ------- 69 | 70 | Turbo is released under the MIT public license. 71 | -------------------------------------------------------------------------------- /src/Turbo/Provider/Fuel/Response.php: -------------------------------------------------------------------------------- 1 | 7 | * @license MIT 8 | */ 9 | 10 | namespace Turbo\Provider\Fuel; 11 | 12 | use Event; 13 | use Turbo\Turbo; 14 | 15 | /** 16 | * Brings the power of Turbo/PJAX to Fuel. 17 | */ 18 | class Response extends \Fuel\Core\Response 19 | { 20 | /** 21 | * Get the content/body to return to browser. 22 | * 23 | * If the request is a pjax one, only the body is returned. 24 | * 25 | * @param string $value 26 | * @return string 27 | */ 28 | public function body($value = false) 29 | { 30 | $value AND $this->body = $value; 31 | 32 | // Deal with pjax request 33 | $turbo = new Turbo; 34 | 35 | if ($turbo->isPjax()) { 36 | $this->body = $turbo->extract((string)$this->body); 37 | } 38 | 39 | // Fire event, then remove so that not called multiple times 40 | Event::trigger('turbo.pjax'); 41 | Event::unregister('turbo.pjax'); 42 | 43 | return parent::body($this->body); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Turbo/Provider/Laravel/TurboServiceProvider.php: -------------------------------------------------------------------------------- 1 | 7 | * @license MIT 8 | */ 9 | 10 | namespace Turbo\Provider\Laravel; 11 | 12 | use Illuminate\Support\ServiceProvider; 13 | use App; 14 | use Event; 15 | use Turbo\Turbo; 16 | 17 | /** 18 | * Brings the power of Turbo/PJAX to Laravel. 19 | */ 20 | class TurboServiceProvider extends ServiceProvider 21 | { 22 | /** 23 | * Register the service provider. 24 | * 25 | * @return void 26 | */ 27 | public function register() 28 | { 29 | App::after(function($request, $response) { 30 | 31 | $turbo = new Turbo; 32 | 33 | if ($turbo->isPjax()) { 34 | 35 | if (is_a($response, 'Illuminate\Http\Response')) { 36 | 37 | // Extract the body from the response 38 | $content = (string)$response->getOriginalContent(); 39 | $body = $turbo->extract($content); 40 | 41 | // Set new response content 42 | $response->setContent($body); 43 | } 44 | 45 | // Fire that we are in a pjax request 46 | Event::fire('turbo.pjax', array($request, $response)); 47 | } 48 | }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Turbo/Turbo.php: -------------------------------------------------------------------------------- 1 | 7 | * @license MIT 8 | */ 9 | 10 | namespace Turbo; 11 | 12 | /** 13 | * Returns the body of the HTML along with any title. 14 | * 15 | * Checks for a PJAX request and tailors the response. 16 | */ 17 | class Turbo 18 | { 19 | /** 20 | * Create a new instance of Turbo. 21 | */ 22 | public function __construct() 23 | { 24 | } 25 | 26 | /** 27 | * Is this request a PJAX request? 28 | */ 29 | public function isPjax() 30 | { 31 | return (isset($_SERVER['HTTP_X_PJAX']) OR isset($_GET['_pjax'])); 32 | } 33 | 34 | /** 35 | * If PJAX request, extract HTML body. Any problems we just sent back the original content. 36 | * 37 | * @param string $content Original content you want to extract. 38 | * @return string 39 | */ 40 | public function extract($content) 41 | { 42 | // Send back the original content if we aren't supposed to be extracting 43 | if (!is_string($content) OR !$this->isPjax()) { 44 | return $content; 45 | } 46 | 47 | // We only process if we find a valid 48 | preg_match('/(?:]*>)(.*)<\/body>/isU', $content, $matches); 49 | 50 | // Did we find the body 51 | if (count($matches) !== 2) { 52 | return $content; 53 | } 54 | 55 | $body = $matches[1]; 56 | 57 | // Does the page have a title 58 | preg_match('@([^<]+)@', $content, $matches); 59 | 60 | // Did we find the title 61 | $title = (count($matches) === 2) ? $matches[0] : ''; 62 | 63 | // Set new content 64 | return $title.$body; 65 | } 66 | } -------------------------------------------------------------------------------- /tests/TurboTests/TurboTest.php: -------------------------------------------------------------------------------- 1 | turbo = new Turbo; 13 | $this->setIsPjax(); 14 | } 15 | 16 | public function tearDown() 17 | { 18 | $this->removeIsPjax(); 19 | } 20 | 21 | public function testIsPjax() 22 | { 23 | $this->assertTrue($this->turbo->isPjax()); 24 | $this->removeIsPjax(); 25 | $this->assertFalse($this->turbo->isPjax()); 26 | 27 | // Test different variables that effect isPjax 28 | $_SERVER['HTTP_X_PJAX'] = true; 29 | $this->assertTrue($this->turbo->isPjax()); 30 | $this->removeIsPjax(); 31 | 32 | $_GET['_pjax'] = true; 33 | $this->assertTrue($this->turbo->isPjax()); 34 | $this->removeIsPjax(); 35 | } 36 | 37 | public function testNotString() 38 | { 39 | foreach (array(null, 1234, new \stdClass, true) as $check) { 40 | $this->assertEquals($check, $this->turbo->extract($check)); 41 | } 42 | } 43 | 44 | public function testNoBodyFound() 45 | { 46 | foreach (array('test', 'test', 'test') as $check) { 47 | $this->assertEquals($check, $this->turbo->extract($check)); 48 | } 49 | } 50 | 51 | public function testBodyTagRemoved() 52 | { 53 | $str = 'This is just a test'; 54 | $content = $this->turbo->extract(''.$str.''); 55 | 56 | $this->assertEquals($str, $content); 57 | } 58 | 59 | public function testNoTitleFound() 60 | { 61 | // opening tag 62 | $content = $this->turbo->extract('Noodle<body>Test</body>'); 63 | $this->assertEquals('Test', $content); 64 | 65 | // closing tag 66 | $content = $this->turbo->extract('NoodleTest'); 67 | $this->assertEquals('Test', $content); 68 | } 69 | 70 | public function testTitleFound() 71 | { 72 | $html = ''; 73 | $html .= 'Hello world'; 74 | $html .= 'Butter Bean'; 75 | $html .= ''; 76 | 77 | $this->assertEquals('Hello worldButter Bean', $this->turbo->extract($html)); 78 | } 79 | 80 | public function testIsNotPjaxExtract() 81 | { 82 | $this->removeIsPjax(); 83 | $str = 'test'; 84 | 85 | $this->assertEquals($str, $this->turbo->extract($str)); 86 | } 87 | 88 | public function setIsPjax() 89 | { 90 | $_SERVER['HTTP_X_PJAX'] = true; 91 | $_GET['_pjax'] = true; 92 | } 93 | 94 | public function removeIsPjax() 95 | { 96 | unset($_SERVER['HTTP_X_PJAX'], $_GET['_pjax']); 97 | } 98 | } --------------------------------------------------------------------------------