Rails & Javascript
Faça isso direito!
Sacadas de como organizar seu javascript
em apps Rails não SPA
$ whoami
• Cezinha Anjos.
• Comecei programando num Apple II
e gravando programas em fitas
cassetes há 26 anos atrás.
• Atualmente focado em Ruby on Rails
e Javascript.
• CEO da ASSEINFO.
Rails & Javascript
Faça isso direito!
Sacadas de como organizar seu javascript
em apps Rails não SPA
Do que vamos falar?
// Caro mantenedor:
//
// Quando eu escrevi este código, somente eu
// e Deus sabíamos o que ele fazia.
// Agora somente Deus sabe!
//
// Então, se você está tentando “otimizar"
// esta rotina (e falhou!), por favor,
// incremente o contador a seguir como um aviso
// para o próximo cara:
//
// total_de_horas_gastas_aqui = 67
ORGANIZAÇÃO
App tradicional
Rails
Organizar o JS
Aplicar um
framework JS
Separar o
front-end do
back-end
App tradicional
Rails
Organizar o JS
Aplicar um
framework JS
Separar o
front-end do
back-end
App tradicional
Rails
Organizar o JS
Aplicar um
framework JS
Separar o
front-end do
back-end
App tradicional
Rails
Organizar o JS
Aplicar um
framework JS
Separar o
front-end do
back-end
App tradicional
Rails
Organizar o JS
Aplicar um
framework JS
Separar o
front-end do
back-end
Formato do talk
Sacadas
X
Use o Gemfile somente
para as dependências
do back-end
9901
Independência de um fornecedor
que encapsule a library
Maior domínio sobre o que você
quer incluir no seu app
# Arquivo: Gemfile
source 'https://rubygems.org'
 
gem 'rails', '4.2.0'
gem 'sqlite3'
gem 'sass-rails', '~> 5.0'
gem 'uglifier', '>= 1.3.0'
gem 'coffee-rails', '~> 4.1.0’
gem 'jquery-rails'
X
Use o bower para as
dependências do frontend
02
É o bundler do front-end
Virou padrão no mundo
front-end
Tudo que você imagina de front-
end é publicado no bower
E o que não está… basta
referenciar o github
// arquivo: bower.json
 
{
"name": "rails-and-js",
"version": "0.0.0",
"authors": [
"Cezinha
<cesar@asseinfo.com.br>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"jquery": "2.0.3",
"jquery-ujs": "1.0.3"
}
}
X
Crie uma pasta para cada
view e pelo menos um
arquivo para cada action
03
Uma pasta para
cada view
Pelo menos um
arquivo por action
Facilidade em localizar os
scripts
Evita a escrita de scripts
gigantescos
X
Aceite que todos os seus
javascripts acabarão em um
único arquivo
04
Visão do programador
4 arquivos separados
// arquivo: app/assets/javascripts/views/people/index.js
 
alert("Running index.js");
// arquivo: app/assets/javascripts/views/people/edit.js
 
alert("Running edit.js");
// arquivo: app/assets/javascripts/views/people/show.js
 
alert("Running show.js");
// arquivo: app/assets/javascripts/views/people/new.js
 
alert("Running new.js");
// arquivo: application.js
// na visão do sprockets
// sem "uglificação"
 
alert("Running edit.js");
alert("Running index.js");
alert("Running new.js");
alert("Running show.js");
Visão do sprockets
Você não tem como separar os
scripts sem aumentar o número de
requisições.
X
O segredo é modularizar05
http://larsjung.de/modulejs/
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
var mymodule = modulejs.require("people.index");
console.log(mymodule);
mymodule();
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
var mymodule = modulejs.require("people.index");
console.log(mymodule);
mymodule();
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
var mymodule = modulejs.require("people.index");
console.log(mymodule);
mymodule();
// Resultado do console:
function () { "use string"; alert("Running index.js"); }
modulejs.define("people.index", function() {
return function() {
"use string";
alert("Running index.js”);
};
});
var mymodule = modulejs.require("people.index");
console.log(mymodule);
mymodule();
// Resultado do console:
function () { "use string"; alert("Running index.js"); }
X
Estabeleça um
Single Entry Point
06
Defina um único ponto para início de
execução de todo o seu Javascript
O fluxo de execução tende a
ficar mais claro
Carregamento
da página
Executa JS de
terceiros
Dispatcher
(boot.js)
people/index.js
foo.js
people/new.js people/edit.js people/show.js
bar.js
x.js y.js
Fluxo de
execução
Carregamento
da página
Executa JS de
terceiros
Dispatcher
(boot.js)
people/index.js
foo.js
people/new.js people/edit.js people/show.js
bar.js
x.js y.js
Fluxo de
execução
Carregamento
da página
Executa JS de
terceiros
Dispatcher
(boot.js)
people/index.js
foo.js
people/new.js people/edit.js people/show.js
bar.js
x.js y.js
Fluxo de
execução
Carregamento
da página
Executa JS de
terceiros
Dispatcher
(boot.js)
people/index.js
foo.js
people/new.js people/edit.js people/show.js
bar.js
x.js y.js
Fluxo de
execução
Carregamento
da página
Executa JS de
terceiros
Dispatcher
(boot.js)
people/index.js
foo.js
people/new.js people/edit.js people/show.js
bar.js
x.js y.js
Fluxo de
execução
Carregamento
da página
Executa JS de
terceiros
Dispatcher
(boot.js)
people/index.js
foo.js
people/new.js people/edit.js people/show.js
bar.js
x.js y.js
Fluxo de
execução
// arquivo app/assets/javascripts/boot.js
 
(function() {
"use strict";
 
$(document).ready(function() {
// Aqui deve ser o seu primeiro
// ponto de execução de javascript
});
})()
X
Use um dispatcher para
executar o JS de cada view
07
Desafio: como sinalizar para o JS
qual action deve ser executada?
Resposta: através do HTML
gerado pelo servidor
<body dispatcher="people.index">
<body dispatcher="people.index">
Indicamos aqui qual JS deve ser executado
<body <%= dispatcher_tag %>>
<body <%= dispatcher_tag %>>
# arquivo: app/helpers/application_helper.rb
 
module ApplicationHelper
def dispatcher_tag
controller_name = controller.class.name.underscore
controller_name.gsub!(///, "_")
controller_name.gsub!(/_controller$/, "")
 
div_tag = %(dispatcher="#{controller_name}.#{controller.action_name}")
 
div_tag.html_safe
end
end
(function() {
"use strict";
 
$(document).ready(function() {
var dispatch_to = $("body").attr("dispatcher");
var mymodule = modulejs.require(dispatch_to);
mymodule();
});
})()
Single
Entry
Point
(function() {
"use strict";
 
$(document).ready(function() {
var dispatch_to = $("body").attr("dispatcher");
var mymodule = modulejs.require(dispatch_to);
mymodule();
});
})()
Single
Entry
Point
(function() {
"use strict";
 
$(document).ready(function() {
var dispatch_to = $("body").attr("dispatcher");
var mymodule = modulejs.require(dispatch_to);
mymodule();
});
})()
Single
Entry
Point
<body dispatcher="people.index">
(function() {
"use strict";
 
$(document).ready(function() {
var dispatch_to = $("body").attr("dispatcher");
var mymodule = modulejs.require(dispatch_to);
mymodule();
});
})()
Single
Entry
Point
function () { "use string"; alert("Running index.js"); }
(function() {
"use strict";
 
$(document).ready(function() {
var dispatch_to = $("body").attr("dispatcher");
var mymodule = modulejs.require(dispatch_to);
mymodule();
});
})()
Single
Entry
Point
X
js-routes: named routes do
Rails disponíveis no JS
08
Rails.application.routes.draw do
resources :people
end
people GET /people(.:format) people#index
new_person GET /people/new(.:format) people#new
edit_person GET /people/:id/edit(.:format) people#edit
person GET /people/:id(.:format) people#show
$ bin/rake routes
✔
✘
var promise, id;
id = 1; 
promise = $.get("/people/"+id+".json");
gem js-routes
Routes.people_path()
"/people.json"
 
Routes.new_person_path()
"/people/new.json"
 
Routes.edit_person_path(1)
"/people/1/edit.json"
 
Routes.person_path(1)
"/people/1.json"
✘
var promise, id;
id = 1; 
promise = $.get("/people/"+id+".json");
✔
var promise, id;
 
id = 1;
promise = $.get(Routes.person_path(id));
X
Exporte as traduções do
Rails para o JS
09
person.yml
pt-BR:
activerecord:
models:
person: Pessoa
people: Pessoas
attributes:
person:
id: ID
name: Nome
person.yml
pt-BR
✔
I18n.t(:person)
"Pessoa"
gem i18n-js
person.yml
pt-BR
✔
I18n.t(:person)
"Pessoa"
person.yml
pt-BR
✔
I18n.t(“activerecord.att
ributes.models.person”)
"Pessoa"
X
Faça o sprockets servir
templates JS usando ejs
10
✘var name, phone, html;
name = "The name";
phone = “(48) 1234-5678”;
 
html = "<p>Name:</p><p>" + name + "</p>";
html += "<p>Phone:</p><p>" + phone + “</p>”;
$("div#person").html(html)
gem ruby-ejs
// arquivo: application.js
 
//= require jquery
//= require jquery-ujs
//= require modulejs
//= require js-routes
//= require_tree ./templates
<p>Name:</p>
<p><%= name %></p>
 
<p>Phone:</p>
<p><%= phone %></p>
✘var name, phone, html;
name = "The name”;
phone = “(48) 1234-5678”;
 
html = "<p>Name:</p><p>" + name + "</p>";
html += "<p>Phone:</p><p>" + phone + “</p>”;
$("div#person").html(html)
var name, phone, html;
name = "The name”;
phone = “(48) 1234-5678”;
 
html = JST["templates/people/example"]({
name: name, phone: phone });
$("div#person").html(html)
✔
X
Crie escopos com IIFE11
IIFE
Immediately-invoked
function expression
(expressão de função invocada
imediatamente)
IIFE pode ser usada para isolar
escopos
 
(function() {
}())
Immediately-invoked function expression
 
(function() {
}())
Expressão
Immediately-invoked function expression
 
(function() {
}())
Função
Immediately-invoked function expression
 
(function() {
}())
Execução imediata
Immediately-invoked function expression
var foo = "value outside IIFE";
 
(function() {
var foo = "value inside IIFE";
console.log(foo);
}())
 
console.log(foo);
var foo = "value outside IIFE";
 
(function() {
var foo = "value inside IIFE";
console.log(foo);
}())
 
console.log(foo);
var foo = "value outside IIFE";
 
(function() {
var foo = "value inside IIFE";
console.log(foo);
}())
 
console.log(foo);
// Resultado:
// value inside IIFE
// value outside IIFE
Lembre-se que todos os seus
scripts acabarão em um
único arquivo
Sem escopo o resultado da
uglificação pode ser
imprevisível
X
Na boa… “use strict"12
Principal função: converter
enganos em erros!
(function () {
foo = "this should be a private content";
}())
✘
(function () {
foo = "this should be a private content";
}())
✘Falta do “use strict" fará
não gerar erro.
Falta do "var" fará de "foo" global
(function () {
"use strict";
foo = "this should be a private content";
}())
✘
(function () {
"use strict";
foo = "this should be a private content";
}())
✘ReferenceError: Can’t find variable foo
(function () {
"use strict";
var foo = "this should be a private content";
}())
✔
X
Fuja do callback hell
usando promises
13
var promise;
 
promise = $.get("/people.json");
 
promise.done(function(data) {
alert("done");
alert(JSON.stringify(data));
});
 
promise.fail(function(error) {
alert("fail");
alert("status text:" + error.statusText);
});
 
promise.always(function() {
alert("always");
});
var promise;
 
promise = $.get("/people.json");
 
promise.done(function(data) {
alert("done");
alert(JSON.stringify(data));
});
 
promise.fail(function(error) {
alert("fail");
alert("status text:" + error.statusText);
});
 
promise.always(function() {
alert("always");
});
var promise;
 
promise = $.get("/people.json");
 
promise.done(function(data) {
alert("done");
alert(JSON.stringify(data));
});
 
promise.fail(function(error) {
alert("fail");
alert("status text:" + error.statusText);
});
 
promise.always(function() {
alert("always");
});
var promise;
 
promise = $.get("/people.json");
 
promise.done(function(data) {
alert("done");
alert(JSON.stringify(data));
});
 
promise.fail(function(error) {
alert("fail");
alert("status text:" + error.statusText);
});
 
promise.always(function() {
alert("always");
});
var promise;
 
promise = $.get("/people.json");
 
promise.done(function(data) {
alert("done");
alert(JSON.stringify(data));
});
 
promise.fail(function(error) {
alert("fail");
alert("status text:" + error.statusText);
});
 
promise.always(function() {
alert("always");
});
X
So long, and thanks for all the fish!
@cezinha_anjos
http://cezinha.info
http://asseinfo.com.br

RubyConfBr 2015 - Rails & Javascript: faça isso direito