Vocabulary
Â
HTTP - HyperText Transfer Protocol
CSRF - Cross-Site Request Forgery
JSON - JavaScript Object Notation
SPA - Single Page Application
DOM - Document Object Model
Bundle thick Javascript
client
Modules aka Javascript Gems
JSON REST API
GET request
JSON response
Install Browserify-rails gem
app/assets/javascripts
app/assets/javascripts/components/
node_modules/
package.json
.buildpacks
Things added to a standard RoR project
Node Package Manager
Install Node.js
package.json
npm install
npm run task
Gemfile + Rakefile
bundle install
rake task
{
"//": "package.json file",
"scripts": {
"test": "mocha"
},
"dependencies": {
"dependency": "6.3"
}
}
# Rakefile
task :test do
ruby "test/unittest.rb"
end
# Gemfile
gem 'dependency', '6.3'
+
~
Point sprockets to our SPA
// app/assets/javascripts/application.js
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//
//= require client
// require_tree .
// app/assets/javascripts/client.js
var React = require('react')
var Router = require('react-router')
var routes = require('./routes.jsx')
Router.run(routes, function (Handler) {
React.render(React.createElement(Handler, null),
document.getElementById('content'))
})
Initialize React onto a DOM element
In your view
<div id="content"></div>
In your view
<% if user_signed_in? %>
<div id="content"></div>
<% else %>
<div class="container">
<div class="row">
<div class="col-xs-12 col-xs-offset-0 welcome">
<h1>Welcome</h1>
</div>
</div>
</div>
<% end %>
Demo
CSRF
<%-# view.erb -%>
<%= csrf_meta_tags %>
<!-- renders -->
...
<meta name="csrf-token" content="MA2lN1qA==">
...
// globals.js
var $ = require('jquery')
function globular () {
var csrf = $('meta[name="csrf-token"]')
.attr('content')
return {
csrf: csrf
}
}
module.exports = globular()
CSRF
var $ = require('jquery')
var globals = require('../globular.js')
var csrfToken = globals.csrf
$.ajax({
url: 'logout',
type: 'DELETE',
headers: {
'X-CSRF-Token': csrfToken
},
success: function(data) {
debug('successful delete', data)
window.location.replace(window.location.origin)
}
})
.fail(function() {
debug('logout failed')
})
You should namespace your SPA global variables. I use this approach.
Deploying to Heroku
Deploying to Heroku
bundle
npm install
Deployment time increases
Use a buildpack
Testing with Mocha
Testing with Mocha
To install
$ npm install --save mocha
To run
$ mocha spec/javascripts
or
package.json
{
"scripts": {
"test": "mocha spec/javascripts"
}
}
$ npm run test
class ModelController < ApplicationController
def model
@model = Model.find(params[:id])
render json: @model
end
end
{
"id": 108,
"created_at": "2015-03-27T03:12:57Z",
"updated_at": "2015-03-27T03:12:59Z"
}
Get /model/:id
class ModelController < ApplicationController
def update
@model = Model.find(params[:id])
@model.update!(safe_params)
render nothing: true
end
private
def safe_params
params.require(:model).permit(:foo)
end
end
$.ajax({
type: 'PUT',
url: 'models/' + Id,
headers: {
'X-CSRF-Token': csrfToken
},
data: {model: {foo: 'bar'}},
dataType: 'json',
success: function (res) {
console.log(res)
}
})
PUT /model/:id
React.js
// app.jsx
var React = require('react')
var Searchbox = require('/searchbox.jsx')
var List = require('/list.jsx')
var ListItem = require('/listItem.jsx')
var ListTitle = require('/listTitle.jsx')
var App = React.createClass({
render: function() {
return (
<div>
<Searchbox />
<List>
<ListTitle title="Sporting goods"/>
<ListItem name="football" price="49.99" />
<ListItem name="baseball" price="9.99" />
<ListItem name="basketball" price="29.99" />
<ListTitle title="Electronics"/>
<ListItem name="iPod Touch" price="99.99" />
<ListItem name="iPhone 5" price="399.99" />
<ListItem name="Nexus 7" price="199.99" />
</List>
</div>
)
}
})
module.exports = App
React.js
React.js
Future Work
Help us out!